Back to "Come analizzare grandi file CSV con LLM locali in C#"

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

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

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

AI C# Data Analysis DuckDB LLM Ollama

Come analizzare grandi file CSV con LLM locali in C#

Thursday, 18 December 2025

Serie: LLM locali per i dati - Parte 1 di 2

Ecco l'errore che tutti commettono: cercano di alimentare il loro CSV in un LLM. Non farlo. I LLM dovrebbero generare query, non consumare dati.

Hai un file CSV da 500MB e vuoi chiedere "Qual è il valore medio dell'ordine per regione?" Copilota in Excel può farlo, ma se i tuoi dati sono troppo sensibili per i servizi cloud? Cosa succede se hai bisogno di costruirli da soli?

Questo articolo mostra come - localmente, privatamente, in C#.

Usa DuckDB per interrogare direttamente i file CSV. Usa un LLM locale per generare SQL. Il LLM non vede mai i tuoi dati - solo lo schema. Risultato: query sub-100ms su file da milioni di righe, completamente offline.

Per un trattamento complementare e più focalizzato su CLI che si espande sull'utilizzo di profili statistici come interfaccia LLM (e mostra uno strumento completo che implementa queste idee - profilazione, modalità SQL sicura, clonazione sintetica e rilevamento della deriva), vedere l'articolo seguente: DataSummarizer: Profilazione rapida dei dati locali - in particolare la sezione "The Key Upgrade: Statistics as the Interface." Questi due articoli formano una breve serie sui modelli pratici locali LLM + query.

Il Core Insight

Usare un LLM come archivio dati è l'astrazione sbagliata. I LLM sono fondamentalmente incapaci di scansionare milioni di righe per calcolare una media - non è quello che servono. Anche una finestra di contesto token da 200K misura forse 50.000 righe. Il tuo CSV da 500MB ha milioni.

Il modello corretto: Motivi dell'LLM, calcolo del database.

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

Notate cosa sta succedendo: l'LLM genera una query SQL basata sulla vostra domanda e sullo schema. DuckDB la esegue contro i dati reali. L'LLM non tocca mai i vostri dati - vede solo i nomi delle colonne e i tipi. Ecco perché è veloce, privato e preciso.

Per maggiori informazioni sul trattamento dei profili come interfaccia LLM (e una CLI concreta che implementa la prima narrazione del profilo, il Q&A protetto da SQL, le sessioni supportate dal registro e la clonazione sintetica), vedere DataSummarizer: Profilazione rapida dei dati locali.

Perché non caricarlo nella memoria?

Gli approcci ovvi hanno tutti lo stesso difetto fatale:

CsvHelper / DataFrames: Caricare l'intero file in RAM. Un CSV da 500MB diventa 2-4GB di oggetti. Un file da 5GB? Crash OOM.

SQLite / PostgreSQL: Richiede un passo lento di importazione (minuti per file di grandi dimensioni), definizioni di schema upfront, e gestione del database overhead.

PandasAICity name (optional, probably does not need a translation): Ancora carica tutto in memoria. Inoltre, l'esecuzione di codice arbitrario generato da LLM è un incubo di sicurezza - SQL è dichiarativo e sandboxable; Python non lo è.

Perché DuckDB

AnatraDBunit synonyms for matching user input è diverso. Interroga i file CSV direttamente - nessun passaggio di importazione, nessun caricamento in memoria:

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

La caratteristica killer: tratta i file come tabelle. Puntalo su un file CSV, Parquet o JSON e chiedi immediatamente. Nessun CREATE TABLE, nessun inserto sfuso, nessuna attesa.

Fattore CsvHelper SQLite DuckDB
Memoria Carica tutto il file Carica durante l'importazione Streams from disc
Configurazione Nessuno Schema + importazione Nessuno
File 500MB ~2GB RAM Minuti di importazione Istantanea
File 5GB Crash OOM Molto lento Funziona bene
Parquet No No Sì (10-100x più veloce)

DuckDB è ciò che gli ingegneri di dati utilizzano in Python per esattamente questo caso d'uso. Legame .NET dare il supporto completo ADO.NET - si sente come qualsiasi altro database, tranne che si sta interrogando i file.

Lo StackCity name (optional, probably does not need a translation)

Componente Perché questo
AnatraDBunit synonyms for matching user input Queries CSV directly, no import step
DuckDB.NET Supporto completo ADO.NET, si sente nativo
OllamaCity name (optional, probably does not need a translation) Inferenza locale, nessuna chiave API, nessuna nuvola
BogusCity name (optional, probably does not need a translation) Dati di prova realistici su qualsiasi scala
qwen2.5-coder:7b Migliore precisione SQL a dimensione 7B

Nota sulla sicurezza: Stiamo eseguendo SQL generato da LLM. Questo è più sicuro del codice arbitrario, ma richiede comunque la convalida. Sezione Sicurezza Per una più ampia discussione sulla trasformazione dei profili nell'interfaccia LLM e in una CLI che implementa questi modelli, vedere il pezzo di accompagnamento DataSummarizer: Profilazione rapida dei dati locali.

