# Πώς να αναλύσετε μεγάλα αρχεία CSV με τοπικά LLMs σε C#

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

**Σειρά: Τοπικές LLMs για δεδομένα - Μέρος 1 από 2**

Εδώ είναι το λάθος που κάνουν όλοι: προσπαθούν να ταΐσουν το CSV τους σε LLM. **Οι LLMs θα πρέπει να δημιουργούν ερωτήματα, όχι να καταναλώνουν δεδομένα.**

Έχετε ένα αρχείο CSV 500MB και θέλετε να ρωτήσετε "Ποια είναι η μέση τιμή παραγγελίας ανά περιοχή;" Εργαλεία όπως [Συνπιλότος στο Excel](https://support.microsoft.com/en-gb/copilot-excel) μπορεί να το κάνει αυτό, αλλά τι γίνεται αν τα δεδομένα σας είναι πολύ ευαίσθητα για τις υπηρεσίες σύννεφων; Τι γίνεται αν χρειάζεται να τα χτίσετε μόνοι σας;

Αυτό το άρθρο σας δείχνει πώς - τοπικά, ιδιωτικά, σε C#.

> Χρησιμοποιήστε το DuckDB για να ρωτήσετε απευθείας τα αρχεία CSV. Χρησιμοποιήστε ένα τοπικό LLM για να δημιουργήσετε το SQL. Το LLM δεν βλέπει ποτέ τα δεδομένα σας - μόνο το σχήμα. Αποτέλεσμα: υπο-100ms ερωτήματα σε αρχεία εκατομμυρίων γραμμών, εντελώς εκτός σύνδεσης.

Για μια συμπληρωματική, πιο CLI-εστιασμένη θεραπεία που επεκτείνεται στη χρήση στατιστικών προφίλ ως διεπαφή LLM (και δείχνει ένα πλήρες εργαλείο εφαρμογής αυτών των ιδεών - προφίλ, ασφαλής λειτουργία SQL, συνθετική κλωνοποίηση και ανίχνευση παρασυρόμενων) δείτε το συνοδευτικό άρθρο: **[DataSummarizer: Fast Local Data Profiling](/blog/datasummarizer-how-it-works)** - ειδικά το τμήμα "Η βασική αναβάθμιση: Στατιστικά ως Διεπαφή" . Αυτά τα δύο άρθρα σχηματίζουν μια σύντομη σειρά για πρακτικά τοπικά πρότυπα LLM + ερωτήσεις .

[TOC]

## Η Ενόραση του Πυρήνα

Χρησιμοποιώντας ένα LLM ως κατάστημα δεδομένων είναι η λάθος αφαίρεση. LLMs είναι ουσιαστικά ανίκανη να σαρώσει εκατομμύρια σειρές για να υπολογίσει ένα μέσο όρο - αυτό δεν είναι ό, τι είναι για. Ακόμη και ένα παράθυρο των 200K συμβολικό πλαίσιο ταιριάζει ίσως 50.000 σειρές. 500MB CSV σας έχει εκατομμύρια.

Το σωστό μοτίβο: **Για λόγους LLM, η βάση δεδομένων υπολογίζει.**

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

Παρατηρήστε τι συμβαίνει: η LLM δημιουργεί ένα ερώτημα SQL με βάση την ερώτησή σας και το σχήμα. DuckDB εκτελεί ενάντια στα πραγματικά δεδομένα. Η LLM ποτέ δεν αγγίζει τα δεδομένα σας - βλέπει μόνο ονόματα στήλης και τύπους.

Για περισσότερες πληροφορίες σχετικά με την επεξεργασία προφίλ ως διεπαφή LLM (και ένα σκυρόδεμα CLI που εφαρμόζει προφίλ-πρώτη αφήγηση, ασφαλή Q&A που υποστηρίζεται SQL, συνόδους που υποστηρίζονται από μητρώο και συνθετική κλωνοποίηση), βλέπε **[DataSummarizer: Fast Local Data Profiling](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## Γιατί να μην το φορτώσουμε στη μνήμη;

Οι προφανείς προσεγγίσεις όλες έχουν το ίδιο μοιραίο ελάττωμα:

**[CsvHelperCity name (optional, probably does not need a translation)](https://joshclose.github.io/CsvHelper/) / DataFrames**: Φορτώστε ολόκληρο το αρχείο σε RAM. Ένα CSV 500MB γίνεται 2-4GB αντικειμένων. Ένα αρχείο 5GB; OOM συντριβή.

**SQLite / PostgreSQL**: Απαιτεί ένα αργό βήμα εισαγωγής (λεπτά για μεγάλα αρχεία), μπροστινούς ορισμούς σχημάτων, και διαχείριση βάσεων δεδομένων από πάνω.

**[ΠάντασαϊCity name (optional, probably does not need a translation)](https://github.com/Sinaptik-AI/pandas-ai)**Επιπλέον, η εκτέλεση αυθαίρετου κώδικα LLM είναι ένας εφιάλτης ασφαλείας - το SQL είναι δηλωτικό και γυαλιστερό· ο Python δεν είναι.

## Γιατί Πάπια DB

[Πάπιες](https://duckdb.org/) είναι διαφορετική. Αμφισβητεί αρχεία CSV *άμεσα* - χωρίς βήμα εισαγωγής, χωρίς φόρτωση στη μνήμη:

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

Το χαρακτηριστικό του δολοφόνου: **αντιμετωπίζει τα αρχεία ως πίνακες**Δείξτε το σε ένα αρχείο CSV, Parquet, ή JSON και ρωτήστε αμέσως.

Εργοστάσιο CsvHelper SQLite DuckDB
|--------|-----------|--------|--------|
| **Μνήμη** Φόρτωση ολόκληρου του αρχείου Φόρτωση κατά την εισαγωγή
| **Ρύθμιση** Κανένας Στέμα + εισαγωγή Κανένας
| **Αρχείο 500MB** ~2GB RAM ~ λεπτά για την εισαγωγή ~ Άμεση ~
| **Αρχείο 5GB** ~ OOM συντριβή ~ Πολύ αργή ~ Λειτουργεί πρόστιμο ~
| **ΠάρκετCity name (optional, probably does not need a translation)** Όχι, όχι, όχι, όχι Ναι

DuckDB είναι αυτό που χρησιμοποιούν οι μηχανικοί δεδομένων στο Python για ακριβώς αυτή τη χρήση περίπτωση. [Συνδέσεις NET](https://github.com/Giorgi/DuckDB.NET) σας παρέχει πλήρη υποστήριξη ADO.NET - αισθάνεται σαν οποιαδήποτε άλλη βάση δεδομένων, εκτός από είστε ερωτήσεις αρχεία.

## Η Στοίβα

Γιατί αυτό εδώ;
|-----------|--------------|
| [Πάπιες](https://duckdb.org/) Ερωτήματα CSV άμεσα, κανένα βήμα εισαγωγής □
| [DuckDB.NET](https://github.com/Giorgi/DuckDB.NET) Πλήρες ADO.NET υποστήριξη, αισθάνεται ιθαγενής
| [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/) Τοπικό συμπέρασμα, χωρίς κλειδιά API, χωρίς σύννεφο
| [ΜπόγκουςCity name (optional, probably does not need a translation)](https://github.com/bchavez/Bogus) Ρεαλιστικά δεδομένα δοκιμών σε οποιαδήποτε κλίμακα
| [`qwen2.5-coder:7b`](https://ollama.ai/library/qwen2.5-coder) Βέλτιστη ακρίβεια SQL στο μέγεθος 7B .

> **Σημείωση σχετικά με την ασφάλεια**Αυτό είναι ασφαλέστερο από τον αυθαίρετο κώδικα, αλλά εξακολουθεί να απαιτεί επικύρωση. [Τμήμα ασφαλείας](#security-considerations) Για μια ευρύτερη συζήτηση σχετικά με τη μετατροπή προφίλ στη διεπαφή LLM και ένα CLI που εφαρμόζει αυτά τα πρότυπα, δείτε το συνοδευτικό κομμάτι **[DataSummarizer: Fast Local Data Profiling](/blog/datasummarizer-how-it-works)**.

## Ρύθμιση έργου

Ας δημιουργήσουμε ένα δείγμα έργου. Εγκαταστήστε τα πακέτα NuGet:

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

Τραβήξτε ένα μοντέλο κωδικοποίησης που είναι καλό στο SQL:

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

## Η Αρχιτεκτονική

Εδώ είναι πώς τα κομμάτια ταιριάζουν μεταξύ τους:

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

Η βασική διορατικότητα: δίνουμε το LLM **στοιχεία σχεδίασης και δειγματοληψίας**, όχι τα πραγματικά δεδομένα . Αυτό κρατά τα συμφραζόμενα μικρά και τις απαντήσεις γρήγορα .

**Γιατί αυτό μετράει;**: Το LLM δημιουργεί πρόθεση (SQL). Το DuckDB το εκτελεί. Το βήμα επικύρωσης πιάνει λάθη σύνταξης πριν από την εκτέλεση. Ο βρόχος επαναπροσπάθειας χειρίζεται το περιστασιακό λάθος. Αυτός ο διαχωρισμός κάνει το σύστημα τόσο ασφαλές όσο και ακριβές. Για μια πιο λεπτομερή, CLI-κεντρική αναφορά (συμπεριλαμβανομένων προφίλ-πρώτη αφήγηση και ασφαλή όρια εκτέλεσης SQL) βλέπε **[DataSummarizer: Fast Local Data Profiling](/blog/datasummarizer-how-it-works#the-key-upgrade-statistics-as-the-interface)**.

## Βήμα 1: Δημιουργία δεδομένων δοκιμής με Bogus

> **Έχεις ήδη δεδομένα CSV;** Μετάβαση στην [Βήμα 2: Κατασκευή του πλαισίου Schema](#step-2-build-the-schema-context).

Πριν μπορέσουμε να ελέγξουμε τον αναλυτή CSV LLM, χρειαζόμαστε δεδομένα για να αναλύσουμε.

1. **Δοκιμή κλίμακας** - Δημιουργία 100K, 1M, ή 10M σειρές για την επαλήθευση της απόδοσης σε διαφορετικά μεγέθη
2. **Απόρρητο** - Δεν υπάρχει κίνδυνος έκθεσης πραγματικών δεδομένων πελάτη / επιχείρησης σε demos ή στιγμιότυπα οθόνης
3. **Αναδρομιμότητα** - Ίδιος σπόρος = ίδια δεδομένα, καθιστώντας τα έντομα αναπαραγώγιμα
4. **Θήκες εκτροχιασμού** - Έλεγχος της κατανομής (π.χ., δύναμη 5% αποδόσεις, ειδικές τιμές ημερομηνίας)

### Τι είναι ο Μπόγκους;

[ΜπόγκουςCity name (optional, probably does not need a translation)](https://github.com/bchavez/Bogus) είναι ένα .NET θύρα της δημοφιλούς βιβλιοθήκης faker.js. Δημιουργεί ρεαλιστικό εμφάνιση πλαστά δεδομένα - ονόματα, διευθύνσεις, emails, ημερομηνίες, αριθμούς - με σωστή υποστήριξη τοποθεσίας. Αντί της χειροτεχνίας αρχεία CSV δοκιμής ή χρησιμοποιώντας τυχαία δεδομένα σκουπιδιών, Bogus σας δίνει δεδομένα που *ματιά* Πραγματικό:

- `f.Name.FullName()` → "John Smith" (όχι "asdf1234")
- `f.Internet.Email()` → "john.smith@gmail.com" (κατάλληλα μορφοποιημένο)
- `f.Date.Between(start, end)` → Ρεαλιστική κατανομή ημερομηνίας
- `f.Commerce.ProductName()` → "Handcrafted Granite Cheese" (διασκέδαση, αλλά αναγνωρίσιμο)

Αυτό έχει σημασία επειδή τα ρεαλιστικά δεδομένα σας βοηθούν να εντοπίσετε θέματα - περίεργη μορφοποίηση, απροσδόκητες συγκεντρώσεις, ακραίες περιπτώσεις στο χειρισμό ημερομηνίας - ότι τυχαίες συμβολοσειρές θα κρύβονταν.

### Ορισμός του μοντέλου δεδομένων

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

### Ρύθμιση του Faker

Ο Μπόγκους χρησιμοποιεί ένα άπταιστο API για να καθορίσει τους κανόνες παραγωγής:

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

Ας σπάσουμε αυτό που συμβαίνει.

- **`f` (Faker)** - Το παράδειγμα της γεννήτριας με πρόσβαση σε όλες τις ενότητες δεδομένων (Όνομα, Ημερομηνία, Τυχαία κ.λπ.)
- **`f.Random.Guid().ToString()[..8]`** - Δημιουργήστε ένα GUID αλλά πάρτε μόνο τα πρώτα 8 chars για μια ευανάγνωστη ταυτότητα παραγγελίας
- **`f.Date.Between()`** - Τυχαία ημερομηνία εντός ρεαλιστικού εύρους (όχι έτους 9999)
- **`f.PickRandom(array)`** - Επιλέξτε τυχαία από προκαθορισμένες επιλογές (διασφαλίζει έγκυρες κατηγορίες)
- **`f.Random.Bool(0.3f)`** - 30% πιθανότητα της αλήθειας (30% των παραγγελιών πάρει μια έκπτωση)
- **`(f, s)` σύνταξη** - Πρόσβαση και στα δύο ψεύτικα και το μερικώς ενσωματωμένο ρεκόρ. `ProductName` Εξαρτάται από `Category`

Η `(f, s)` μοτίβο είναι ισχυρό - αυτό σημαίνει "Ηλεκτρονικά" παραγγελίες πάρει ηλεκτρονικά ονόματα προϊόντων, όχι τυχαία στοιχεία. Αυτή η συνοχή καθιστά τα δεδομένα που παράγονται πολύ πιο ρεαλιστικά για δοκιμές συγκεντρώσεις όπως "ανανέωση ανά κατηγορία."

### Δημιουργία και εγγραφή στο 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 σειρές παράγει περίπου 15MB CSV - αρκετά για να δοκιμάσετε με, αλλά μπορείτε εύκολα να κλιμακωθεί σε εκατομμύρια. Η γενιά είναι γρήγορη (~2 δευτερόλεπτα για 100K σειρές) επειδή Bogus βελτιστοποιείται για την παραγωγή χύδην.

> **Συμβουλή**: Ορισμός `Randomizer.Seed = new Random(12345)` ο ίδιος σπόρος = ίδια "τυχαία" αρχεία κάθε φορά, η οποία είναι ανεκτίμητη για αποσφαλμάτωση.

## Βήμα 2: Κατασκευή του πλαισίου Schema

Πριν η LLM μπορεί να δημιουργήσει SQL, πρέπει να κατανοήσει τη δομή των δεδομένων.

### Το Μοντέλο Πλαίσιο

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

Αυτό συλλαμβάνει όλα όσα χρειάζεται η LLM: ονόματα στήλης, τύποι, και μερικές σειρές δειγμάτων για να κατανοήσουν τη μορφή των δεδομένων.

### Εξαγωγή του Schema

Το DuckDB μπορεί να περιγράψει οποιοδήποτε CSV χωρίς να το φορτώσει όλα:

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

Η `DESCRIBE` εντολή διαβάζει μόνο την κεφαλίδα αρχείων συν μερικές σειρές για το συμπέρασμα τύπου - είναι άμεση ακόμη και σε τεράστια αρχεία.

### Λήψη δειγμάτων δεδομένων

Οι σειρές δειγμάτων βοηθούν την LLM να κατανοήσει μορφές δεδομένων (ημερομηνία, ταυτότητες κ.λπ.):

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

Τρεις σειρές είναι συνήθως αρκετές - δείχνει το LLM τι μορφές να περιμένετε χωρίς να σπαταλάτε μάρκες.

## Βήμα 3: Δημιουργία SQL με το LLM

**Αυτό είναι το δύσκολο κομμάτι.** Η άμεση μηχανική εδώ δεν είναι διαπραγματεύσιμη - χωρίς αυστηρούς κανόνες, οι τοπικές LLMs θα παράγουν δημιουργικά αλλά σπασμένα SQL. Ο στόχος είναι ο προσδιορισμός, όχι η δημιουργικότητα.

### Χτίζοντας το Prompt

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

Το τμήμα κανόνων είναι ζωτικής σημασίας - λέει στο LLM ακριβώς πώς να διαμορφώσει το ερώτημα για DuckDB. Όντας σαφής σχετικά με τη σύνταξη (LIMIT vs TOP, concatenation συμβολοσειρά) αποτρέπει κοινά λάθη.

### Προσθήκη δειγμάτων δεδομένων

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

### Ανάκτηση λάθους

Εάν μια προηγούμενη προσπάθεια απέτυχε, συμπεριλάβετε το σφάλμα:

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

Αυτός ο μηχανισμός επαναπροσπάθειας είναι σημαντικός - οι τοπικές LLMs μερικές φορές κάνουν λάθη σύνταξης, και δίνοντας τους το σφάλμα συνήθως το διορθώνει στη δεύτερη προσπάθεια.

### Καλώ το LLM

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

Η `StreamToEndAsync()` Για ένα καλύτερο UX, θα μπορούσατε να ρέει μάρκες καθώς φτάνουν.

### Καθαρισμός της Ανταπόκρισης

LLMs συχνά τυλίγει SQL σε markdown code blocks παρά το γεγονός ότι τους λένε να μην:

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

## Βήμα 4: Επιβεβαίωση πριν από την εκτέλεση

Παπιοπεριπέτειες `EXPLAIN` Μας επιτρέπει να ελέγξουμε τη σύνταξη SQL χωρίς να κάνουμε την ερώτηση:

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

Εάν η επικύρωση αποτύχει, τροφοδοτούμε το σφάλμα πίσω στο LLM και ξαναπροσπαθούμε (μέχρι ένα όριο).

## Βήμα 5: Εκτέλεση αποτελεσμάτων και διαμόρφωση

Τέλος, να εκτελέσετε το ερώτημα και να διαμορφώσετε την έξοδο:

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

Η `QueryResult` κατηγορία (πλήρης παρουσίαση του δείγματος) περιλαμβάνει a `ToString()` μέθοδος που σχηματίζει τα αποτελέσματα ως αναγνώσιμος πίνακας.

## Προσθήκη πλαισίου συζήτησης

Για διαδραστική ανάλυση, οι χρήστες συχνά θέλουν να κάνουν ερωτήσεις παρακολούθησης:

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

Το δεύτερο και το τρίτο ερώτημα έχουν νόημα μόνο με το πλαίσιο από το πρώτο.

### Παρακολούθηση της Ιστορίας της Συνομιλίας

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

### Συμπεριλαμβανομένης της Ιστορίας σε Προβολές

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

Η ιστορία δίνει το πλαίσιο LLM για να κατανοήσουν αναφορές όπως "αυτό," "αυτά τα αποτελέσματα" ή "καταστρέψτε το περαιτέρω."

## Ποιο Μοντέλο να Χρησιμοποιήσετε;

Για την παραγωγή SQL, η κωδικοποίηση-εστιασμένα μοντέλα λειτουργούν καλύτερα. Εδώ είναι οι διαθέσιμες επιλογές μέσω [Μοντέλο βιβλιοθήκης Ollama](https://ollama.ai/library):

Μοντέλο Μέγεθος Ταχύτητα Ποιότητας Σύνδεσμος
|-------|------|-------|---------|------|
| `qwen2.5-coder:7b` Γρήγορη... Εξαιρετική... [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/library/qwen2.5-coder) |
| `deepseek-coder-v2:16b` Μεσαίο 9GB μεσαίο Best [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/library/deepseek-coder-v2) |
| `codellama:7b` Γρήγορα Ωραία [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/library/codellama) |
| `llama3.2:3b` Πολύ γρήγορος. Αποδεκτός [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/library/llama3.2) |

Για τις περισσότερες περιπτώσεις χρήσης, **`qwen2.5-coder:7b`** χτυπά το γλυκό σημείο - ακριβή SQL, καλή ταχύτητα, τρέχει σε μέτρια υλικό (8GB+ RAM).

## Επιδόσεις

### Real-World Benchmarks

Δοκιμή σε ένα αρχείο CSV σειράς 100K (14MB) σε ένα πρότυπο μηχάνημα dev (Ryzen 5, NVMe SSD, 32GB RAM):

Ώρα ερώτησης ώρα
|------------|------|
Απλή COUNT 65ms _BAR_
ΟΜΙΛΟΣ ΜΕ SUM 58-68ms
Συγκρότημα Συγκρότημα με FILTER 63ms
Multi-table GROUP BY with HEaving 71ms

Υπο-100ms για αναλυτικές ερωτήσεις σε σειρές 100K - χωρίς κανένα βήμα εισαγωγής.

### Μετάτρεψε Μεγάλα Αρχεία σε Parquet

Για αρχεία άνω του 1GB, [Μορφή παρκέ](https://parquet.apache.org/) είναι 100- 100x γρηγορότερα:

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

Το συμπιεσμένο αρχείο Parquet είναι επίσης πολύ μικρότερο.

### Παρατηρήσεις σχετικά με την ασφάλεια

Κατά την εκτέλεση του SQL που παράγεται από 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 παρέχει επίσης φυσική απομόνωση - δεν μπορεί να επηρεάσει τις βάσεις δεδομένων παραγωγής σας.

## Πλήρες παράδειγμα

Εδώ είναι πώς όλα έρχονται μαζί:

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

## Περίληψη

Το πνευματικό μοντέλο που θα κρατήσει: **LLMs λόγος; βάσεις δεδομένων compute.**

Μην τροφοδοτήσετε τα δεδομένα στο LLM. Ταΐστε το σχήμα, αφήστε το να δημιουργήσει SQL, εκτελέσει ότι SQL ενάντια σε μια σωστή μηχανή ερώτημα. Αυτός είναι ο λόγος για τον οποίο η προσέγγιση λειτουργεί σε κλίμακα.

Η εφαρμογή:

1. **Πάπιες** Ερωτήσεις CSV άμεσα - καμία εισαγωγή, ροές από το δίσκο
2. **Schema + δείγματα** Δώστε στο LLM αρκετό πλαίσιο χωρίς να εκθέσετε τα δεδομένα
3. **Αυστηροί γρήγοροι κανόνες** power deterministic SQL, not creative prose
4. **Επιβεβαίωση μέσω της EXPLAIN** Σφάλματα αλιευμάτων πριν από την εκτέλεση
5. **Ξαναπροσπαθήστε με ανατροφοδότηση λάθους** χειρίζεται το περιστασιακό δελτίο σύνταξης

Το αποτέλεσμα: υπο-100ms αναλυτικές ερωτήσεις σε αρχεία εκατομμυρίων γραμμών, εντελώς εκτός σύνδεσης, με δεδομένα που δεν αφήνουν ποτέ τη μηχανή σας.

Το πλήρες δείγμα του έργου είναι διαθέσιμο στις [Κυρίως διαυγής.CsvLlm](https://github.com/scottgal/mostlylucidweb) - περιλαμβάνει `CsvQueryService`, `ConversationalCsvAnalyser`, και Bogus-based παραγωγή δεδομένων.

## Πόροι

### Πάπιες

- [Documentation DuckDB](https://duckdb.org/docs/) - Πλήρης αναφορά
- [Εισαγωγή DuckDB CSV](https://duckdb.org/docs/data/csv/overview.html) - Ειδικά χαρακτηριστικά CSV
- [Αναφορά DuckDB SQL](https://duckdb.org/docs/sql/introduction) - διαφορές σύνταξης SQL από άλλες βάσεις δεδομένων
- [DuckDB.NET GitHub](https://github.com/Giorgi/DuckDB.NET) - Σύνδεση C#
- [DuckDB.NET NuGet](https://www.nuget.org/packages/DuckDB.NET.Data.Full) - Πλήρης συσκευασία με ιθαγενή δυαδικά

### Ollama & LLMs

- [ΟλάμαCity name (optional, probably does not need a translation)](https://ollama.ai/) - Τοπικός χρόνος λειτουργίας LLM
- [Μοντέλο βιβλιοθήκης Ollama](https://ollama.ai/library) - Διαθέσιμα μοντέλα
- [OllamaSharp](https://github.com/awaescher/OllamaSharp) - Βιβλιοθήκη πελατών C#
- [OllamaSharp NuGet](https://www.nuget.org/packages/OllamaSharp/)

### Παραγωγή δεδομένων δοκιμής

- [Μπόγκους ΓκίτχουμπCity name (optional, probably does not need a translation)](https://github.com/bchavez/Bogus) - Ψεύτικη γεννήτρια δεδομένων
- [Αναφορά Bogus API](https://github.com/bchavez/Bogus#bogus-api-support) - Διαθέσιμοι τύποι δεδομένων

### Αναφέρονται εναλλακτικές λύσεις

- [CsvHelperCity name (optional, probably does not need a translation)](https://joshclose.github.io/CsvHelper/) - Βιβλιοθήκη ανάλυσης CSV
- [Microsoft.Data.Analysis](https://www.nuget.org/packages/Microsoft.Data.Analysis) - DataFrame για .NET
- [ΠάντασαϊCity name (optional, probably does not need a translation)](https://github.com/Sinaptik-AI/pandas-ai) - Python LLM + pandas ενσωμάτωση

### Σχετικά άρθρα

- [DataSummarizer: Fast Local Data Profiling](/blog/datasummarizer-how-it-works) - Companion article covering CLI-based data profiling