如何用 C# 中的本地 LLM 分析大型 CSV 文件 (中文 (Chinese Simplified))

如何用 C# 中的本地 LLM 分析大型 CSV 文件

Thursday, 18 December 2025

//

12 minute read

系列:用于数据的地方LMLM(当地LLM) -- -- 第2部分第1部分

这是每个人犯的错误: 他们试图把他们的CSV 输入一个LLM。不要。 LLMs应提出查询,而不是消耗数据。

您有一个 500MB CSV 文件, 想要问“ 区域的平均订单值是多少? ” 工具类似 。 使用Excel的副驾驶员 如果数据对云服务太敏感了呢?如果需要自己建造呢?

这篇文章向您展示了当地、私人、 C# 中的方式。

使用 DuckDB 直接查询 CSV 文件。 使用本地 LLM 生成 SQL。 LLM 从未看到您的数据 - 只看到 schema 。 结果: 百万行文件的子 100 m 查询, 完全脱机 。

关于补充性、更注重国家牵头倡议的处理方法,即扩大利用统计概况作为LLM接口(并展示一个完整工具来落实这些想法----特征分析、安全SQL模式、合成克隆和漂流探测),见配套文章: DataSummarizer: 快速本地数据分析 - 特别是“关键升级:统计作为接口”一节。这两篇文章构成关于当地实用LLM+查询模式的简短系列。

核心洞见

将LLM 用作数据存储器是错误的抽象。 LLM 根本上无法扫描数百万行来计算平均值 — — 这并不是它们的目的。即使是200K 象征性的上下文窗口,也可能是5万行。 您的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接口(以及执行剖面第一个解析、安全 SQL支持的 SQL A、登记册支持的会话和合成克隆的具体的CLI),见 DataSummarizer: 快速本地数据分析.

为什么不把它装进记忆里呢?

显而易见的方法都有着相同的致命缺陷:

Csv 帮助器 / 数据框架: 将整个文件装入 RAM 。 A 500MB CSV 变成 2-4GB 对象。 A 5GB 文件? OOM 崩溃 。

SQLite / PostgreSQL: 需要缓慢的输入步骤(大文件的分钟),前期计划定义,以及数据库管理间接费用。

潘达斯国际: 仍然将所有东西加载到记忆中。 此外, 执行LLM 产生的任意代码是一种安全恶梦 SQL 是宣示性的, 沙箱可以; Python 不是 。

为什么 duckDB

鸭鸭DDB 查询 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 文件并立即查询。没有 CREATE Table, 没有大容量插入, 没有等待 。

  • 因素 * * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelper * SQLite * DuckDB * * 因素 * CsvHelfer * SQLite * DuckDB * * 因素 * CsvHelfer * SQLite * DuckDB * |--------|-----------|--------|--------| | 内存 在导入可以在 | 设置设置设置设置设置设置设置 无 | 500500MB 文件 *~2GB RAM * 导入分钟 * 即时 * | 5GB 文件 * OOM 坠机 * * 非常慢 * * 工作顺利 * | 拼格 没有 没有 是 (10-100x更快) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

DuckDB 是 Python 中的数据工程师对这个使用大小写所使用的数据 。 . NET 约束 给予您完整的 ADO. NET 支持 - 它感觉像任何其他数据库, 除了您正在查询文件 。

堆叠

  • 元件 * * 为何如此 * * * 为何如此 * * * 为何如此 * * * * * * * 部分为何如此 * * * * * * * * * * * 部分为何如此 * * * * Why this one * * * * * why this one * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |-----------|--------------| | 鸭鸭DDB 直接询问CSV,没有进口步骤 | Duck DD. 网 ADO. | 奥拉马 本地推论,无API键,无云 | 错数 任何尺度的现实测试数据 | qwen2.5-coder:7b 最大 SQL 精度为 7B 大小

关于安全问题的说明我们正在执行 LLM 生成 SQL。 这比任意代码更安全, 但仍然需要验证。 见 安保科 关于将概况转换成LLM接口和实施这些模式的CLI的更广泛讨论,请参看随附部分。 DataSummarizer: 快速本地数据分析.

项目设置

让我们创建一个样本工程。 安装 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) 。 DuckDDB 执行。验证步骤在执行前会捕捉语法错误。重试环会处理偶发错误。这种分离使系统既安全又准确。更详细、以 CLI 为中心的引用(包括配置文件第一解析和安全 SQL 执行限制)见 DataSummarizer: 快速本地数据分析.

第1步:用bogus生成测试数据

有CSV数据了吗? 跳跳到 步骤2:构建方案表背景.

在测试我们的LLM驱动的CSV分析器之前,我们需要分析数据。为了开发和测试,合成数据比真实数据要快:

  1. 规模测试 - 生成100K、1M或1 000M行,以不同大小校验性能
  2. 隐私隐私 - 没有在演示或截图中暴露真实客户/商业数据的风险
  3. 可复制 - 同一种子=同一数据,使虫虫可复制
  4. 边缘病例 - 控制分配(例如,5%的军力返回,具体日期范围)

什么是博格斯?