Configurazione progetto

Creiamo un progetto di esempio. Installa i pacchetti NuGet:

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

Tirare un modello focalizzato sulla codifica che è bravo in SQL:

ollama pull qwen2.5-coder:7b

L'architettura

Ecco come i pezzi si adattano insieme:

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

L'intuizione chiave: diamo l'LLM schema e dati del campione, non i dati reali. Questo mantiene il contesto piccolo e le risposte veloci.

Perche' questo e' importante?: Il LLM genera intent (SQL). DuckDB lo esegue. Il passo di validazione cattura gli errori di sintassi prima dell'esecuzione. Il loop di riprova gestisce l'errore occasionale. Questa separazione è ciò che rende il sistema sia sicuro che preciso. Per un riferimento più dettagliato, CLI-centrato (incluso profilo-primo narrazione e sicuri limiti di esecuzione SQL) vedere DataSummarizer: Profilazione rapida dei dati locali.

Passo 1: Generare i dati di prova con Bogus

Hai già i dati CSV? Skip to Passo 2: Costruire il contesto schema.

Prima di poter testare il nostro analizzatore CSV alimentato da LLM, abbiamo bisogno di dati da analizzare. Per lo sviluppo e il test, i dati sintetici battono i dati reali:

  1. Prova su scala - Generare righe 100K, 1M o 10M per verificare le prestazioni in diverse dimensioni
  2. Privacy - Nessun rischio di esporre i dati reali cliente / business in demo o screenshot
  3. Riproducibilità - Stesso seme = stessi dati, rendendo riproducibili i bug
  4. Casi di bordo - Controllare la distribuzione (ad esempio, forza 5% ritorni, intervalli di date specifici)

Che cos'è Bogus?

BogusCity name (optional, probably does not need a translation) è una porta .NET della libreria popolare faker.js. Genera dati falsi realistici - nomi, indirizzi, e-mail, date, numeri - con un adeguato supporto locale. Invece di file CSV di test artigianali o utilizzando dati casuali della spazzatura, Bogus ti dà dati che sguardi reale:

  • f.Name.FullName() → "John Smith" (non "asdf1234")
  • f.Internet.Email() → "[email protected]" (proprio formattato)
  • f.Date.Between(start, end) → Distribuzione realistica delle date
  • f.Commerce.ProductName() → "Formaggio di Granito artigianale" (divertente, ma riconoscibile)

Questo è importante perché dati realistici ti aiutano a individuare problemi - formattazione strana, aggregazioni inaspettate, casi di bordo nella gestione delle date - che stringhe casuali si nascondono.

Definire il modello di dati

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

Configura il Faker

Bogus utilizza un'API fluente per definire le regole di generazione:

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

Distruggiamo quello che sta succedendo:

  • f (Faker) - L'istanza generatore con accesso a tutti i moduli di dati (Nome, Data, Casuale, ecc.)
  • f.Random.Guid().ToString()[..8] - Generare un GUID ma prendere solo i primi 8 caratteri per un ID ordine leggibile
  • f.Date.Between() - Data casuale entro un intervallo realistico (non anno 9999)
  • f.PickRandom(array) - Selezionare in modo casuale da opzioni predefinite (esprime categorie valide)
  • f.Random.Bool(0.3f) - 30% di probabilità di Vero (30% di ordini ottenere uno sconto)
  • (f, s) sintassi - Accedi sia al falso che al disco parzialmente costruito. ProductName dipende da Category

La (f, s) pattern è potente - significa che gli ordini "Electronics" ottengono i nomi dei prodotti elettronici, non oggetti casuali. Questa coerenza rende i dati generati molto più realistici per testare le aggregazioni come "revenue per categoria."

Genera e scrivi su CSV

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 righe genera circa 15MB di CSV - abbastanza per testare, ma si può facilmente scalare a milioni. La generazione è veloce (~ 2 secondi per 100K righe) perché Bogus è ottimizzato per la generazione all'ingrosso.

Suggerimento: Imposta Randomizer.Seed = new Random(12345) prima di generare per ottenere dati riproducibili. Stesso seme = stesso "casuale" registra ogni volta, che è inestimabile per il debug.

Passo 2: Costruire il contesto schema

Prima che l'LLM possa generare SQL, deve capire la struttura dei dati. Lo estraiamo da DuckDB:

Il modello contestuale

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

Questo cattura tutto ciò di cui l'LLM ha bisogno: nomi delle colonne, tipi e alcune righe di esempio per capire il formato dei dati.

Estrazione dello schema

DuckDB può descrivere qualsiasi CSV senza caricare tutto:

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

La DESCRIBE comando legge solo l'intestazione del file più alcune righe per l'inferenza di tipo - è istantaneo anche su file enormi.

Ottenere i dati del campione

Le righe di esempio aiutano l'LLM a comprendere i formati dei dati (date, ID, ecc.):

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 righe sono di solito sufficienti - mostra al LLM quali formati aspettarsi senza sprecare gettoni.

Passo 3: Generare SQL con il LLM

