Comment analyser les grands fichiers CSV avec des LLM locaux en C# (Français (French))

Comment analyser les grands fichiers CSV avec des LLM locaux en C#

Thursday, 18 December 2025

//

20 minute read

Série: LLMs locaux pour les données - Partie 1 de 2

C’est là l’erreur que tout le monde fait, c’est ici qu’ils tentent d’alimenter leur CSV dans un LLM. Les LLM devraient générer des demandes, ne consomment pas de données.

Vous ' avez un fichier CSV de 500MB et vous voulez demander MSC2 Quelle est la valeur moyenne d'ordonnance par région? Copilote dans Excel peut faire cela, mais que faire si vos données sont trop sensibles pour les services de nuage? Que faire si vous devez les construire vous-mêmeM SK2

Cet article vous montre comment - localement , privéementM SK2 dans C#.

Utiliser DuckDB pour interroger directement les fichiers CSV. Utiliser un LLM local pour générer l’ SQL. Le LLM ne voit jamais vos données M SK2 simplement le schéma . RésultatMSC4 sous-référenceMST5 ms de demandes sur un million MST6 fichiers en ligneM ST7 complètement hors connexionM st8

Pour un traitement complémentaire , plus CLI - axé sur l'utilisation de profils statistiques comme interface LLM ( et montre un outil complet pour la mise en œuvre de ces idées M- profilage m, mode SQL sécuritaire МSK5 clonage synthétique et détection des dérives P), voir l'article conjoint DataSummarizer: Profilage rapide des données locales - en particulier la section "La mise à niveau cléM SK2 Statistiques comme interface". Ces deux articles forment une courte série sur le LLM local pratique.

L'incertitude fondamentale

Utiliser un LLM comme entrepôt de données est une abstraction erronée. Les LLM sont fondamentalement incapables d’analyser des millions de lignes pour calculer une moyenne M SK1 que' n’est pas ce qu’ils 're pourMSC4 Même une fenêtre contextuelle de token 200K se prête peut-être à |50,000 rowsMST7 Votre ♫500MB CSV a des millionsMSL9

Le modèle correct: Motifs de LLM, Computes de base de données.

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

Veuillez noter ce qui se passe' : le LLM génère une demande SQL fondée sur votre question et le schéma. DuckDB l'execute contre les données réellesM SK3 Le LLM ne touche jamais vos données MSC4 il ne voit que les noms de colonnes et les typesMST5 C'est la raison pour laquelle c'est rapideMSL6 MST7 privéM ST8 et exacteM st9

Pour en savoir plus sur le traitement des profils comme l'interface LLM (et un CLI concrète qui met en œuvre le profil-première présentationM SK2 SQL sécuritaire - Q-backedMST4AMSS5 registreMSST6 sessions backed et clonage synthétiqueMSM7 voir DataSummarizer: Profilage rapide des données locales.

Pourquoi ne pas simplement le charger dans la mémoire?

Les approches évidentes partagent tous le même défaut fatal.

CsvHelper / Cadres de données: Charger l'ensemble du fichier dans le RAM. Un 500MB CSV devient un MSC3GB d'objetsM SK4 Un file S5GBMSC6 OOM crashMST7