错数 是一个流行的假冒js 库的.NET 端口。 它生成真实的假数据 - 姓名、 地址、 电子邮件、 日期、 编号 - 在适当的本地支持下。 博格斯没有手工艺测试 CSV 文件或使用随机的垃圾数据, 而是提供您的数据 。 外观 实数 :

  • f.Name.FullName() "约翰史密斯"(不是"Asdf1234")
  • f.Internet.Email()[email protected]”(正确格式化)
  • f.Date.Between(start, end) 现实日期分布
  • f.Commerce.ProductName() "手工艺的葛兰地奶酪" (有趣,但可识别)

这很重要,因为现实的数据能帮助你发现随机字符串隐藏的问题 -- -- 怪异的格式、意想不到的聚合、日期处理中的边缘案例。

定义数据模型

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

Bogus使用流利的 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 (方 价) - 能够访问所有数据模块(名称、日期、随机等)的生成器实例
  • f.Random.Guid().ToString()[..8] - 生成一个 GUID, 但仅先取前 8 个字符作为可读顺序 ID
  • f.Date.Between() - 实际范围内的随机日期(不是9999年)
  • f.PickRandom(array) - 从预定义选项中随机选择( 确保有效类别)
  • f.Random.Bool(0.3f) - 30%的正确机会(30%的订单得到折扣)
  • (f, s) 语法 - 访问假冒和部分制作的记录。 ProductName 取决于 Category

缩略 (f, s) 这种一致性使得生成的数据更符合现实性, 用于测试“按类别分类的收入”等聚合物。

生成并写入 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 列生成大约 15MB CSV - 足以测试, 但您可以很容易的缩放到 百万 。 生成速度很快( 100K 列为 ~ 2 秒 ) , 因为 Bogus 被优化成批生成 。

提示提示: 设置 Randomizer.Seed = new Random(12345) 在生成数据前获取可复制的数据。相同的种子=每次相同的“随机”记录,这对调试非常宝贵。

步骤2:构建方案表背景

LLM 生成 SQL 之前, 它需要理解数据结构 。 我们从 DuckDB 提取此数据 :

上下文模式

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展示了在不浪费象征物的情况下可以期待的格式。

步骤3:与LLM生成 SQL

这是困难的部分。 这里的迅速工程是不可谈判的-没有严格的规则,当地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 相对于 TOP, 字符串连接) 防止常见错误 。

添加样本数据

    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有时会犯语法错误,

致电LLM

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

缩略 StreamToEndAsync() 等待完整回复。为了更好的 UX,您可以在标志到达时流传它们。

清理反应

LLMs经常用标记代码块包装SQL, 尽管他们被告知不要:

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:执行前验证

鸭式 DDB 的 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 并重试( 以限制为限 ) 。

步骤5:执行和格式结果

最后,运行查询并格式化输出:

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 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 9GB 9GB 中 中 中 中 中 中 中 中 中 中 中 中 9GB 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 中 奥拉马
codellama:7b 4GB 快速 ~好快 奥拉马
llama3.2:3b * 2GB * 非常快 * * 接受 * * * * * 接受 * * * * 接受 * * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * 接受 * * * 接受 * * 接受 * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * 接受 * * * * * 接受 * * * * * 接受 * * * * * * 接受 * * * * * * * 2GB * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 奥拉马

对于大多数使用的案件, qwen2.5-coder:7b 精确的 SQL, 速度良好, 用微小的硬件运行 (8GB+ RAM) 。

业绩 业绩业绩 业绩业绩

现实世界基准

在标准德夫机器上测试100K行 CSV 文件(14MB) (Ryzen 5, NVME SSD, 32GB RAM):

  • 查询类型 * 时间 * |------------|------| 简单 COUNT $ \(65 ms\) \(65 ms\) 与58-68ms集团 复杂集成与FILTER 63ms 由Having + 71ms组成的多表格组

用于100K行分析查询的子100米 - 没有任何导入步骤。 查询的复杂性比行数要重要得多; DuckDB 的列引擎能有效处理聚合, 不论文件大小 。

将大文件转换为 Parquet

对于超过1GB的文件, 拼格格式 速度为 10- 100x :

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

压缩的 Parquet 文件也小得多。

安全考虑

执行 LLM 生成 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 的模拟模式也提供自然隔离, 它不会影响您的生产数据库 。

完整示例

下面是所有事情的共同点:

// 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 理由;数据库计算。

不要将数据输入LLM。 喂它 schema, 让它生成 SQL, 用合适的查询引擎执行 SQL 。 分离就是这个方法在规模上起作用的原因 。

执行:

  1. 鸭鸭DDB 直接查询 CSV - 无导入, 磁盘流
  2. Schema+样本 给予 LLM 足够的上下文而不曝光数据
  3. 严格迅速规则 确定性 SQL, 不是创造性的流言
  4. 通过 EXPLAIN 验证 执行前的捕获错误
  5. 使用错误反馈重试 处理偶发语法滑块

结果:每100米子分析查询百万行文件,完全脱机,数据从不离开机器。

全部抽样项目可于下列时间提供: 最精密的 CsvLllm - 包括 CsvQueryService, ConversationalCsvAnalyser和基于 bogus 的数据生成 。

资源资源资源 资源资源资源 资源资源 资源资源

鸭鸭DDB

Ollama和LLMs

测试数据生成

提及的替代措施

相关条款

Finding related posts...
logo

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