# Hur man analyserar stora CSV-Dater med Lokala LLM i C#

<!--category-- AI, LLM, Data Analysis, DuckDB, C#, Ollama -->
<datetime class="hidden">2025-12-18T10:00</datetime>

**Serien: Lokala LLMs för data - Del | 1 | |**

Här är det misstag som alla gör. De försöker mata sin CSV till en LLM. **LLMs borde generera frågor , inte konsumera data.**

Du' har en 500MB CSV-Datei och vill fråga: " Vad är den genomsnittliga inställningsvaluten per region? [Kopilot i Excel](https://support.microsoft.com/en-gb/copilot-excel) kan göra detta, men vad händer om dina data är för känsliga för molntjänster?

Den här artikeln visar hur - lokalt , privatt

> Använd DuckDB för att fråga CSV-Dater direkt. Använd en lokal LLM för att generera SQL . Den LLM ser aldrig dina data | - bara schemat |. Resultat ♫ : ♫ sub ♫ МSK5 ♫ ms frågor på miljontals ♫

För en komplementär treatment som expanderar på användandet av statistikprofiler som LLM-gränssnittet ( och visar ett komplett verktyg för att implementera dessa idéer | - | profilering |, | säker SQL-modus | МSK5 | syntetiskt kloning och driftdetektion **[DataSummarizer: Vinniga lokaldataprofilering](/blog/datasummarizer-how-it-works)** - i synnerhet i sektionen "Kysyluppgraderingen | : | Statistik som gränssnittet | МSK3 | De här två artiklarna utgör en kort serie om praktisk lokal LLM

[TOC]

## Kerninsikten

Att använda en LLM som dataspeicher är fel abstraktion.

Det korrekta mönstret: **LLM anledningar , databasberäkningar.**

```mermaid
flowchart LR
    A[User Question] --> B[LLM]
    B --> C[SQL Query]
    C --> D[DuckDB]
    D --> E[Results]
    
    style B stroke:#333,stroke-width:4px
    style D stroke:#333,stroke-width:4px
```

Lägg märke till vad som händer.', :, LLM genererar en SQL-fråga baserat på din fråga och schemat.., DuckDB löser den mot den faktiska datan.M SK3, LDM rör aldrig vid dina data. -, det ser bara kolumnernas namn och typer.

För mer information om hur man hanterar profile som LLM-gränssnittet ( och en konkret CLI som implementerar profilen -first narration, säkra SQLM SK3bevarad Q~&~A+,~registret~-~bevarade sessioner och syntetiskt kloning~ ),~ se **[DataSummarizer: Vinniga lokaldataprofilering](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## Varför inte bara ladda in den i minnet?

De uppenbara tillvägagångssätten har samma dödliga brist

**[CsvHelper](https://joshclose.github.io/CsvHelper/) / Dataframer**: Lade hela filen in i RAM. En 500MB CSV blir 2-4GB av objekt

**SQLite / PostgreSQL**: kräver en långsam importerad steg (minuter för stora filer M SK2 framfronts schemadefinitioner , och databashantering överhuvudtaget

**[PandasAI](https://github.com/Sinaptik-AI/pandas-ai)**: Den laddar fortfarande allt i minnet . Plus

## Varför DuckDB

[DuckDB](https://duckdb.org/) är annorlunda. Den frågar CSV-filer *direkt* - ingen importerad steg , inget laddande i minnet

```csharp
using var connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT Region, SUM(Amount) FROM 'sales.csv' GROUP BY Region";
// Executes directly against the file - no import, no memory explosion
```

Killer Featuren: **den behandlar filer som tabeller**. Närma den till en CSV , Parquet M SK2 eller JSON-fiel och fråga direkt . Inga skapadestablar

| Faktor | CsvHelper | SQLite m| DuckDB M|
|--------|-----------|--------|--------|
| **minne** | Lader hela filen | | | Lader under import || | Stromer från disken |
| **Konfiguration** | Inget | Schema | | + | Import | МSK3 | Inget
| **500MB-Datei** | ~2 GB RAM | | | Mina minuter att importera
| **5GB-Datei** | OOMkraschen | Väldigt långsamt ♫ ♫ | ♫ Fungerar bra ♫
| **Parquet** | Nej | Nej

DuckDB är vad dataingenjörer använder i Python för exakt detta användandefall [.NET bindinger](https://github.com/Giorgi/DuckDB.NET) ge dig full ADO.NET support - det känns som vilken annan databas som helst

## Stanken

| Komponent ♫ | ♫ Varför den här ♫
|-----------|--------------|
| [DuckDB](https://duckdb.org/) | Fråga CSV direkt , ingen införingsteg
| [DuckDB.NET](https://github.com/Giorgi/DuckDB.NET) | Full ADO. Net supportM SK2 känns inhemskt |
| [Ollama](https://ollama.ai/) | Lokalt utgångssätt , inga API-kyler M SK2 ingen moln |
| [Bogus](https://github.com/bchavez/Bogus) | Realistiska tester på alla skalor |
| [`qwen2.5-coder:7b`](https://ollama.ai/library/qwen2.5-coder) | Den bästa SQL-tegenskapen på 7B-storlek |

> **Beteckning om säkerhet**: Vi executerar LLM-genererad SQLM SK3 Det är säkrare än godtyckligt kodt , men kräver fortfarande validering [säkerhetssektionen](#security-considerations) för säkerhetsåtgärder . För en bredare diskussion om att omvandla profiler till LLM-gränssnittet och en CLI som implementerar dessa mönster **[DataSummarizer: Vinniga lokaldataprofilering](/blog/datasummarizer-how-it-works)**.

## Projektuppsättning

Låt oss skapa ett exempelprojekt.

```bash
dotnet add package DuckDB.NET.Data.Full
dotnet add package OllamaSharp
dotnet add package Bogus
```

Ta fram en kodning, -, som fokuserar på modellen och som är bra på SQL.

```bash
ollama pull qwen2.5-coder:7b
```

## Architekturen

Här' är hur de delar passar ihop

```mermaid
flowchart TB
    subgraph Input
        Q[User Question]
        CSV[CSV File]
    end
    
    subgraph Processing
        Schema[Extract Schema]
        Sample[Get Sample Rows]
        Context[Build LLM Context]
        LLM[Generate SQL]
        Validate[Validate SQL]
        Execute[Execute Query]
    end
    
    subgraph Output
        Results[Query Results]
    end
    
    CSV --> Schema
    CSV --> Sample
    Schema --> Context
    Sample --> Context
    Q --> Context
    Context --> LLM
    LLM --> Validate
    Validate -->|Error| LLM
    Validate -->|OK| Execute
    CSV --> Execute
    Execute --> Results
    
    style LLM stroke:#333,stroke-width:4px
    style Execute stroke:#333,stroke-width:4px
```

Den viktigaste insikten vi ger LLM **schema och proverdata**, inte den faktiska datan . Detta gör att kontexten är liten och reaktioner snabbar

**Varför är detta viktigt?**: LLM genererar intentionen | ( | SQL | МSK2 | DuckDB löser det | . | Valideringsstegen fångar syntaktiska fel innan Executionen ♫ . | Retry-loopet hanterar slumpmässiga fel |. | Det är denna separation som gör systemet både säkert och korrekt ♫ **[DataSummarizer: Vinniga lokaldataprofilering](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## steg 1: Generera testerdata med Bogus

> **Har redan CSV data?** Spring till [steg 2: bygga schemakontexten](#step-2-build-the-schema-context).

Innan vi kan testa vår LLM, behöver vi data att analysera.

1. **Skala tester** - Generera ♫ 100 ♫ K ♫ , ♫ ♫ МSK3 ♫ mm ♫M, ♫ eller ♫ M10 ♫ rader för att kontrollera effekten på olika storlekar
2. **Privatliv** - Inga risker för att exponera riktiga kunder / affärsdata i demo eller screenshots
3. **Reproduktivitet** - Samma frö = samma data, gör att bugs är återskapbara
4. **Randfall** - Kontrollera fördelningen |  | e | . | g | мSK3 | kraft | МSK4 | återspeglar |, | specifika datumsträngar

### Vad är Bogus?

[Bogus](https://github.com/bchavez/Bogus) är en .NET port av den populära fakern.js biblioteketM SK2 Den genererar realistiskaMST3liknande falska data MST4 namnM ST5 adressernaM st6 e-posterMst7 datumM St8 siffror | MST9 med korrekt lokalisering | МST10 | Istället för att göra test CSV-Dater eller använda slumpmässiga skräpdata |MST12 | Bogus ger dig data som *ser ut* riktiga:

- `f.Name.FullName()` → "John SmithM SK2 ♫ ♫ ( ♫
- `f.Internet.Email()` →
- `f.Date.Between(start, end)` → Distribution av realistiska datum
- `f.Commerce.ProductName()` → "Handgjorda granitiskt ost" | | ( roligtM SK4 men igenkännande

Detta spelar roll, eftersom realistiska data hjälper dig att upptäcka problem - konstiga formateringar , oväntade aggregationer

### Definiera datamodellen

```csharp
internal class SaleRecord
{
    public string OrderId { get; set; } = "";
    public DateTime OrderDate { get; set; }
    public string CustomerId { get; set; } = "";
    public string CustomerName { get; set; } = "";
    public string Region { get; set; } = "";
    public string Category { get; set; } = "";
    public string ProductName { get; set; } = "";
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Discount { get; set; }
    public bool IsReturned { get; set; }
}
```

### Konfigurera Fakern

Bogus använder en flytande API för att definiera generregler:

```csharp
var categories = new[] { "Electronics", "Clothing", "Home & Garden", "Sports", "Books" };
var regions = new[] { "North", "South", "East", "West", "Central" };

var faker = new Faker<SaleRecord>()
    .RuleFor(s => s.OrderId, f => f.Random.Guid().ToString()[..8].ToUpper())
    .RuleFor(s => s.OrderDate, f => f.Date.Between(
        new DateTime(2022, 1, 1), 
        new DateTime(2024, 12, 31)))
    .RuleFor(s => s.CustomerId, f => $"CUST-{f.Random.Number(10000, 99999)}")
    .RuleFor(s => s.CustomerName, f => f.Name.FullName())
    .RuleFor(s => s.Region, f => f.PickRandom(regions))
    .RuleFor(s => s.Category, f => f.PickRandom(categories))
    .RuleFor(s => s.ProductName, (f, s) => GenerateProductName(f, s.Category))
    .RuleFor(s => s.Quantity, f => f.Random.Number(1, 20))
    .RuleFor(s => s.UnitPrice, f => f.Random.Decimal(9.99m, 299.99m))
    .RuleFor(s => s.Discount, f => f.Random.Bool(0.3f) ? f.Random.Decimal(0.05m, 0.25m) : 0m)
    .RuleFor(s => s.IsReturned, f => f.Random.Bool(0.05f));
```

Låt oss bryta ner vad som händer

- **`f` (Faker)** - Genereringsinstancen med tillgång till alla datamoduler |  | namn | , | datum |, | slumpmässigt | , etc | SMK5
- **`f.Random.Guid().ToString()[..8]`** - Generera en GUID men ta bara först 8 tecken för en läsbar ordningsID
- **`f.Date.Between()`** - Lukraket datum inom en realistisk intervall  inte år
- **`f.PickRandom(array)`** - Välj slumpmässigt från förutdefinierade alternativ bestämdar giltiga kategorier
- **`f.Random.Bool(0.3f)`** - 30% chans att riktiga order blir beskömda
- **`(f, s)` syntax** - Zugriff både på faker och på de delvis konstruerade rekorden - `ProductName` beroende av `Category`

Den `(f, s)` mönster är kraftfulla - det betyder "Elektronics" order får elektroniksprodukternas namnM SK3 inte slumpmässiga prylarMSC4 Den här konsekvencen gör datan som genereras mycket mer realistisk för att testa aggregationer som |

### Generera och skriva till CSV

```csharp
var records = faker.Generate(100_000); // Adjust for your testing needs

await using var writer = new StreamWriter(csvPath, false, Encoding.UTF8);
await writer.WriteLineAsync("OrderId,OrderDate,CustomerId,CustomerName,Region,Category,...");

foreach (var record in records)
{
    var total = record.Quantity * record.UnitPrice * (1 - record.Discount);
    await writer.WriteLineAsync($"{record.OrderId},{record.OrderDate:yyyy-MM-dd},...");
}
```

100K rader genererar ungefär 15MB av CSV | - | tillräckligt för att testa med |, | men man kan enkelt skala upp till miljoner |

> **Tipp**: Set `Randomizer.Seed = new Random(12345)` innan man genererar för att få återskapbara data

## steg 2: bygga schemakontexten

Innan LLM kan generera SQL, måste den förstå datastrukturen.

### Kontextmodellen

```csharp
public class DataContext
{
    public string CsvPath { get; set; } = "";
    public List<ColumnInfo> Columns { get; set; } = new();
    public List<Dictionary<string, string>> SampleRows { get; set; } = new();
    public long RowCount { get; set; }
}

public class ColumnInfo
{
    public string Name { get; set; } = "";
    public string Type { get; set; } = "";  // VARCHAR, DOUBLE, TIMESTAMP, etc.
}
```

Det fångar allt som LLM behöver, columns namn, ,, typer och några mönster rader för att förstå dataformatet.

### Att extrahera schemat

DuckDB kan beskriva vilken CSV som helst utan att ladda den alla

```csharp
private DataContext BuildContext(DuckDBConnection connection, string csvPath)
{
    var context = new DataContext { CsvPath = csvPath };

    // Get schema - DuckDB infers types from the CSV
    using var cmd = connection.CreateCommand();
    cmd.CommandText = $"DESCRIBE SELECT * FROM '{csvPath}'";
    using var reader = cmd.ExecuteReader();

    while (reader.Read())
    {
        context.Columns.Add(new ColumnInfo
        {
            Name = reader.GetString(0),  // Column name
            Type = reader.GetString(1)   // Inferred type
        });
    }

    return context;
}
```

Den `DESCRIBE` kommandon läser bara fileheader plus några rader för typsuppfattning - det 's omedelbart även på stora filer.

### Att få proverdata

Sammlingraden hjälper LLM att förstå dataformat (dataM SK1 IDs , etc.):

```csharp
using var cmd = connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM '{csvPath}' LIMIT 3";
using var reader = cmd.ExecuteReader();

while (reader.Read())
{
    var row = new Dictionary<string, string>();
    for (int i = 0; i < reader.FieldCount; i++)
    {
        var value = reader.IsDBNull(i) ? "NULL" : reader.GetValue(i)?.ToString() ?? "";
        row[reader.GetName(i)] = value;
    }
    context.SampleRows.Add(row);
}
```

Tre rader är vanligtvis tillräckligt. - den visar LLM vilka format man ska förvänta sig utan att slösa bort token.

## steg 3: Generera SQL med LLM

**Det här är den hårda delen** Det snabbaste ingenjörskonst här är inte

### Att bygga upp springen

```csharp
private string BuildPrompt(DataContext context, string question, string? previousError)
{
    var sb = new StringBuilder();

    sb.AppendLine("You are a SQL expert. Generate a DuckDB SQL query to answer the user's question.");
    sb.AppendLine();
    sb.AppendLine("IMPORTANT RULES:");
    sb.AppendLine("1. The table is accessed directly from the CSV file path");
    sb.AppendLine("2. Use single quotes around the file path in FROM clause");
    sb.AppendLine("3. DuckDB syntax - use LIMIT not TOP, use || for string concat");
    sb.AppendLine("4. Return ONLY the SQL query, no explanation, no markdown");
    sb.AppendLine();
    
    sb.AppendLine($"CSV File: '{context.CsvPath}'");
    sb.AppendLine($"Row Count: {context.RowCount:N0}");
    sb.AppendLine();
    
    // Schema
    sb.AppendLine("Schema:");
    foreach (var col in context.Columns)
    {
        sb.AppendLine($"  - {col.Name}: {col.Type}");
    }
```

Säcken om regler är avgörande - den berättar för LLM exakt hur man formaterar queryn för DuckDB . Att vara tydlig med syntaxen | | ( | LIMIT vs TOP

### Att lägga till proverdata

```csharp
    if (context.SampleRows.Count > 0)
    {
        sb.AppendLine();
        sb.AppendLine("Sample data (first 3 rows):");
        foreach (var row in context.SampleRows)
        {
            var values = row.Select(kv => $"{kv.Key}='{kv.Value}'");
            sb.AppendLine($"  {{{string.Join(", ", values)}}}");
        }
    }
```

### Fout återhämta

Om ett tidigare försök misslyckades

```csharp
    if (previousError != null)
    {
        sb.AppendLine();
        sb.AppendLine("YOUR PREVIOUS QUERY HAD AN ERROR:");
        sb.AppendLine(previousError);
        sb.AppendLine("Please fix the query based on this error.");
    }

    sb.AppendLine();
    sb.AppendLine($"Question: {question}");
    sb.AppendLine();
    sb.AppendLine("SQL Query (no markdown, no explanation):");

    return sb.ToString();
}
```

Den här upprepningsmekanismen är viktig. Lokala LLM gör ibland syntaktiska fel och genom att ge dem felet löser den ofta vid det andra försöket

### Att kalla LLM

```csharp
var request = new GenerateRequest { Model = _model, Prompt = prompt };
var response = await _ollama.GenerateAsync(request).StreamToEndAsync();
var sql = CleanSqlResponse(response?.Response ?? "");
```

Den `StreamToEndAsync()` väntar på den kompletta svaret. För en bättre UX, skulle man kunna skicka ut token när de kommer fram

### Att rena reaktionen

LLM wrapar ofta SQL i markerdown-kodblock trots att de fått höra att inte göra det.

```csharp
private string CleanSqlResponse(string response)
{
    var sql = response.Trim();

    // Remove markdown code blocks if present
    if (sql.StartsWith("```"))
    {
        var lines = sql.Split('\n').ToList();
        lines.RemoveAt(0);  // Remove opening ```sql
        if (lines.Count > 0 && lines[^1].Trim().StartsWith("```"))
        {
            lines.RemoveAt(lines.Count - 1);  // Remove closing ```
        }
        sql = string.Join('\n', lines);
    }

    return sql.Trim('`', ' ', '\n', '\r');
}
```

## steg 4: Validatera före att utföra

DuckDB's `EXPLAIN` låter oss kontrollera SQL syntax utan att köra query:

```csharp
private string? ValidateSql(DuckDBConnection connection, string sql)
{
    try
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = $"EXPLAIN {sql}";
        cmd.ExecuteNonQuery();
        return null; // Valid
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}
```

Om bekräftelset misslyckas, skickar vi felet tillbaka till LLM och testar igen (till en gräns

## steg 5: utföra och formatera resultat

Slutligen, köra queryn och formatera utgången

```csharp
private QueryResult ExecuteQuery(DuckDBConnection connection, string sql)
{
    var result = new QueryResult { Sql = sql };

    try
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        using var reader = cmd.ExecuteReader();

        // Capture column names
        for (int i = 0; i < reader.FieldCount; i++)
        {
            result.Columns.Add(reader.GetName(i));
        }

        // Capture rows
        while (reader.Read())
        {
            var row = new List<object?>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                row.Add(reader.IsDBNull(i) ? null : reader.GetValue(i));
            }
            result.Rows.Add(row);
        }

        result.Success = true;
    }
    catch (Exception ex)
    {
        result.Success = false;
        result.Error = ex.Message;
    }

    return result;
}
```

Den `QueryResult` klass ( fullkomligt visat i exemplet projekt `ToString()` metod som formaterar resultat som en läsbar tabell.

## Att lägga till konversationskontext

När det gäller interaktiv analys vill användare ofta ställa frågor som följer:

```
"What's the total revenue?"
→ "Break that down by region"
→ "Show the top 5 regions"
```

De andra och tredje frågorna är logiska bara i sammanhanget från den första

### Att följa konversationers historia

```csharp
public class ConversationTurn
{
    public string Question { get; set; } = "";
    public string Sql { get; set; } = "";
    public bool Success { get; set; }
    public int RowCount { get; set; }
    public string Summary { get; set; } = "";  // "Single value: 1234567.89"
}
```

### Inklusive historia i prompt

```csharp
if (_history.Count > 0)
{
    sb.AppendLine();
    sb.AppendLine("CONVERSATION HISTORY (for context):");
    
    foreach (var turn in _history.TakeLast(5))  // Last 5 turns
    {
        sb.AppendLine($"Q: {turn.Question}");
        sb.AppendLine($"SQL: {turn.Sql}");
        if (turn.Success)
        {
            sb.AppendLine($"Result: {turn.Summary}");
        }
        sb.AppendLine();
    }
}
```

Historien ger LLM: s kontext för att förstå referenser som "som ", | | " dessa resultat

## Vilken modell att använda?

För SQL-generationen, kodering - fokuserade modeller fungerar bäst. Här är de tillgängliga alternativen via [Ollamas modellbibliotek](https://ollama.ai/library):

| Modell | | | Storlek || | Rymden | МSK3 | Jakt |
|-------|------|-------|---------|------|
| `qwen2.5-coder:7b` | 4.7GB | snabb | Utmärkt | [Ollama](https://ollama.ai/library/qwen2.5-coder) |
| `deepseek-coder-v2:16b` | 9 GB | Medel | bäst | [Ollama](https://ollama.ai/library/deepseek-coder-v2) |
| `codellama:7b` | 4GB | snabb | Bra | [Ollama](https://ollama.ai/library/codellama) |
| `llama3.2:3b` | [Ollama](https://ollama.ai/library/llama3.2) |

För de flesta användande fall, **`qwen2.5-coder:7b`** träffar den bästa punkten. - korrekt SQL.

## Förmågor

### Real-World Benchmarks

Testande på en 100K rad CSV-Datei (14MB) på en standard- dev-maskin | | (Ryzen | МSK4 | NVMe SSD |

| frågeställningstyp | tid |
|------------|------|
| Enkel siffra ♫ ♫ | ♫
| GROUP BY med SUM | 58-68ms |
| Kompleks aggregation med FILTER | 63ms |
| Multi -table GROUP BY with HAVING

Sub-100ms för analytiska queryer på | 100 | K rader |- | utan någon importerad steg | МSK3 | Query-komplexiteten är viktigare än radsräkningen ♫ ; | DuckDB | ' | vår pilarmotor hanterar agregationer effektivt oavsett filestorlek ♫

### Konvertera stora filer till Parquet

För filer över 1GB, [Parquet-format](https://parquet.apache.org/) är 10-100x snabbareM SK1

```csharp
using var cmd = connection.CreateCommand();
cmd.CommandText = $"COPY (SELECT * FROM '{csvPath}') TO '{parquetPath}' (FORMAT PARQUET)";
cmd.ExecuteNonQuery();
```

Den komprimerade Parquet-fiilen är också mycket mindre.

### säkerhetsfrågor

När man sätter igång LLM

```csharp
private bool IsSafeQuery(string sql)
{
    var dangerous = new[] { "DROP", "DELETE", "TRUNCATE", "UPDATE", "INSERT", "ALTER", "CREATE" };
    var upperSql = sql.ToUpperInvariant();
    return !dangerous.Any(d => upperSql.Contains(d));
}
```

DuckDB's i memory-modus ger också naturlig isolering - den kanM SK3 inte påverka dina produktionsdatabaser

## Ett komplett exempel

Här är "'", det är hur allt går ihop.

```csharp
// Generate test data
await GenerateSalesCsvAsync("sales.csv", 100_000);

// Simple query
using var service = new CsvQueryService("qwen2.5-coder:7b", verbose: true);
var result = await service.QueryAsync("sales.csv", "What are total sales by region?");
Console.WriteLine(result);

// Conversational analysis
using var analyser = new ConversationalCsvAnalyser("sales.csv", "qwen2.5-coder:7b");

Console.WriteLine(await analyser.AskAsync("What's the total revenue?"));
Console.WriteLine(await analyser.AskAsync("Break that down by category"));
Console.WriteLine(await analyser.AskAsync("Which category has the most returns?"));
```

## sammanfattning

Den mentala modellen för att hålla **LLMs skäl; databasernas beräkningar**

Sorg inte data till LLM. Sorg det schemat. Schick det ,. Sorg att det genererar SQL. ,. Släng det SQL mot en äkta sökmotor.

Implementeringen:

1. **DuckDB** queries CSV direkt - inga importer , strömmar från disket
2. **Schema + prover** ge LLM tillräckligt med kontext utan att avslöja data
3. **Strict prompt-regler** kraft deterministisk SQL, inte kreativ prosa
4. **Validering via EXPLAIN** fångar fel före uitvoeringen
5. **Försök igen med fel återkoppling** hanterar den slumpmässiga syntaxen

Resultatet: sub-100 ms analytiska queryr på miljontals filer - rader files M SK3 helt offline , med data som aldrig lämnar din maskin

Det kompletta exemplaret är tillgängligt [Mostlylucid.CsvLlm](https://github.com/scottgal/mostlylucidweb) - inkluderar `CsvQueryService`, `ConversationalCsvAnalyser`, och Bogus

## resurser

### DuckDB

- [DuckDB Dokumentation](https://duckdb.org/docs/) - Hela referensen
- [DuckDB CSV-import](https://duckdb.org/docs/data/csv/overview.html) Specifiska kännetecken
- [DuckDB SQL-referens](https://duckdb.org/docs/sql/introduction) - SQL-syntaxer skiljer sig från andra databaser
- [DuckDB.NET GitHub](https://github.com/Giorgi/DuckDB.NET) - CM SK1 bindningar
- [DuckDB.NET NuGet](https://www.nuget.org/packages/DuckDB.NET.Data.Full) - Hela paketet med binärsystem

### Ollama & LLMs

- [Ollama](https://ollama.ai/) - Lokal LLM körtid
- [Ollama Modellbiblioteket](https://ollama.ai/library) - tillgängliga modeller
- [Ollamascharp](https://github.com/awaescher/OllamaSharp) - C# klientbibliotek
- [OllamaSharp NuGet](https://www.nuget.org/packages/OllamaSharp/)

### Testa datagenerering

- [Bogus GitHub](https://github.com/bchavez/Bogus) - Fake datagenerator
- [Bogus API-referens](https://github.com/bchavez/Bogus#bogus-api-support) - tillgängliga datatyper

### Alternativer Beskrivna

- [CsvHelper](https://joshclose.github.io/CsvHelper/) - CSV-testningsbibliotek
- [Microsoft.Data](https://www.nuget.org/packages/Microsoft.Data.Analysis) - Dataframem för .NET
- [PandasAI](https://github.com/Sinaptik-AI/pandas-ai) - Python LLM + integration av pander

### Related Articles

- [DataSummarizer: Vinniga lokaldataprofilering](/blog/datasummarizer-how-it-works) - Kompanionsartikel om CLI-baserad dataprofilering