<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arian Mokhtariha</title>
    <description>The latest articles on DEV Community by Arian Mokhtariha (@arian_mokhtariha_6206efac).</description>
    <link>https://dev.to/arian_mokhtariha_6206efac</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3961717%2F193a1aac-fb1c-484d-bf46-c7f092cae8fb.jpg</url>
      <title>DEV Community: Arian Mokhtariha</title>
      <link>https://dev.to/arian_mokhtariha_6206efac</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arian_mokhtariha_6206efac"/>
    <language>en</language>
    <item>
      <title>A CSV sample that looks like the whole dataset is worse than no CSV at all</title>
      <dc:creator>Arian Mokhtariha</dc:creator>
      <pubDate>Thu, 09 Jul 2026 01:42:55 +0000</pubDate>
      <link>https://dev.to/arian_mokhtariha_6206efac/a-csv-sample-that-looks-like-the-whole-dataset-is-worse-than-no-csv-at-all-3546</link>
      <guid>https://dev.to/arian_mokhtariha_6206efac/a-csv-sample-that-looks-like-the-whole-dataset-is-worse-than-no-csv-at-all-3546</guid>
      <description>&lt;p&gt;I've been building &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;data2prompt&lt;/a&gt; for a few months now. It takes a data-heavy project (CSVs, SQL dumps, notebooks, Excel files) and turns it into a single file an LLM can read, instead of the model choking on raw 200MB CSVs or a generic repo-packager just skipping them. This week I shipped v0.5.0, and the real work in that release wasn't a new parser or a flag. It was a document I wrote called &lt;code&gt;docs/output-contract.md&lt;/code&gt;, and writing it forced me to think about a failure mode I'd been quietly ignoring for months.&lt;/p&gt;

&lt;p&gt;Here's the problem. To fit a large table into a context window, data2prompt samples it, say, 15 random rows out of 1.2 million. That's necessary and fine. But once you hand that sample to an LLM without being extremely explicit about what it's looking at, the model will happily treat 15 rows as the dataset. It'll compute an "average" from them. It'll describe a "trend." It'll answer "how many customers churned" using a number that only exists because of your random seed. The sample doesn't just lose information, it actively invites the model to hallucinate as if it had everything.&lt;/p&gt;

&lt;p&gt;That's the actual design problem behind data2prompt: the output isn't documentation for a person, it's an input for a model, and models fail differently than people do. A person skimming a CSV preview instinctively knows it's a preview. A model doesn't have that instinct unless you build it in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "grounding" actually means in the code
&lt;/h2&gt;

&lt;p&gt;The rule I landed on: any time data gets reduced, the full count has to be captured &lt;em&gt;before&lt;/em&gt; the reduction happens, and it has to travel with the sample as a notice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;total_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# capture first
&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv_sample_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# -- [Sample: random 15 of 1,234,567 rows] --
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every sampled table, every truncated SQL insert block, every notebook cell whose output got cut off, all of it carries a line like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- [Sample: random 15 of 1,234,567 rows] --&lt;/span&gt;
&lt;span class="c1"&gt;-- [CSV truncated: Showing random 15 of 1,234,567 rows to save context] --&lt;/span&gt;
&lt;span class="c1"&gt;-- [Table data truncated: Showing random 15 of 200 buffered rows to save context] --&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same grammar every time: &lt;code&gt;-- [Category: detail] --&lt;/code&gt;. Not a &lt;code&gt;*Note:*&lt;/code&gt; in italics, no emoji, no "heads up!" One shape, always, on purpose. The system prompt at the top of the generated file teaches the model this exact pattern once, and after that the model can reliably tell "this line is the tool talking to me" apart from "this line is file content." Mix in even one differently-formatted note and that separation gets fuzzy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing partial is allowed to look complete
&lt;/h2&gt;

&lt;p&gt;This is the rule that actually matters, and it applies past just numeric sampling. A &lt;code&gt;.parquet&lt;/code&gt; file with no &lt;code&gt;pyarrow&lt;/code&gt; installed doesn't vanish. It shows up with &lt;code&gt;Skipped (No pyarrow)&lt;/code&gt; and an install command. An SQL table's schema-only mode still prints &lt;code&gt;CREATE TABLE&lt;/code&gt;, just with the rows replaced by &lt;code&gt;-- [N data row(s) omitted: schema-only] --&lt;/code&gt;. A file excluded by &lt;code&gt;.gitignore&lt;/code&gt; still gets a row in the File Index with status &lt;code&gt;Omitted&lt;/code&gt;. If a file was scanned, it's accounted for somewhere in the document, even if the answer is "not shown, here's why."&lt;/p&gt;