SQLite / PostgreSQL: Demande une étape d’importation lente (minutes pour les fichiers de grande taille

PandasAI: Il charge toujours tout dans la mémoire . Plus, exécuter LLMM SK3 le code arbitraire généré est un cauchemar de sécurité.

Pourquoi DuckDB

DuckDB est différent. Il demande des fichiers CSV Directement - aucune étape d'importation , aucun chargement dans la mémoire:

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 caractéristique assassine: il traite les fichiers comme des tableaux. Envoyez-le à un CSVM SK1 Parquet , ou fichier JSON et demandez immédiatement. Pas de TABLEAU DE CRÉATION

Facteur CsvHelper SQLite S DuckDB M
Mémoire Charge l'intégralité du fichier Charge pendant l'importation
Mise en place Aucune Système d’importation MSC2 Importations MSC3 Aucunes SMC4
500MB fichier ~2 RAM de GB MSC2 Minutes à importer SMC3 Instant SMC4
5GB fichier OOM crash Très lente fonctionne bien S
Parquet Non Non

DuckDB est ce que les ingénieurs de données utilisent dans Python pour exactement cette utilisation. .Bindings NET vous donner le plein ADO.Support NET - il se sent comme toute autre base de données , à l’exception de vousM SK3des fichiers de demandes de renseignements

The Stack

Component Pourquoi c'est-ce qui est le cas ?
DuckDB Demandes de renseignements CSV directementM SK1 pas d'importation
DuckDB.NET ADO complet.Support NETM SK2 se sent natif
Ollama Inférence locale , pas de clés API
Bogus Données d'essai réalistes à n'importe quelle échelle
qwen2.5-coder:7b La meilleure précision SQL à la taille de 7B

Note sur la sécurité: Nous ' exécutons LLM- SQL généréM SK3 Il s’agit d’un code plus sûr que le codage arbitraire Section de la sécurité pour les garanties. Pour une discussion plus large sur la transformation des profils dans l’interface LLM et un CLI qui applique ces modèles ,, voir le document complémentaire DataSummarizer: Profilage rapide des données locales.

Établissement du projet

Let's créer un projet d'échantillonnage . Installer les paquets NuGet:

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

Faire appel à un modèle de codage axé sur - qui soit bon dans SQL

ollama pull qwen2.5-coder:7b

L'architecture

Voici' comment les pièces s’intègrent ensemble:

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’influence clé que nous avons apportée à la LLM, c’est le :. schéma et données d'échantillonnage, non les données réellesM SK1 Cela maintient le contexte petit et les réponses rapides .

Pourquoi cela est important?: Le LLM génère l'intention (SQLM SK2 DuckDB l'execute. La étape de validation saisit les erreurs de syntaxe avant l'exécutionMSC4 La boucle d'essai remédie aux erreurs occasionnellesMNK5 Cette séparation est ce qui rend le système à la fois sûr et précisMRK6 Pour un aperçu plus détailléMMK7 CLI-\Reférence centrée |(\y compris le profil\MNK10\premier exposé et limites de sécurité d'exécution SQL\MGK11\ voir DataSummarizer: Profilage rapide des données locales.

Étape 1: Gérer des données d'essai avec Bogus

Vous avez déjà des données CSV? Passer à Étape 2: Construire le contexte du schéma.

Avant que nous puissions tester notre LLM-analyseur CSV à moteur, nous avons besoin de données pour analyserM SK2 Pour le développement et la mise à l’essai , les données synthétiques battent les données réellesMSC4

  1. Évaluation à l'échelle - Générer des rangées 100KM SK2 | | 3 | 4 | ou | 5 | pour vérifier la performance à différentes tailles
  2. Protection des renseignements personnels - Il n'y a pas de risque d'exposer des données commerciales sur les clients réels / dans des démonstrations ou des screenshots
  3. Reproductibilité - Le même graine = les mêmes donnéesM SK2 rendant les bogues reproduisables
  4. Cases à bord - Contrôle de la distribution eM SK2g., force

Qu'est-ce que Bogus?

Bogus est un port .NET du faker populaireM SK1js library . Il produit des données réalistes-apparentement fausses |- noms=, adressesMSSK6 courriels+, dates*, numéros - avec le soutien local approprié | . Au lieu d'utiliser la main à l'essai de fichiers CSV ou en utilisant les données de déchets aléatoires | aperçus Réel:

  • f.Name.FullName() → "John SmithM SK2 ≥(non
  • f.Internet.Email() → "johnMSC2smith@gmailM SK4comMST5 |
  • f.Date.Between(start, end) → Répartition réaliste des dates
  • f.Commerce.ProductName() →

Cela est important parce que les données réalistes vous aident à repérer des problèmes - formats étranges , agrégations inattendues M SK2 cas de bord dans la manipulation des dates

Définir le modèle de données

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

Configurer le Faker

Bogus utilise une API fluide pour définir les règles de génération:

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

Qu’on détermine ce qui se passe.

  • f (FakerM SK1 - Instance génératrice avec accès à tous les modules de données NomM SK2 Date, Aléatoire
  • f.Random.Guid().ToString()[..8] - Gérer un GUID mais ne prendre que les premières chars 8 pour une ID d'ordre lisible
  • f.Date.Between() - Date aléatoire dans une plage réaliste (non année 9999)
  • f.PickRandom(array) - Choisissez au hasard parmi les options définies précédemment.
  • f.Random.Bool(0.3f) - 30% chance d’obtenir un rabais sur les commandes
  • (f, s) syntaxe - Accès à la fois faker ET partiellement -enregistrement bâti. Ce qui permet ProductName de dépendre Category

L'article 5 de la Loi sur l'immigration et le statut des réfugiés (f, s) Cette cohérence rend les données générées beaucoup plus réalistes pour tester des agrégats comme :

Générer et écrire en 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 rows génére environ 15MB de CSV

Conseils: Set Randomizer.Seed = new Random(12345) avant de produire des données reproductibles. Same seed = same " randomM SK3 records every timeMSC4 which is invaluable for debuggingMNK5

Étape 2: Construire le contexte du schéma

Avant que le LLM puisse générer SQL, il faut comprendre la structure des données.

Le modèle de contexte

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

Cela permet de saisir tout ce dont le LLM a besoin: noms de colonnes , typesM SK2 et quelques rangées d’échantillon pour comprendre le format des données.

Extraction du schéma

DuckDB peut décrire n'importe quel CSV sans le charger tout:

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

L'article 5 de la Loi sur l'immigration et le statut des réfugiés DESCRIBE la commande ne lit que le titre du fichier plus quelques lignes pour l'inférence de type - it' s instantanée même sur des fichiers énormes .

Obtenir des données d'échantillonnage

Les lignes d'échantillonnage aident le LLM à comprendre les formats de données (datesM SK1IDs , etc.):

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

Trois rangées sont habituellement suffisantes - il montre au LLM les formats à attendre sans perdre des tokens.

Étape 3: Gérer SQL avec le LLM

C'est la partie difficile. L’ingénierie rapide ici n’est pas - négociable - sans règles strictesM SK2 les LLM locaux produisent des SQL créatifs mais déformés. l’objectif est le determinismeMST4 non la créativitéMSS5

Établissement de la promesse

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 section des règles est cruciale - elle indique au LLM exactement comment formater la demande pour DuckDB. Être explicite quant à la syntaxe (LIMIT vs TOPMSC3 concatenation de chaînesM SK4 empêche les erreurs courantes

Ajouter des données d'échantillonnage

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

Récupération d'une erreur

Si une tentative antérieure a échoué, inclure l'erreur

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

Ce mécanisme de réessai est important - les LLM locaux font parfois des erreurs de syntaxe , et en leur donnant l’erreur, ils le corrigent habituellement lors du deuxième essai.

En appelant le LLM

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

L'article 5 de la Loi sur l'immigration et le statut des réfugiés StreamToEndAsync() waits for the complete response. For a better UX, you could stream tokens as they arrive

Nettoyage de la réponse

Les LLM enveloppent souvent SQL dans des blocs de code de marquage en dépit d’être informés de ne pas le faire:

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

Étape 4: Valider avant l'exécution

DuckDB's EXPLAIN nous permet de vérifier la syntaxe SQL sans faire passer la demande:

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

Si la validation n'est pas effectuée, nous retournons l'erreur au LLM et tenons à nouveau ( jusqu'à un seuil

Étape 5: Exécuter et formater les résultats

Finalement, effectuer la recherche et formater le résultat:

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

L'article 5 de la Loi sur l'immigration et le statut des réfugiés QueryResult class ( montré en entier dans le projet d’échantillonnage ToString() méthode qui forme les résultats comme une table lisible.

Ajouter le contexte de la conversation

Pour l’analyse interactive, les utilisateurs souhaitent souvent poser des questions à la suite

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

Les deuxième et troisième questions n’ont de sens que dans le contexte du premier questionnaire.

Suivi de l'historique des conversations

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

Incluant l'historique dans les prompts

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

L’historique permet au contexte de l’ALM de comprendre des références comme : "qu’M SK1 "ceux résultats", ou "la débrouiller plus loin

Quel modèle utiliser?

Pour la génération de SQL, codage-les modèles ciblés fonctionnent le mieux . Voici les options disponibles par Ollama's modèle de bibliothèque:

Modèle MSC1 Taille MSC2 Accès rapide SMC3 Qualité MMC4 Lien RMC5
qwen2.5-coder:7b MSC1GB MSC2 rapide Excellent SMC4 Ollama
deepseek-coder-v2:16b 9GB MSC2 Moyenne MSC3 Meilleure SMC4 Ollama
codellama:7b MSC1GB MSC2 Fast SMC3 Bon SMC4 Ollama
llama3.2:3b MSC1GB MSC2 Très rapide SMC3 Acceptable SMC4 Ollama

Pour la plupart des cas d'utilisation, qwen2.5-coder:7b , fonctionne sur un matériel modeste (8GBM SK4 RAM).

Rendement

Real-Marches de référence mondiales

Essai sur une row CSV de 100K (14MB) sur un ordinateur standard dev | (Ryzen |5, | NVMe SSD |

Type de demande Temps
Compte simple 65ms S
GROUPE PAR avec SUM 58-68ms M
Aggregation complexe avec FILTER 63ms
Multi-table GROUPE PAR avec HAVING 71ms S

Sub-100ms pour les demandes analytiques sur M SK1K rows - sans aucune étape d'importation. La complexité des demandes est plus importante que le nombre de lignesMSC4 DuckDBMNK5le moteur à colonnes permet de gérer efficacement l'agrégation indépendamment de la taille du fichier

Convertir de grands fichiers en parquet

Pour les fichiers au-dessus de 1GB, Format du parquet est 10-100x plus rapideM SK1

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

Le fichier de parquet comprimé est également beaucoup plus petit.

Considérations en matière de sécurité

En exécutant LLM-générer SQL:

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 dans le mode de mémoire- offre également une isolation naturelle.

Exemple complète

Voici comment cela allègue-t-il ?

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

Résumé

Le modèle mental à maintenir: LLMs raison; calcul des bases de données.

Donn't feed data to the LLMM SK1 Feed it schema, let it generate SQL , execute that SQL against a proper query engineMSC4 Cette séparation est la raison pour laquelle l’approche fonctionne à grande échelle

La mise en œuvre:

  1. DuckDB demandes CSV directement - pas d'importation, flux de disque
  2. Échantillons du schéma + donner au LLM suffisamment de contexte sans exposer les données
  3. Règles strictes de prompt force deterministic SQL, non prose créative
  4. Validation via EXPLAIN saisit les erreurs avant l'exécution
  5. Essayer de nouveau avec un retour d'information sur les erreurs gère le feuillet de syntaxe occasionnel

Résultat: sous--100ms de demandes analytiques sur un million de fichiers de rangéeM SK2 files en ligne, complètement hors ligne , avec des données qui n'abandonnent jamais votre machineMSC5

L'échantillon complet du projet est disponible à : Mostlylucid.CsvLlm - inclut CsvQueryService, ConversationalCsvAnalyser, et Bogus-génération de données fondée sur la .

Ressources

DuckDB

Ollama & LLMs

Production de données d'essai

Solutions envisagées

Articles connexes

Finding related posts...
logo

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