Σειρά: Τοπικές LLMs για δεδομένα - Μέρος 1 από 2
Εδώ είναι το λάθος που κάνουν όλοι: προσπαθούν να ταΐσουν το CSV τους σε LLM. Οι LLMs θα πρέπει να δημιουργούν ερωτήματα, όχι να καταναλώνουν δεδομένα.
Έχετε ένα αρχείο CSV 500MB και θέλετε να ρωτήσετε "Ποια είναι η μέση τιμή παραγγελίας ανά περιοχή;" Εργαλεία όπως Συνπιλότος στο Excel μπορεί να το κάνει αυτό, αλλά τι γίνεται αν τα δεδομένα σας είναι πολύ ευαίσθητα για τις υπηρεσίες σύννεφων; Τι γίνεται αν χρειάζεται να τα χτίσετε μόνοι σας;
Αυτό το άρθρο σας δείχνει πώς - τοπικά, ιδιωτικά, σε C#.
Χρησιμοποιήστε το DuckDB για να ρωτήσετε απευθείας τα αρχεία CSV. Χρησιμοποιήστε ένα τοπικό LLM για να δημιουργήσετε το SQL. Το LLM δεν βλέπει ποτέ τα δεδομένα σας - μόνο το σχήμα. Αποτέλεσμα: υπο-100ms ερωτήματα σε αρχεία εκατομμυρίων γραμμών, εντελώς εκτός σύνδεσης.
Για μια συμπληρωματική, πιο CLI-εστιασμένη θεραπεία που επεκτείνεται στη χρήση στατιστικών προφίλ ως διεπαφή LLM (και δείχνει ένα πλήρες εργαλείο εφαρμογής αυτών των ιδεών - προφίλ, ασφαλής λειτουργία SQL, συνθετική κλωνοποίηση και ανίχνευση παρασυρόμενων) δείτε το συνοδευτικό άρθρο: DataSummarizer: Fast Local Data Profiling - ειδικά το τμήμα "Η βασική αναβάθμιση: Στατιστικά ως Διεπαφή" . Αυτά τα δύο άρθρα σχηματίζουν μια σύντομη σειρά για πρακτικά τοπικά πρότυπα LLM + ερωτήσεις .
Χρησιμοποιώντας ένα LLM ως κατάστημα δεδομένων είναι η λάθος αφαίρεση. LLMs είναι ουσιαστικά ανίκανη να σαρώσει εκατομμύρια σειρές για να υπολογίσει ένα μέσο όρο - αυτό δεν είναι ό, τι είναι για. Ακόμη και ένα παράθυρο των 200K συμβολικό πλαίσιο ταιριάζει ίσως 50.000 σειρές. 500MB CSV σας έχει εκατομμύρια.
Το σωστό μοτίβο: Για λόγους LLM, η βάση δεδομένων υπολογίζει.
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.
Οι προφανείς προσεγγίσεις όλες έχουν το ίδιο μοιραίο ελάττωμα:
CsvHelperCity name (optional, probably does not need a translation) / DataFrames: Φορτώστε ολόκληρο το αρχείο σε RAM. Ένα CSV 500MB γίνεται 2-4GB αντικειμένων. Ένα αρχείο 5GB; OOM συντριβή.
SQLite / PostgreSQL: Απαιτεί ένα αργό βήμα εισαγωγής (λεπτά για μεγάλα αρχεία), μπροστινούς ορισμούς σχημάτων, και διαχείριση βάσεων δεδομένων από πάνω.
**ΠάντασαϊCity name (optional, probably does not need a translation)**Επιπλέον, η εκτέλεση αυθαίρετου κώδικα LLM είναι ένας εφιάλτης ασφαλείας - το SQL είναι δηλωτικό και γυαλιστερό· ο Python δεν είναι.
Πάπιες είναι διαφορετική. Αμφισβητεί αρχεία CSV άμεσα - χωρίς βήμα εισαγωγής, χωρίς φόρτωση στη μνήμη:
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 σας παρέχει πλήρη υποστήριξη ADO.NET - αισθάνεται σαν οποιαδήποτε άλλη βάση δεδομένων, εκτός από είστε ερωτήσεις αρχεία.
Γιατί αυτό εδώ;
|-----------|--------------|
| Πάπιες Ερωτήματα CSV άμεσα, κανένα βήμα εισαγωγής □
| DuckDB.NET Πλήρες ADO.NET υποστήριξη, αισθάνεται ιθαγενής
| ΟλάμαCity name (optional, probably does not need a translation) Τοπικό συμπέρασμα, χωρίς κλειδιά API, χωρίς σύννεφο
| ΜπόγκουςCity name (optional, probably does not need a translation) Ρεαλιστικά δεδομένα δοκιμών σε οποιαδήποτε κλίμακα
| qwen2.5-coder:7b Βέλτιστη ακρίβεια SQL στο μέγεθος 7B .
Σημείωση σχετικά με την ασφάλειαΑυτό είναι ασφαλέστερο από τον αυθαίρετο κώδικα, αλλά εξακολουθεί να απαιτεί επικύρωση. Τμήμα ασφαλείας Για μια ευρύτερη συζήτηση σχετικά με τη μετατροπή προφίλ στη διεπαφή LLM και ένα CLI που εφαρμόζει αυτά τα πρότυπα, δείτε το συνοδευτικό κομμάτι DataSummarizer: Fast Local Data Profiling.
Ας δημιουργήσουμε ένα δείγμα έργου. Εγκαταστήστε τα πακέτα NuGet:
dotnet add package DuckDB.NET.Data.Full
dotnet add package OllamaSharp
dotnet add package Bogus
Τραβήξτε ένα μοντέλο κωδικοποίησης που είναι καλό στο SQL:
ollama pull qwen2.5-coder:7b
Εδώ είναι πώς τα κομμάτια ταιριάζουν μεταξύ τους:
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.
Έχεις ήδη δεδομένα CSV; Μετάβαση στην Βήμα 2: Κατασκευή του πλαισίου Schema.
Πριν μπορέσουμε να ελέγξουμε τον αναλυτή CSV LLM, χρειαζόμαστε δεδομένα για να αναλύσουμε.
ΜπόγκουςCity name (optional, probably does not need a translation) είναι ένα .NET θύρα της δημοφιλούς βιβλιοθήκης faker.js. Δημιουργεί ρεαλιστικό εμφάνιση πλαστά δεδομένα - ονόματα, διευθύνσεις, emails, ημερομηνίες, αριθμούς - με σωστή υποστήριξη τοποθεσίας. Αντί της χειροτεχνίας αρχεία CSV δοκιμής ή χρησιμοποιώντας τυχαία δεδομένα σκουπιδιών, Bogus σας δίνει δεδομένα που ματιά Πραγματικό:
f.Name.FullName() → "John Smith" (όχι "asdf1234")f.Internet.Email() → "[email protected]" (κατάλληλα μορφοποιημένο)f.Date.Between(start, end) → Ρεαλιστική κατανομή ημερομηνίαςf.Commerce.ProductName() → "Handcrafted Granite Cheese" (διασκέδαση, αλλά αναγνωρίσιμο)Αυτό έχει σημασία επειδή τα ρεαλιστικά δεδομένα σας βοηθούν να εντοπίσετε θέματα - περίεργη μορφοποίηση, απροσδόκητες συγκεντρώσεις, ακραίες περιπτώσεις στο χειρισμό ημερομηνίας - ότι τυχαίες συμβολοσειρές θα κρύβονταν.
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; }
}
Ο Μπόγκους χρησιμοποιεί ένα άπταιστο API για να καθορίσει τους κανόνες παραγωγής:
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) μοτίβο είναι ισχυρό - αυτό σημαίνει "Ηλεκτρονικά" παραγγελίες πάρει ηλεκτρονικά ονόματα προϊόντων, όχι τυχαία στοιχεία. Αυτή η συνοχή καθιστά τα δεδομένα που παράγονται πολύ πιο ρεαλιστικά για δοκιμές συγκεντρώσεις όπως "ανανέωση ανά κατηγορία."
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)ο ίδιος σπόρος = ίδια "τυχαία" αρχεία κάθε φορά, η οποία είναι ανεκτίμητη για αποσφαλμάτωση.
Πριν η LLM μπορεί να δημιουργήσει SQL, πρέπει να κατανοήσει τη δομή των δεδομένων.
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: ονόματα στήλης, τύποι, και μερικές σειρές δειγμάτων για να κατανοήσουν τη μορφή των δεδομένων.
Το DuckDB μπορεί να περιγράψει οποιοδήποτε CSV χωρίς να το φορτώσει όλα:
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 να κατανοήσει μορφές δεδομένων (ημερομηνία, ταυτότητες κ.λπ.):
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 τι μορφές να περιμένετε χωρίς να σπαταλάτε μάρκες.
Αυτό είναι το δύσκολο κομμάτι. Η άμεση μηχανική εδώ δεν είναι διαπραγματεύσιμη - χωρίς αυστηρούς κανόνες, οι τοπικές LLMs θα παράγουν δημιουργικά αλλά σπασμένα SQL. Ο στόχος είναι ο προσδιορισμός, όχι η δημιουργικότητα.
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 συμβολοσειρά) αποτρέπει κοινά λάθη.
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)}}}");
}
}
Εάν μια προηγούμενη προσπάθεια απέτυχε, συμπεριλάβετε το σφάλμα:
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 μερικές φορές κάνουν λάθη σύνταξης, και δίνοντας τους το σφάλμα συνήθως το διορθώνει στη δεύτερη προσπάθεια.
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 παρά το γεγονός ότι τους λένε να μην:
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');
}
Παπιοπεριπέτειες EXPLAIN Μας επιτρέπει να ελέγξουμε τη σύνταξη SQL χωρίς να κάνουμε την ερώτηση:
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 και ξαναπροσπαθούμε (μέχρι ένα όριο).
Τέλος, να εκτελέσετε το ερώτημα και να διαμορφώσετε την έξοδο:
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"
Το δεύτερο και το τρίτο ερώτημα έχουν νόημα μόνο με το πλαίσιο από το πρώτο.
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"
}
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:
Μοντέλο Μέγεθος Ταχύτητα Ποιότητας Σύνδεσμος
| ------- | ------ | ------- | --------- | ------ |
|---|---|---|---|---|
deepseek-coder-v2:16b Μεσαίο 9GB μεσαίο Best ΟλάμαCity name (optional, probably does not need a translation) |
||||
codellama:7b Γρήγορα Ωραία ΟλάμαCity name (optional, probably does not need a translation) |
||||
llama3.2:3b Πολύ γρήγορος. Αποδεκτός ΟλάμαCity name (optional, probably does not need a translation) |
Για τις περισσότερες περιπτώσεις χρήσης, qwen2.5-coder:7b χτυπά το γλυκό σημείο - ακριβή SQL, καλή ταχύτητα, τρέχει σε μέτρια υλικό (8GB+ RAM).
Δοκιμή σε ένα αρχείο 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 - χωρίς κανένα βήμα εισαγωγής.
Για αρχεία άνω του 1GB, Μορφή παρκέ είναι 100- 100x γρηγορότερα:
using var cmd = connection.CreateCommand();
cmd.CommandText = $"COPY (SELECT * FROM '{csvPath}') TO '{parquetPath}' (FORMAT PARQUET)";
cmd.ExecuteNonQuery();
Το συμπιεσμένο αρχείο Parquet είναι επίσης πολύ μικρότερο.
Κατά την εκτέλεση του SQL που παράγεται από 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));
}
Ο τρόπος μνήμης του DuckDB παρέχει επίσης φυσική απομόνωση - δεν μπορεί να επηρεάσει τις βάσεις δεδομένων παραγωγής σας.
Εδώ είναι πώς όλα έρχονται μαζί:
// 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 ενάντια σε μια σωστή μηχανή ερώτημα. Αυτός είναι ο λόγος για τον οποίο η προσέγγιση λειτουργεί σε κλίμακα.
Η εφαρμογή:
Το αποτέλεσμα: υπο-100ms αναλυτικές ερωτήσεις σε αρχεία εκατομμυρίων γραμμών, εντελώς εκτός σύνδεσης, με δεδομένα που δεν αφήνουν ποτέ τη μηχανή σας.
Το πλήρες δείγμα του έργου είναι διαθέσιμο στις Κυρίως διαυγής.CsvLlm - περιλαμβάνει CsvQueryService, ConversationalCsvAnalyser, και Bogus-based παραγωγή δεδομένων.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.