Questa è la parte difficile. L'ingegneria rapida qui non è negoziabile - senza regole rigorose, LLM locali produrrà SQL creativo ma rotto. L'obiettivo è il determinismo, non la creatività.

Costruire il Prompt

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

La sezione regole è cruciale - dice al LLM esattamente come formattare la query per DuckDB. Essere espliciti sulla sintassi (LIMIT vs TOP, concatenazione delle stringhe) impedisce errori comuni.

Aggiunta dei dati del campione

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

Errore di recupero

Se un tentativo precedente non è riuscito, includere l'errore:

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

Questo meccanismo di riprovazione è importante - gli LLM locali a volte commettono errori di sintassi, e dando loro l'errore di solito lo corregge al secondo tentativo.

Chiamare l'LLM

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

La StreamToEndAsync() Aspetta la risposta completa. Per un UX migliore, si potrebbe trasmettere i gettoni quando arrivano.

Pulire la risposta

I LLM spesso avvolgono SQL nei blocchi di codice markdown nonostante sia stato detto di non:

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

Passo 4: Convalida prima dell'esecuzione

DUCKDB'S EXPLAIN ci permette di controllare la sintassi SQL senza eseguire la query:

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

Se la convalida fallisce, rimettiamo l'errore alla LLM e riproviamo (fino a un limite).

Passo 5: Eseguire e Formattare i risultati

Infine, eseguire la query e formattare l'output:

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

La QueryResult class (mostrato per intero nel progetto campione) include un ToString() metodo che formatta risultati come una tabella leggibile.

Aggiunta del contesto di conversazione

Per l'analisi interattiva, gli utenti spesso vogliono porre domande di follow-up:

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

La seconda e la terza questione hanno senso solo con il contesto dalla prima.

Cronologia delle conversazioni di monitoraggio

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

Compresa la storia nei prompt

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

La storia dà al contesto LLM per comprendere riferimenti come "quello," "quelli risultati," o "disgregarlo ulteriormente."

Quale modello usare?

Per la generazione SQL, i modelli focalizzati sulla codifica funzionano meglio. Ecco le opzioni disponibili tramite La libreria modello di Ollama:

Modello Dimensioni Velocità Qualità Collegamento
qwen2.5-coder:7b 4.7GB Fast Excellent OllamaCity name (optional, probably does not need a translation)
deepseek-coder-v2:16b 9GB Medio Migliore OllamaCity name (optional, probably does not need a translation)
codellama:7b 4GB Fast Good OllamaCity name (optional, probably does not need a translation)
llama3.2:3b 2GB Molto veloce Accettabile OllamaCity name (optional, probably does not need a translation)

Per la maggior parte dei casi d'uso, qwen2.5-coder:7b colpisce il punto dolce - SQL accurato, buona velocità, funziona su hardware modesto (8GB + RAM).

Prestazioni

Benchmark del mondo reale

Test su un file CSV a riga 100K (14MB) su una macchina dev standard (Ryzen 5, NVMe SSD, 32GB RAM):

Tipo di domanda Tempo
COUNT semplice 65ms
GROUP BY with SUM 58-68ms
Aggregazione complessa con FILTRO 63ms
GROUPO multitavola BY con RISPARMIO 71ms

Sotto-100ms per le query analitiche su righe 100K - senza alcun passaggio di importazione. La complessità di query conta più del numero di righe; il motore colonnare di DuckDB gestisce le aggregazioni in modo efficiente indipendentemente dalla dimensione del file.

Converti file di grandi dimensioni in parquet

Per file superiori a 1GB, Formato del parquet è 10-100x più veloce:

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

Anche il file Parquet compresso è molto più piccolo.

Considerazioni in materia di sicurezza

Quando si esegue SQL generato da LLM:

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

La modalità in-memory di DuckDB fornisce anche l'isolamento naturale - non può influenzare i database di produzione.

Esempio completo

Ecco come si fondono le cose:

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

Sommario

Il modello mentale da mantenere: LLMs ragione; basi di dati calcolare.

Non caricare i dati sull'LLM. Lo schema, lascialo generare SQL, esegue SQL contro un motore di query corretto. Questa separazione è il motivo per cui l'approccio funziona in scala.

L'attuazione:

  1. AnatraDBunit synonyms for matching user input query CSV direttamente - nessuna importazione, flussi da disco
  2. Schema + campioni dare all'LLM un contesto sufficiente senza esporre i dati
  3. Regole rigorose e rapide forza deterministica SQL, prosa non creativa
  4. Convalida tramite EXPLAIN errori di cattura prima dell'esecuzione
  5. Riprova con il feedback degli errori gestisce lo slip di sintassi occasionale

Il risultato: query analitiche sub-100ms su file million-row, completamente offline, con dati che non lasciano mai la tua macchina.

Il progetto completo del campione è disponibile all'indirizzo: Per lo più Lucid.CsvLlm - comprende: CsvQueryService, ConversationalCsvAnalyser, e generazione di dati basati su Bogus.

Risorse

AnatraDBunit synonyms for matching user input

Ollama & LLMs

Generazione dei dati di prova

Alternative citate

Articoli correlati

logo

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