LLM-powered CSV enrichment made easy
Project description
RicherText
Turn raw CSV data into enriched insights using LLMs. Classify, label, score, summarize, and analyze your data at scale.
Before: After:
┌─────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐
│ title │ description │ │ title │ seniority │ department │ skills │
├─────────────────────────────┤ → ├─────────────────────────────────────────────────────────┤
│ Sr Engineer │ Python, AWS │ │ Sr Engineer │ senior │ engineering│ python;aws │
│ Sales Rep │ Outbound... │ │ Sales Rep │ entry │ sales │ communication │
└─────────────────────────────┘ └─────────────────────────────────────────────────────────┘
100 records × 8 enrichments = 800 LLM calls in ~8 seconds with automatic rate limiting and resume capability.
Quick Start
1. Install
pip install richertext
2. Set your API key
export GOOGLE_API_KEY="your-gemini-api-key"
# Or add to .env file (auto-loaded by richertext)
echo 'GOOGLE_API_KEY=your-gemini-api-key' >> .env
Get a free API key at Google AI Studio.
3. Initialize and run
richertext init my-project
cd my-project
richertext run input/job_postings_10.csv --config taskflows/job_postings_tasks.yaml -v
That's it. Check output/job_postings_10_enriched.csv for results.
What You Get
The init command creates a complete working example:
my-project/
├── .env # Your API key goes here
├── taskflows/
│ └── job_postings_tasks.yaml # Enrichment configuration
├── prompts/
│ └── job_postings_prompts.yaml # Prompt templates
├── input/
│ ├── job_postings_10.csv # 10 sample records
│ └── job_postings_100.csv # 100 sample records
├── output/ # Enriched results
└── docs/ # Documentation
├── README.md
├── TUTORIAL.md
└── ENRICHMENTS.md
The sample taskflow enriches job postings with:
| Enrichment | Type | Output |
|---|---|---|
| seniority | classifier | entry, mid, senior, executive |
| department | classifier | engineering, sales, hr, etc. |
| skills | labeler | python;aws;sql (multiple) |
| experience_required | scorer | 7 (1-10 scale) |
| technical_complexity | scorer | 8 (1-10 scale) |
| salary_estimate | scorer | 6 (1-10 scale) |
| candidate_profile | summarizer | "Ideal candidate is..." |
| remote_friendly | classifier | remote, hybrid, onsite, unclear (inline prompt) |
Use Cases
- Lead scoring - Classify prospects by fit, urgency, budget
- Customer feedback - Sentiment, topics, urgency detection
- Content categorization - Auto-tag articles, support tickets, documents
- Resume screening - Skills extraction, experience level, role fit
- Product reviews - Sentiment, feature mentions, competitor comparisons
- Survey analysis - Theme extraction, satisfaction scoring
Configuration
Taskflow Structure
# taskflows/job_postings_tasks.yaml
provider:
type: gemini
model: gemini-2.5-flash-lite # Fast and cheap
prompts_file: ../prompts/job_postings_prompts.yaml # External prompts
enrichments:
- name: sentiment
type: classifier
prompt_key: sentiment_prompt # References job_postings_prompts.yaml
categories: [positive, negative, neutral]
input_columns: [review_text]
- name: themes
type: labeler
labels: [pricing, quality, support, shipping]
input_columns: [review_text]
prompt: | # Or inline prompt
What themes appear in this review?
{review_text}
Enrichment Types
| Type | What it does | Output |
|---|---|---|
classifier |
Pick ONE category | sentiment_category |
labeler |
Pick MULTIPLE labels | themes_labels (semicolon-separated) |
summarizer |
Condense text | summary_summary |
scorer |
Rate on a scale (1-10) | quality_score |
reasoner |
Free-form analysis | analysis_analysis |
Prompts File
# prompts/job_postings_prompts.yaml
sentiment_prompt: |
Classify the sentiment of this review:
{review_text}
summary_prompt: |
Summarize in 1-2 sentences:
{review_text}
Use {column_name} to inject CSV columns into prompts.
Python API
For programmatic use without config files:
from richertext import classify, summarize, score, label, reason
# Single-record functions
result = classify("I love this product!", categories=["positive", "negative"])
print(result["category"]) # "positive"
summary = summarize("Long article text here...", max_length=100)
clarity = score("Evaluate this essay", prompt="Rate the clarity of: {text}")
# 8
labels = label("Tech news about AI", labels=["tech", "politics", "sports"])
# {"labels": ["tech"]}
analysis = reason("Q3 revenue: $2.1M, down 5% YoY")
# Free-form analysis text
Batch Processing
from richertext import enrich_csv, enrich_records
# From CSV file
enrich_csv("reviews.csv", config="taskflows/sentiment.yaml")
# From list of dicts (custom loading)
records = [
{"id": 1, "text": "Great product!"},
{"id": 2, "text": "Terrible experience"},
]
enriched = enrich_records(records, config="taskflows/sentiment.yaml")
# With resume capability (restart interrupted jobs)
enriched = enrich_records(
records,
config="taskflows/sentiment.yaml",
output_path="output/enriched.csv",
pk_field="id" # Skip already-processed records
)
Features
Automatic Rate Limiting
Built-in token bucket rate limiting respects Gemini API limits:
| Model | Limit | Burst |
|---|---|---|
| gemini-2.5-flash-lite | 4,000 RPM | 360 req |
| gemini-2.5-flash | 1,000 RPM | 90 req |
| gemini-2.5-pro | 150 RPM | 13 req |
No 429 errors. No manual throttling.
Parallel Processing
Worker count is automatically calculated based on the model's rate limit:
| Model | Rate Limit | Default Workers |
|---|---|---|
| gemini-2.5-flash-lite | 4,000 RPM | 133 workers |
| gemini-2.5-flash | 1,000 RPM | 33 workers |
| gemini-2.5-pro | 150 RPM | 5 workers |
Override with --workers if needed:
richertext run data.csv --config job_postings_tasks.yaml --workers 50
Resume Capability
Interrupted? Just re-run the same command. RicherText reads the output file, extracts the --pk-field values already processed, and skips those records:
# First run - processes 50 records, then interrupted (Ctrl+C)
richertext run data.csv --config job_postings_tasks.yaml --pk-field id
# Output: output/data_enriched.csv (50 rows)
# Second run - reads output file, skips 50 already done, continues with remaining
richertext run data.csv --config job_postings_tasks.yaml --pk-field id
# Output: appends to output/data_enriched.csv
The --pk-field must match a column in your CSV (default: id).
Chaining Enrichments
You can reference output from earlier enrichments in later ones. This is not automatic - you must explicitly include the output column in input_columns:
enrichments:
- name: language
type: classifier
categories: [english, spanish, french]
input_columns: [text]
output_column: detected_language # Creates 'detected_language' column
- name: summary
type: summarizer
input_columns: [text, detected_language] # Explicitly include previous output
prompt: |
Summarize this {detected_language} text:
{text}
CLI Reference
# Initialize new project
richertext init [project_name]
# Run enrichment
richertext run <input.csv> --config <taskflow.yaml> [options]
Options:
-c, --config FILE Taskflow config (required)
-o, --output FILE Output path (default: output/<input>_enriched.csv)
-v, --verbose Show progress
-w, --workers N Parallel workers (default: auto based on model)
--pk-field FIELD Primary key for resume (default: id)
Troubleshooting
"No API key found"
export GOOGLE_API_KEY="your-key"
# Or add to .env file
"Prompts file not found"
- Path is relative to the taskflow location
- Use
../prompts/job_postings_prompts.yamlif prompts dir is sibling to taskflows
Rate limit errors
- The built-in rate limiter should prevent these
- If you see 429s, reduce
--workers
Resuming doesn't skip records
- Make sure
--pk-fieldmatches a column in your CSV - Check that output file exists from previous run
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file richertext-0.1.1.tar.gz.
File metadata
- Download URL: richertext-0.1.1.tar.gz
- Upload date:
- Size: 43.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0369c716308e224121101105c359a83498dc8a57d7769beede4269a9b050c9d
|
|
| MD5 |
b551066f0129adeb6cf342a169cbdea9
|
|
| BLAKE2b-256 |
76a44d7ca705799c1e4078fab8daa29cbf76eaa7b4f4f1631116d841e093cb2f
|
File details
Details for the file richertext-0.1.1-py3-none-any.whl.
File metadata
- Download URL: richertext-0.1.1-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46dedaccce39a75f16db3f38a50d958b7dbc0c51d3a3f6b4783564b77299601a
|
|
| MD5 |
ee6662a6e7a6919b4e9719a14b991343
|
|
| BLAKE2b-256 |
9c67b4261c6bcbd66fef5112566e4d5ecd0921bf65fc53c4eed3a15a838ad9df
|