Intelligent Multi-Stage Data Processing Framework
A general-purpose, highly configurable data processing framework that transforms diverse data inputs into structured, categorized, and relevance-filtered information through multi-stage LLM processing.
- Overview
- Key Features
- Architecture
- Installation
- Quick Start
- Configuration
- Usage
- Logging & Traceability
- Examples
- Advanced Features
- Database Schema
Organizations receive data from multiple sources in varying formats. This framework solves the challenge of:
- Normalizing diverse inputs into consistent structure
- Categorizing content based on domain taxonomy
- Filtering for relevance to specific use cases
The framework is wildly configurable - adaptable to any domain (marine science, finance, legal, etc.) through configuration alone, without code changes.
-
Layer 1 (Ingestion): Handles 4 input types
- Structured Single (e.g., one API object)
- Unstructured Single (e.g., one email)
- Structured Multiple (e.g., API array of 50 papers)
- Unstructured Multiple (e.g., HTML with embedded articles)
-
Layer 2 (Structuring): LLM-based extraction & categorization
- Input-type-specific prompts
- Configurable LLM settings per input type
- Optional parallel validation (majority voting)
-
Layer 3 (Relevance Filtering): LLM-based relevance determination
- Batch processing with configurable triggers
- Auto-rules for common patterns
- Quarantine system for uncertain cases
- Make N parallel LLM calls for critical decisions
- Majority voting, unanimous, or 2/3 agreement
- Reduces inconsistency in categorization
- Configurable per-stage and per-input-type
Every operation is logged:
- Raw data backups
- Exact prompts sent
- All LLM responses (including parallel calls)
- Agreement analysis
- API costs
- Processing durations
For any database entry, you can trace:
- Where it came from
- What prompts processed it
- What the LLM decided (with voting record)
- Why it was categorized/filtered as such
- No code changes for new domains
- Different LLM settings per stage
- Pluggable prompt templates
- Flexible category taxonomies
- Auto-rules and post-processing
- Raw data always preserved
- Configurable retry logic
- Failed items quarantined
- Duplicate detection
- Graceful degradation
┌─────────────────────────────────────────────────────────────┐
│ LAYER 1: INGESTION │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ API Fetch │ │ Web Scraping │ │ Webhook Receipt │ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
│ │ │ │ │
│ └────────────────┴────────────────────┘ │
│ │ │
│ Input Classification (4 types) │
│ │ │
│ Store Raw Data + Metadata │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────────────┐
│ LAYER 2: LLM STRUCTURING │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Input-Type-Specific Prompt Templates │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ LLM Call(s) with Optional Validation │ │
│ │ - Single call OR │ │
│ │ - N parallel calls + majority voting │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Extract Fields + Assign Categories │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ Store Structured Entries + Processing Log │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────────────┐
│ LAYER 3: RELEVANCE FILTERING │
│ │
│ Triggered when: N new entries OR max wait time │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Apply Auto-Rules (optional) │ │
│ │ - Auto-relevant patterns │ │
│ │ - Auto-exclude patterns │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ LLM Relevance Evaluation │ │
│ │ - Single call OR │ │
│ │ - N parallel calls + majority voting │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Mark: Relevant / Not Relevant / Quarantined │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ Update Entries + Processing Log │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────┴──────┐
│ DATABASE │
│ + LINEAGE │
└─────────────┘
- Python 3.8+
- PostgreSQL (or SQLite for development)
- Access to LLM Router (bkvy) at
http://localhost:10006
-
Clone the repository:
git clone https://github.com/NitroxHead/thefirstinfobender.git cd thefirstinfobender -
Install dependencies:
pip install -r requirements.txt
-
Configure environment variables:
cp .env.example .env # Edit .env with your API keys and database password -
Set up database:
For PostgreSQL:
createdb thefirstinfobender
For SQLite (development):
# Edit config/database.yaml to use SQLite # Database will be created automatically
-
Configure your domain:
Edit configuration files in
config/:sources.yaml- Define your data sourcescategories.yaml- Define your category taxonomylayer2_config.yaml- Configure structuring promptslayer3_config.yaml- Configure relevance criteria
python main.py process-source job_api_singlepython main.py process-allpython main.py run-layer3 --batch-size 20python main.py statsDefine where data comes from:
sources:
my_api_source:
enabled: true
source_type: "api"
input_type: "structured_multiple"
description: "Research papers API"
fetch_config:
url: "https://api.example.com/papers"
method: "GET"
headers:
X-API-Key: "${RESEARCH_API_KEY}"
polling_interval_seconds: 3600
layer2_config_name: "paper_structuring"Define your domain taxonomy:
categories:
RESEARCH:
description: "Research positions and projects"
subcategories:
PHD:
description: "PhD positions"
POSTDOC:
description: "Postdoctoral positions"Configure LLM structuring:
configurations:
paper_structuring:
input_type: "structured_multiple"
llm_settings:
endpoint: "http://localhost:10006/llm/intelligence"
intelligence_level: "medium"
max_wait_seconds: 30
options:
max_tokens: 2000
temperature: 0.3
llm_validation:
enabled: true
parallel_calls: 3
agreement_threshold: "majority"
compare_fields:
- "category"
- "subcategory"
prompt_template: |
Extract and categorize research papers.
INPUT: {raw_data}
CATEGORIES: {categories}
Output JSON with fields: title, authors, category, etc.Configure relevance filtering:
use_case:
name: "student_opportunities"
description: "Opportunities for students and ECRs"
trigger:
batch_size: 10
max_wait_seconds: 300
llm_validation:
enabled: true
parallel_calls: 5
agreement_threshold: "majority"
relevance_criteria:
target_audience:
- "Students"
- "Early-career researchers"
exclude:
- "Senior positions (>5 years experience)"
- "Faculty positions"# Process specific source
python main.py process-source <source_id>
# Process all sources
python main.py process-all
# Run Layer 3 batch
python main.py run-layer3 --batch-size 20
# View statistics
python main.py stats
# Set log level
python main.py --log-level DEBUG process-source my_sourcefrom src.pipeline import PipelineOrchestrator
from src.logging import setup_logging
# Initialize
setup_logging(log_level="INFO")
pipeline = PipelineOrchestrator(config_dir="config")
# Process source with custom data
custom_data = {"title": "Marine Biology PhD", ...}
result = pipeline.process_source("job_api_single", data=custom_data)
# Run Layer 3
layer3_result = pipeline.run_layer3_batch(batch_size=20)
# Shutdown
pipeline.shutdown()See example_usage.py for more examples.
Layer 1 (Ingestion):
- Source ID and fetch timestamp
- Raw data (full backup)
- Input type classification
- Content hash (deduplication)
Layer 2 (Structuring):
- Prompt sent to LLM (exact text)
- LLM settings used
- If validation enabled:
- All N parallel responses
- Agreement analysis
- Consensus decision
- Model used, processing time, API cost
Layer 3 (Relevance):
- Prompt sent to LLM
- All parallel responses (if validation enabled)
- Relevance decision + reasoning
- Auto-rules applied
- Processing time, API cost
System Events:
- Pipeline starts/stops
- Configuration changes
- Errors and exceptions
Key tables:
raw_data- All raw ingested dataentries- Structured entries with categoriesprocessing_log- Complete processing historysystem_log- System events
Every entry has full lineage traceable back to source.
from src.pipeline import PipelineOrchestrator
pipeline = PipelineOrchestrator()
job_data = {
"title": "Marine Biologist Position",
"organization": "Ocean Institute",
"requirements": ["PhD in marine biology", "5 years experience"],
"deadline": "2025-06-30"
}
result = pipeline.process_source("job_api_single", data=job_data)
print(f"Entry created: {result['layer2']['entry_ids']}")pipeline = PipelineOrchestrator()
# Process 50 entries
result = pipeline.run_layer3_batch(batch_size=50)
print(f"Relevant: {result['relevant']}")
print(f"Not relevant: {result['not_relevant']}")
print(f"Needs review: {result['quarantined']}")Configuration with 5 parallel calls:
llm_validation:
enabled: true
parallel_calls: 5
agreement_threshold: "majority"
compare_fields:
- "category"
- "is_relevant"If 3+ calls agree: accept consensus If no agreement: quarantine for manual review
Reduce inconsistency through redundant calls:
llm_validation:
enabled: true
parallel_calls: 3 # or 5, 7, etc.
agreement_threshold: "majority" # or "unanimous", "2/3"
compare_fields:
- "category"
- "subcategory"How it works:
- Send same prompt to LLM N times in parallel
- Compare responses on specified fields
- If agreement threshold met: accept consensus
- If no agreement: flag for manual review
Skip LLM calls for obvious cases:
post_processing:
auto_relevant_rules:
- field: "category"
value: "EDUCATION.INTERNSHIPS"
reason: "All internships relevant for students"
auto_exclude_rules:
- field: "experience_level"
value: "senior"
reason: "Senior positions not for students"Flag uncertain cases for review:
quarantine_rules:
- field: "confidence"
operator: "<"
value: 0.6
reason: "Low confidence decision"Automatic duplicate detection via content hashing:
deduplication:
enabled: true
strategy: "content_hash"
fields_to_hash:
- "title"
- "description"- Stores all ingested data
- Content hash for deduplication
- Processing status tracking
- Structured, categorized entries
- Flexible JSON for domain-specific fields
- Relevance status and score
- Complete audit trail
- All prompts and responses
- Validation details (parallel calls, voting)
- Performance metrics
- System events
- Configuration changes
- Error tracking
The framework is designed to be domain-agnostic. To adapt:
- Define your categories (
config/categories.yaml) - Configure your sources (
config/sources.yaml) - Write prompt templates (
config/layer2_config.yaml) - Set relevance criteria (
config/layer3_config.yaml)
No code changes required!
- Legal Documents: Categorize by case type, jurisdiction, relevance to practice area
- Financial News: Structure market updates, filter by portfolio relevance
- Scientific Literature: Extract metadata, categorize by field, filter by research focus
- Job Postings: Structure requirements, categorize by industry/level, filter by candidate profile
- LLM Validation Cost: Parallel calls multiply costs. Use selectively for critical decisions.
- Batch Size: Larger Layer 3 batches improve throughput but increase latency.
- Intelligence Level: Use "low" for simple tasks, "high" only when needed.
- Deduplication: Reduces redundant processing.
Database connection fails:
- Check
config/database.yamlsettings - Verify database exists and credentials are correct
- Check
.envfile for DB_PASSWORD
LLM calls failing:
- Verify LLM router is running at
http://localhost:10006 - Check
/healthendpoint - Review LLM router logs
No agreement in validation:
- Review prompt clarity
- Check if task is too subjective
- Consider lowering threshold or reducing parallel calls
- Review quarantined items in database
Processing too slow:
- Disable validation for simple tasks
- Reduce batch sizes
- Use lower intelligence levels
- Check database query performance
This framework is designed for extensibility:
- Custom input classifiers
- Plugin prompt templates
- Additional post-processing rules
- Alternative storage backends
- Custom LLM providers
MIT License
Built to integrate with the bkvy LLM Router for intelligent, cost-optimized LLM routing.
Questions? Check the examples or review the configuration files for detailed options.