&lt;p&gt;The alternative is silently dropping things, which is how you get an LLM confidently telling you a project has no config file when it does. It just didn't make the cut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bugs this thinking actually caught
&lt;/h2&gt;

&lt;p&gt;Writing the contract wasn't just theory, it surfaced real bugs while I was auditing the codebase against it:&lt;/p&gt;

&lt;p&gt;The Excel visual-element check (are there charts or images in this sheet the tool can't extract) was reading &lt;code&gt;sheet._images&lt;/code&gt; off worksheets opened in &lt;code&gt;read_only=True&lt;/code&gt; mode. Read-only worksheets in openpyxl never parse drawing parts, and the attribute on regular worksheets is &lt;code&gt;_charts&lt;/code&gt;, not &lt;code&gt;charts&lt;/code&gt;, anyway. So that check could never fire. It always said "no visuals," even when a sheet was covered in charts. Fixed it by treating an &lt;code&gt;.xlsx&lt;/code&gt; as what it actually is, a zip file, and checking for &lt;code&gt;xl/media/&lt;/code&gt; and &lt;code&gt;xl/charts/&lt;/code&gt; in the archive listing directly. No workbook load needed, and now it's actually correct instead of silently wrong.&lt;/p&gt;

&lt;p&gt;The other one is worse in a different way: bare &lt;code&gt;.env&lt;/code&gt; files have an empty file suffix, so they were falling through the extension-based skip list and getting parsed by the generic text handler, which just dumps file contents. A tool built specifically to keep secrets out of your LLM context was leaking them because the routing was extension-based and &lt;code&gt;.env&lt;/code&gt; doesn't have an extension. Now &lt;code&gt;.env&lt;/code&gt; files (and &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;prod.env&lt;/code&gt;, etc.) are matched by name before extension dispatch even runs, and the parser only ever emits &lt;code&gt;KEY=&amp;lt;redacted&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Neither of these were found by "testing the feature." They were found by writing down what the system was supposed to guarantee and then checking the code against that sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I bothered writing a contract doc at all
&lt;/h2&gt;

&lt;p&gt;I'm a data scientist, not a software engineer by trade, and I build data2prompt by directing coding agents through the implementation rather than hand-writing every line myself. That workflow has a specific failure mode: without something written down, every session re-derives its own idea of "how should this notice be worded" or "does this new parser need to update the preamble too," and consistency erodes one plausible-looking PR at a time. The contract doc exists so that decision doesn't have to be re-litigated: format parity across markdown/xml, one notice grammar, one canonical path key, controlled vocabularies only, fixed anchors at the top and bottom of the document. Seven invariants, plus a checklist for each kind of change. Any session, human or agent, that touches the output code reads that first.&lt;/p&gt;

&lt;p&gt;If you're building anything that generates content for an LLM to consume rather than a human, not just data2prompt, any RAG pipeline, any agent tool output, I'd push back on treating "human-readable" and "model-readable" as the same design target. They're not. A human notices when something looks off. A model narrates whatever's in front of it as if it's the truth, unless you've engineered the document to stop it from doing that.&lt;/p&gt;

&lt;p&gt;Repo's here if you want to see the actual contract doc or poke at the code: &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;github.com/arianmokhtariha/data2prompt&lt;/a&gt;. It's MIT licensed, on PyPI as &lt;code&gt;data2prompt&lt;/code&gt;. Happy to talk through any of the tradeoffs above in the comments.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>llm</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop Pasting Raw CSVs Into ChatGPT: A Data Scientist's Guide to LLM Context Engineering</title>
      <dc:creator>Arian Mokhtariha</dc:creator>
      <pubDate>Wed, 24 Jun 2026 20:28:00 +0000</pubDate>
      <link>https://dev.to/arian_mokhtariha_6206efac/stop-pasting-raw-csvs-into-chatgpt-a-data-scientists-guide-to-llm-context-engineering-5846</link>
      <guid>https://dev.to/arian_mokhtariha_6206efac/stop-pasting-raw-csvs-into-chatgpt-a-data-scientists-guide-to-llm-context-engineering-5846</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your LLM doesn't need 50,000 rows. It needs the right 15.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;There's a mistake I see data scientists make constantly when they first start using LLMs for analysis.&lt;/p&gt;

&lt;p&gt;They paste their entire CSV into the prompt.&lt;/p&gt;

&lt;p&gt;I get the instinct. It feels rigorous. The model should have &lt;em&gt;everything&lt;/em&gt;, right? But that instinct is exactly backwards — and it's silently degrading your results in ways that are easy to miss.&lt;/p&gt;

&lt;p&gt;Let me show you why, and walk through a different approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Raw Data Destroys LLM Performance
&lt;/h2&gt;

&lt;p&gt;Large language models have two constraints that matter deeply for data work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context windows are finite.&lt;/strong&gt; A 100-row CSV with 20 columns? Probably fine. A 10,000-row CSV? That's millions of characters. You've burned your entire context window on one file before you've even written your question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More tokens ≠ better answers.&lt;/strong&gt; This is the counterintuitive part. LLM attention degrades with noisy, repetitive input. Row 8,437 of your sales data looks structurally identical to row 4,291. The model doesn't need both — it needs to understand the &lt;em&gt;pattern&lt;/em&gt;, not memorize every instance.&lt;/p&gt;

&lt;p&gt;Dumping raw data into a prompt is the equivalent of handing someone a 500-page report and asking them to summarize it verbally on the spot. They'll struggle, and the important details will get lost in the noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  What LLMs Actually Need From Your Data
&lt;/h2&gt;

&lt;p&gt;For data analysis tasks, a well-structured LLM context needs four things — and only four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; — column names, data types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A representative sample&lt;/strong&gt; — enough rows to understand patterns and edge cases (15–50 is usually enough)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistics computed on the full dataset&lt;/strong&gt; — missing value counts, value distributions, describe() output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; — how files relate to each other in your project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice what's &lt;em&gt;not&lt;/em&gt; on that list: all 50,000 rows.&lt;/p&gt;

&lt;p&gt;Here's the key insight: if your statistics are computed on the full dataset, you don't need the full dataset in the prompt. The model knows the mean, the standard deviation, the missing value rate, the quartiles — all from the full 50K rows — without seeing any of them directly.&lt;/p&gt;

&lt;p&gt;The sample is there to show the model what the data &lt;em&gt;looks like&lt;/em&gt;. The statistics tell it the truth about what the data &lt;em&gt;actually is&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Instead of pasting your entire CSV, you want something like this in your prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## File: sales_data.csv&lt;/span&gt;
[Shape: 52,341 rows × 18 columns | Sampled: 15 random rows]

| order_id | order_date | region | category | sales | discount | profit |
|----------|------------|--------|----------|-------|----------|--------|
| CA-2021-... | 2021-03-12 | West | Technology | 1249.00 | 0.20 | 312.25 |
| ... [13 more rows] ...

&lt;span class="gu"&gt;### Dataset Statistics (full dataset: 52,341 rows)&lt;/span&gt;
Columns: order_id (object), order_date (datetime64), region (object), ...
Missing values: discount: 12.3% (6,438), postal_code: 0.1% (52)
Numerical summary:
  sales: min=0.44  mean=229.86  max=22638.48  std=623.25
  profit: min=-6599.98  mean=28.66  max=8399.98  std=234.26
  discount: min=0.0  mean=0.15  max=0.80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's one file in your context. Now imagine a full project: 3 CSVs, 2 SQL dumps, a Jupyter analysis notebook, an Excel summary. Each one needs this treatment.&lt;/p&gt;

&lt;p&gt;And they all need to be assembled into a &lt;em&gt;single coherent context file&lt;/em&gt; that fits in one prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automating This With data2prompt
&lt;/h2&gt;

&lt;p&gt;This is exactly the problem I built &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;data2prompt&lt;/a&gt; to solve. It runs in your project directory and produces a single structured &lt;code&gt;PROMPT.md&lt;/code&gt; (or &lt;code&gt;.xml&lt;/code&gt;) file ready to paste into any LLM session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt
&lt;span class="nb"&gt;cd &lt;/span&gt;your-data-project
data2prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does under the hood:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For CSV files:&lt;/strong&gt; Draws a random sample of 15 rows (configurable), then computes the full stats block — dtype per column, missing value counts and percentages, and a &lt;code&gt;describe()&lt;/code&gt; summary — on the &lt;em&gt;entire&lt;/em&gt; file. Not the sample.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Jupyter notebooks:&lt;/strong&gt; Extracts code cells and text outputs in execution order. Strips Base64-encoded images and raw HTML that would waste thousands of tokens while contributing nothing to analysis context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For SQL files:&lt;/strong&gt; Applies intelligent sampling to SELECT-able content and surfaces schema structure for DDL statements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Excel files:&lt;/strong&gt; Processes each sheet separately with the same stats-aware approach, up to a configurable sheet limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For .env files:&lt;/strong&gt; Lists variable names with values redacted (&lt;code&gt;SECRET_KEY=&amp;lt;redacted&amp;gt;&lt;/code&gt;). The LLM understands your configuration without you leaking credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Size Difference Is Dramatic
&lt;/h2&gt;

&lt;p&gt;I ran this on a real Superstore analytics project — multiple CSVs, SQL dumps, a Jupyter analysis notebook, and Excel summaries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Output Size&lt;/th&gt;
&lt;th&gt;Data Handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;data2prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;241 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Smart sampling + full-dataset stats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code2prompt&lt;/td&gt;
&lt;td&gt;9,304 KB&lt;/td&gt;
&lt;td&gt;Raw file content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repomix&lt;/td&gt;
&lt;td&gt;22,085 KB&lt;/td&gt;
&lt;td&gt;Raw file content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same project. 91× smaller than Repomix while preserving everything the LLM actually needs.&lt;/p&gt;

&lt;p&gt;But the real win isn't the file size — it's that the LLM now gets &lt;em&gt;better signal&lt;/em&gt; in fewer tokens. Less noise, cleaner attention, more focused responses.&lt;/p&gt;




&lt;h2&gt;
  
  
  How This Changes What You Can Ask
&lt;/h2&gt;

&lt;p&gt;When your context is structured right, your prompts get dramatically more specific and the answers get dramatically better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (raw data dump):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's my data [paste 5,000 rows]. Can you find any interesting patterns?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;After (structured context):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's my project context [paste PROMPT.md]. I'm seeing an unusual spike in returns in the West region in Q3. The discount column has 12.3% missing values — mostly concentrated in the Furniture category. Can you form a hypothesis about what's driving the return spike and suggest which columns to cross-tabulate to test it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second prompt is possible because you &lt;em&gt;already know&lt;/em&gt; the missing value distribution, the regional breakdown, and the data quality issues. data2prompt surfaces all of that automatically, so you can skip the exploratory small talk and go straight to the interesting question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema-Only Mode for Exploration
&lt;/h2&gt;

&lt;p&gt;When you're starting with a new project and don't yet know what to ask, there's a lighter option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;data2prompt &lt;span class="nt"&gt;--schema-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This drops all data rows and gives you just column names, types, and statistics. Useful for a first conversation with an LLM where you want to explore structure before committing to an analysis direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context Engineering Is Now a Core Skill
&lt;/h2&gt;

&lt;p&gt;The data science community talks a lot about prompt engineering — how to phrase questions to get better answers. But for data-heavy work, the bigger leverage is in &lt;em&gt;context engineering&lt;/em&gt;: how you structure and size the information you hand the model before you ask anything.&lt;/p&gt;

&lt;p&gt;The gains I've seen from better context far outweigh the gains from better phrasing. Same model, same question, 10× more specific answer — just by giving it structured context instead of raw rows.&lt;/p&gt;

&lt;p&gt;data2prompt is &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; and installable via PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's MIT-licensed, has no external API calls, and works entirely offline.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How are you currently preparing data context for LLM sessions? Curious whether others have found different approaches — drop it in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;https://github.com/arianmokhtariha/data2prompt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/data2prompt" rel="noopener noreferrer"&gt;https://pypi.org/project/data2prompt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;ai&lt;/code&gt; &lt;code&gt;datascience&lt;/code&gt; &lt;code&gt;python&lt;/code&gt; &lt;code&gt;llm&lt;/code&gt; &lt;code&gt;machinelearning&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>database</category>
      <category>data</category>
    </item>
    <item>
      <title>Meet data2prompt: The CLI Tool That Finally Makes LLMs Understand Your Data Science Projects</title>
      <dc:creator>Arian Mokhtariha</dc:creator>
      <pubDate>Tue, 02 Jun 2026 15:25:43 +0000</pubDate>
      <link>https://dev.to/arian_mokhtariha_6206efac/meet-data2prompt-the-cli-tool-that-finally-makes-llms-understand-your-data-science-projects-lda</link>
      <guid>https://dev.to/arian_mokhtariha_6206efac/meet-data2prompt-the-cli-tool-that-finally-makes-llms-understand-your-data-science-projects-lda</guid>
      <description>&lt;p&gt;Every data scientist has hit this wall.&lt;/p&gt;

&lt;p&gt;You are deep in a project — CSVs, SQL dumps, Jupyter notebooks, maybe some Power BI files — and you want to ask an LLM to help you reason across the whole thing. Not just one script. The entire project. You want it to understand your data structure, your pipeline logic, your model decisions all at once.&lt;/p&gt;

&lt;p&gt;So you try to package it up and paste it in. And it fails. The context window chokes. The LLM forgets files it saw earlier. The responses stop making sense.&lt;/p&gt;

&lt;p&gt;The problem is not your LLM. The problem is that nobody built the right packaging tool for data-heavy projects — until now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing data2prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;data2prompt&lt;/strong&gt; is an open-source CLI tool that packages your entire data science project into a single, optimized, LLM-ready file. Not a generic dump of everything in your folder — a smart, data-aware output that knows how to handle CSVs, SQL, Jupyter notebooks, Excel files, and binary data files the way a data scientist actually needs them handled.&lt;/p&gt;

&lt;p&gt;Install it in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;data2prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. You get a single &lt;code&gt;PROMPT.md&lt;/code&gt; or &lt;code&gt;PROMPT.xml&lt;/code&gt; file ready to paste into Claude, ChatGPT, Gemini, or any LLM with a large context window.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Generic Tools
&lt;/h2&gt;

&lt;p&gt;There are great tools out there for packaging software projects for LLM context. They work beautifully on codebases full of Python scripts and config files.&lt;/p&gt;

&lt;p&gt;But a data science project is not a software project. It contains fundamentally different file types that need fundamentally different handling — and when a generic tool encounters them, it does the worst possible thing: it dumps everything raw.&lt;/p&gt;

&lt;p&gt;Here is what that looks like in practice:&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;CSV with 50,000 rows&lt;/strong&gt; gets written to the output in full — 50,000 rows of token-consuming noise when what the LLM actually needs is the schema and a representative sample.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;SQL dump&lt;/strong&gt; gets included with every single INSERT statement — hundreds of thousands of rows of raw data when what the LLM needs is the CREATE TABLE schema and a handful of example rows per table.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Jupyter notebook&lt;/strong&gt; gets written with all its outputs intact — which includes matplotlib charts and styled dataframes stored as base64-encoded image strings. A single notebook visualization can contribute 60,000 tokens of encoded image data that an LLM literally cannot read or use.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Power BI or pickle file&lt;/strong&gt; is binary — reading it as text produces corrupted garbage that fills your context window with meaningless characters.&lt;/p&gt;

&lt;p&gt;The result is a context file that is technically complete and practically useless. Every token budget gets consumed before the LLM has seen anything worth reasoning about.&lt;/p&gt;




&lt;h2&gt;
  
  
  How data2prompt Handles Each File Type
&lt;/h2&gt;

&lt;p&gt;data2prompt applies a dedicated strategy to every file type found in a data project.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSVs and Excel — Intelligent Random Sampling
&lt;/h3&gt;

&lt;p&gt;Instead of dumping all rows, data2prompt takes a random sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default: 15 random rows per CSV&lt;/span&gt;
data2prompt

&lt;span class="c"&gt;# Increase sample size when you need more context&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--csv-sample-size&lt;/span&gt; 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sampling is &lt;strong&gt;true random&lt;/strong&gt; with a fixed seed — not head/tail. This means the LLM sees a representative spread of your actual data values, not just whatever happened to be at the top of the file. The seed makes the output fully reproducible — same result every time you run it.&lt;/p&gt;

&lt;p&gt;The output is clean and annotated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;-- [Sample - Random 15 rows of 52,411 total] --
| order_id | customer   | region | sales  | profit |
|----------|------------|--------|--------|--------|
| CA-2019  | John Smith | West   | 245.00 | 41.65  |
...
-- [CSV truncated: Showing random 15 rows to save context] --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Excel, each sheet is sampled independently. Sheets that are purely visual dashboards — no tabular data — are detected and noted rather than producing empty tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Files — Schema First, Data Sampled
&lt;/h3&gt;

&lt;p&gt;SQL dumps have two distinct parts: the schema (CREATE TABLE definitions) and the data (INSERT statements). data2prompt treats them completely differently.&lt;/p&gt;

&lt;p&gt;Schema blocks are always preserved in full. Every CREATE TABLE, every column definition, every constraint and foreign key relationship — this is exactly what the LLM needs to understand your database.&lt;/p&gt;

&lt;p&gt;INSERT statements are sampled randomly per table. You get a handful of representative rows for each table, enough to understand what the data looks like, without the thousands of repetitive INSERT lines that collapse your context budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jupyter Notebooks — Logic Without the Noise
&lt;/h3&gt;

&lt;p&gt;Notebooks store both cell source code and cell outputs. The source code is what the LLM needs — your transformations, model definitions, evaluation logic, markdown explanations. The outputs are the problem.&lt;/p&gt;

&lt;p&gt;data2prompt extracts source code from every cell and discards outputs entirely. The base64 image strings, printed dataframes, and error tracebacks that bloat notebook files are stripped out. What remains is clean, readable notebook logic that an LLM can actually reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binary Files — Acknowledged, Not Mangled
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.pbix&lt;/code&gt;, &lt;code&gt;.pkl&lt;/code&gt;, &lt;code&gt;.parquet&lt;/code&gt;, &lt;code&gt;.db&lt;/code&gt;, &lt;code&gt;.sqlite&lt;/code&gt;, &lt;code&gt;.h5&lt;/code&gt;, &lt;code&gt;.feather&lt;/code&gt; — data2prompt lists these in your project tree so the LLM knows they exist, and skips their content entirely. No corrupted binary strings eating your context window.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Output Formats: Markdown and XML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clean Markdown (default)&lt;/span&gt;
data2prompt

&lt;span class="c"&gt;# Structured XML&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--format&lt;/span&gt; xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The XML format was added based on Anthropic's research showing that XML-style structured tags improve LLM attention and parsing within long context windows. Every file gets semantic tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;codebase&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"my-project"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;directory_structure&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/directory_structure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;files&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"data\sales.csv"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...sampled table...
    &lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"notebooks\analysis.ipynb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;index=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;content&amp;gt;&lt;/span&gt;
          df = pd.read_csv('data/sales.csv')
          df.head()
        &lt;span class="nt"&gt;&amp;lt;/content&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/files&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/codebase&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use Markdown for quick analysis sessions. Use XML when you want the LLM to navigate a large project with maximum structural clarity.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;Same data science project — dimension and fact CSVs, a SQL dump, Power BI files, ML notebooks, classification and clustering scripts — run through three tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Output Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generic tool #1&lt;/td&gt;
&lt;td&gt;22,085 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic tool #2&lt;/td&gt;
&lt;td&gt;9,304 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;data2prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;241 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;241 KB versus 22,085 KB.&lt;/strong&gt; Same project. Same information that actually matters.&lt;/p&gt;

&lt;p&gt;That gap exists entirely because of the file-type-specific strategies above — the random CSV sampling, the SQL schema extraction, the notebook output stripping, the binary file handling. Nothing clever, just the right tool for the right files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (pipx recommended for isolated environment)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt
pipx &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt

&lt;span class="c"&gt;# Basic run — outputs PROMPT.md in project root&lt;/span&gt;
data2prompt

&lt;span class="c"&gt;# XML format&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--format&lt;/span&gt; xml

&lt;span class="c"&gt;# Custom CSV sample size&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--csv-sample-size&lt;/span&gt; 25

&lt;span class="c"&gt;# Ignore specific folders&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--ignore-folders&lt;/span&gt; data/raw archive models/checkpoints

&lt;span class="c"&gt;# Custom output filename&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--output&lt;/span&gt; my_project_context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.data2promptignore&lt;/code&gt; file in your project root for permanent exclusions — same syntax as &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/raw/
*.pkl
archive/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Who This Is Built For
&lt;/h2&gt;

&lt;p&gt;data2prompt is specifically designed for &lt;strong&gt;data scientists, data analysts, and data engineers&lt;/strong&gt; who work with real data files alongside their code. If your project has CSVs, SQL, notebooks, or Excel files, this tool will dramatically improve the quality of your LLM interactions with that project.&lt;/p&gt;

&lt;p&gt;If you work in a pure software development context with no data files, a general-purpose packaging tool will serve you well. But if your project looks anything like a real data science workflow, give data2prompt a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;https://github.com/arianmokhtariha/data2prompt&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/data2prompt" rel="noopener noreferrer"&gt;https://pypi.org/project/data2prompt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open to questions, feedback, and contributions — drop them in the comments or open an issue on GitHub.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dataengineering</category>
      <category>datascience</category>
      <category>llm</category>
    </item>
    <item>
      <title>I Tried Repomix on My Data Science Project. It Generated a 22,000 KB File. So I Built My Own Tool</title>
      <dc:creator>Arian Mokhtariha</dc:creator>
      <pubDate>Sun, 31 May 2026 22:31:28 +0000</pubDate>
      <link>https://dev.to/arian_mokhtariha_6206efac/i-tried-repomix-on-my-data-science-project-it-generated-a-22000-kb-file-so-i-built-my-own-tool-58j7</link>
      <guid>https://dev.to/arian_mokhtariha_6206efac/i-tried-repomix-on-my-data-science-project-it-generated-a-22000-kb-file-so-i-built-my-own-tool-58j7</guid>
      <description>&lt;p&gt;A few months ago a friend showed me two tools — Repomix and code2prompt. The idea was simple: point them at your project folder, they package everything into one file, you paste it into an LLM and ask questions about your whole codebase at once. For his pure Python projects they worked great.&lt;/p&gt;

&lt;p&gt;I was working on a data analytics project at the time — dimension and fact CSVs, a SQL dump, some Power BI files, Jupyter notebooks with ML models. I ran Repomix on it and got a 22,085 KB output file. code2prompt gave me 9,304 KB. I tried pasting either of them into Claude. It choked immediately.&lt;/p&gt;

&lt;p&gt;So I opened the files to see what was actually inside them. What I found was the root of the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  What These Tools Get Wrong for Data Projects
&lt;/h2&gt;

&lt;p&gt;Repomix and code2prompt are built for &lt;em&gt;code&lt;/em&gt; repos. They operate on a simple principle: read every file, dump every file. That works fine when your project is Python scripts and config files. It completely falls apart when your project looks like mine.&lt;/p&gt;

&lt;p&gt;Here's what was inflating those files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw CSV dumps.&lt;/strong&gt; My Fact_Sales.csv had tens of thousands of rows. The tool dumped every single one. An LLM doesn't need 50,000 rows of sales data — it needs to understand the structure and a representative sample.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endless SQL INSERT statements.&lt;/strong&gt; My Superstore.sql file had the full database dump including every INSERT INTO statement for every table. The schema — the CREATE TABLE blocks — is what an LLM actually needs. The data rows are mostly noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notebook outputs with base64 images.&lt;/strong&gt; Jupyter notebooks store cell outputs as JSON inside the .ipynb file. When a cell generates a matplotlib chart, that chart gets saved as a base64-encoded image string inside the notebook. A single chart output can be 50,000+ characters of base64 garbage that an LLM cannot use at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary files read as text.&lt;/strong&gt; My .pbix (Power BI) files are binary. These tools attempted to read them as text and produced corrupted garbage that consumed tokens while providing zero information.&lt;/p&gt;

&lt;p&gt;There was no tool that understood these problems. So after three months of building — and being very honest that I'm a data scientist not a developer, so this was heavily AI-assisted — I shipped data2prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What data2prompt Does Differently
&lt;/h2&gt;

&lt;p&gt;The core idea is that each file type in a data project needs its own strategy, not a generic "read and dump" approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSVs and Excel: Smart Sampling
&lt;/h3&gt;

&lt;p&gt;Instead of dumping all rows, data2prompt takes a random sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is 15 rows, configurable with &lt;code&gt;--csv-sample-size&lt;/code&gt;. Critically it's &lt;strong&gt;random&lt;/strong&gt; sampling with a fixed seed — not head/tail. Random sampling gives the LLM a more representative picture of value diversity across the dataset. The seed (default 42) makes output reproducible across runs.&lt;/p&gt;

&lt;p&gt;The output tells the LLM exactly what it's looking at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- [Sample - Random 15 rows] --
| order_id | customer_name | sales  | profit |
|----------|--------------|--------|--------|
| CA-2019  | John Smith   | 245.00 | 41.65  |
...
-- [CSV truncated: Showing random 15 rows to save context] --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Excel files, each sheet is sampled independently. The parser also detects sheets that are purely visual dashboards (charts, images only) and notes them rather than producing an empty table.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Files: Schema Preserved, Data Sampled
&lt;/h3&gt;

&lt;p&gt;This was the hardest parser to get right. SQL dump files typically follow a pattern: CREATE TABLE block defining the schema, followed by hundreds or thousands of INSERT INTO statements loading the data.&lt;/p&gt;

&lt;p&gt;data2prompt reads line by line and applies different logic to each part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Always preserve the schema
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line_upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;flush_buffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;in_create_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;processed_lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Buffer INSERT rows for sampling
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_insert&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;is_data_row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;table_data_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it hits a buffer of INSERT rows, it samples them randomly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rest_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_data_buffer&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;sample_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sampled_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_line&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_data_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rest_indices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line (the INSERT header) is always preserved. The rest are random samples in their original order. The LLM gets the full schema of every table plus a representative data sample — which is exactly what it needs to understand your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jupyter Notebooks: Source Code Only
&lt;/h3&gt;

&lt;p&gt;Notebook cells store three things: source code, execution count, and outputs. The outputs are what bloat the file — printed dataframes, matplotlib charts as base64, error tracebacks.&lt;/p&gt;

&lt;p&gt;data2prompt keeps the source code of every cell and strips the outputs entirely. A notebook that was 8MB of JSON becomes a clean sequence of code cells. The parser also handles truncation of unusually long lines and caps output blocks at a configurable line limit for cases where outputs are genuinely useful text.&lt;/p&gt;

&lt;p&gt;For the XML format, each cell becomes a structured block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"ML/Q1/Q1.ipynb"&lt;/span&gt; &lt;span class="na"&gt;index=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;content&amp;gt;&lt;/span&gt;
model = XGBRegressor(n_estimators=100, learning_rate=0.1)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
    &lt;span class="nt"&gt;&amp;lt;/content&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Binary Files: Listed, Not Read
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.pbix&lt;/code&gt;, &lt;code&gt;.parquet&lt;/code&gt;, &lt;code&gt;.pkl&lt;/code&gt;, &lt;code&gt;.db&lt;/code&gt;, &lt;code&gt;.sqlite&lt;/code&gt;, &lt;code&gt;.feather&lt;/code&gt;, &lt;code&gt;.h5&lt;/code&gt; — all listed in the directory tree so the LLM knows they exist, but content is skipped entirely. No garbage bytes consuming your context window.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Output Formats: Markdown and XML
&lt;/h2&gt;

&lt;p&gt;data2prompt supports both &lt;code&gt;--format markdown&lt;/code&gt; (default) and &lt;code&gt;--format xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The XML format was added after Anthropic published research showing that XML-style tags improve LLM attention and parsing. The full project gets wrapped in a structured hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;codebase&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"superstore-analysis"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;metadata&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;generated_on&amp;gt;&lt;/span&gt;2025-05-31 09:00&lt;span class="nt"&gt;&amp;lt;/generated_on&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;total_tokens&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"o200k_base"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;48293&lt;span class="nt"&gt;&amp;lt;/total_tokens&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/metadata&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;directory_structure&amp;gt;&lt;/span&gt;
    Fact&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;dim-csv\Fact_Sales.csv
    Superstore.sql
    ...
  &lt;span class="nt"&gt;&amp;lt;/directory_structure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;files&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;file&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"Fact&amp;amp;dim-csv\Fact_Sales.csv"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...sampled table...
    &lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/files&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/codebase&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Benchmark
&lt;/h2&gt;

&lt;p&gt;Same project, same files, three tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Output Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repomix&lt;/td&gt;
&lt;td&gt;22,085 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code2prompt&lt;/td&gt;
&lt;td&gt;9,304 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;data2prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;241 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a 98.9% reduction vs Repomix and 97.4% vs code2prompt on the same data-heavy project, while preserving all the structurally useful information — schemas, sampled data, notebook logic, file tree.&lt;/p&gt;

&lt;p&gt;The reduction is so dramatic specifically because of the project type. A pure Python project would show a much smaller gap. That's exactly the point — this tool is built for data projects, not code projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation and Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt

&lt;span class="c"&gt;# Recommended: use pipx for isolated install&lt;/span&gt;
pipx &lt;span class="nb"&gt;install &lt;/span&gt;data2prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basic usage — run inside your project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default: Markdown output&lt;/span&gt;
data2prompt

&lt;span class="c"&gt;# XML output (better for LLM structured parsing)&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--format&lt;/span&gt; xml

&lt;span class="c"&gt;# Increase CSV sample size&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--csv-sample-size&lt;/span&gt; 25

&lt;span class="c"&gt;# Custom output file name&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--output&lt;/span&gt; my_project_context

&lt;span class="c"&gt;# Ignore specific folders&lt;/span&gt;
data2prompt &lt;span class="nt"&gt;--ignore-folders&lt;/span&gt; data/raw 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output file (default: &lt;code&gt;PROMPT.md&lt;/code&gt; or &lt;code&gt;PROMPT.xml&lt;/code&gt;) is ready to paste directly into Claude, ChatGPT, Gemini, or any LLM with a large context window.&lt;/p&gt;

&lt;p&gt;You can also create a &lt;code&gt;.data2promptignore&lt;/code&gt; file in your project root — same syntax as &lt;code&gt;.gitignore&lt;/code&gt; — to exclude specific files or patterns permanently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;data2prompt is specifically designed for data scientists, data analysts, and data engineers who work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV/Excel data files&lt;/li&gt;
&lt;li&gt;SQL database dumps&lt;/li&gt;
&lt;li&gt;Jupyter notebooks&lt;/li&gt;
&lt;li&gt;Power BI or other binary analytics files&lt;/li&gt;
&lt;li&gt;Mixed projects with both code and data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your project is purely Python scripts with no data files, Repomix or code2prompt will serve you fine. But if your project looks anything like a real data science workflow, give data2prompt a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/arianmokhtariha/data2prompt" rel="noopener noreferrer"&gt;https://github.com/arianmokhtariha/data2prompt&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/data2prompt" rel="noopener noreferrer"&gt;https://pypi.org/project/data2prompt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions about how the SQL parser works or why random sampling over head/tail? Drop them in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>code2prompt</category>
      <category>repomix</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
