Skip to content

Commit b75e33a

Browse files
authored
Merge pull request #1 from SQLicious/feat/databricks-ce-delta-mlflow-foundation
feat: Databricks Community foundation with Delta tables, MLflow, and Claude API
2 parents 0858d1d + aca73d8 commit b75e33a

18 files changed

Lines changed: 497 additions & 10 deletions

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ANTHROPIC_API_KEY=your-key-here
2+
DATABRICKS_HOST=https://community.cloud.databricks.com
3+
DATABRICKS_TOKEN=your-databricks-token-here
4+
MLFLOW_TRACKING_URI=databricks
5+
MLFLOW_EXPERIMENT_NAME=/Users/your-email/queryforge-eval

.github/workflows/claude-code.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
id-token: write
2424
pull-requests: write
2525
issues: write
26+
id-token: write
2627
steps:
2728
- name: Run Claude Code
2829
uses: anthropics/claude-code-action@v1

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ node_modules/
66
chroma_data/
77
*.egg-info/
88
.DS_Store
9+
mlruns/
10+
spark-warehouse/
11+
derby.log
12+
metastore_db/

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
COPY . .
9+
10+
EXPOSE 8000
11+
12+
CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "8000"]

README.md

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Inspired by the text-to-SQL POC built at SMBC on real banking data.
2424
+----------------+----------------+
2525
| |
2626
+----------v----------+ +----------v----------+
27-
| Claude API | | PostgreSQL |
28-
| Chain-of-thought | | Financial schemas: |
27+
| Claude API | | Databricks CE |
28+
| Chain-of-thought | | Delta Tables: |
2929
| reasoning + SQL gen | | - accounts |
3030
+----------+----------+ | - transactions |
3131
| | - risk_metrics |
@@ -43,6 +43,7 @@ Inspired by the text-to-SQL POC built at SMBC on real banking data.
4343
|
4444
+----------v----------+
4545
| MLflow Tracking |
46+
| (Databricks CE) |
4647
+---------------------+
4748
```
4849

@@ -51,12 +52,17 @@ Inspired by the text-to-SQL POC built at SMBC on real banking data.
5152
- **MLflow Prompt Registry** — Version-controlled prompt templates (system prompt, schema context, few-shot examples)
5253
- **MLflow Experiments** — Every evaluation run logged with SQL accuracy, execution rate, RAGAS scores
5354
- **MLflow Model Registry**`production` vs `staging` prompt versions, with promotion gates
54-
- **Databricks CE Notebooks** — Schema introspection, Gold layer sample queries for few-shot context
55+
- **Databricks CE Notebooks** — Schema introspection, Delta table setup, Gold layer sample queries for few-shot context
56+
57+
## Storage Layer (Delta Tables on Databricks CE)
58+
59+
- **Delta Lake** — ACID-compliant storage for all financial schemas
60+
- **Four core tables**`accounts`, `transactions`, `risk_metrics`, `model_inventory`
61+
- **Setup notebook**`notebooks/01_setup_delta_tables.py` creates and seeds all tables
5562

5663
## Inference + Evaluation Layer (Docker)
5764

5865
- **FastAPI**`/generate-sql`, `/execute`, `/feedback` endpoints
59-
- **PostgreSQL** — Sample financial database (accounts, transactions, risk_metrics, model_inventory schemas)
6066
- **Claude API** — SQL generation with chain-of-thought reasoning before outputting SQL
6167
- **RAGAS** — Automated evaluation: faithfulness, answer relevancy, context recall
6268
- **GitHub Actions** — Evaluation pipeline runs on every PR, blocks merge if accuracy drops
@@ -67,6 +73,7 @@ Inspired by the text-to-SQL POC built at SMBC on real banking data.
6773

6874
- Docker and Docker Compose
6975
- Anthropic API key
76+
- Databricks Community Edition account (free)
7077
- Python 3.11+
7178

7279
### Setup
@@ -78,9 +85,20 @@ cd QueryForge
7885

7986
# Set environment variables
8087
cp .env.example .env
81-
# Add your ANTHROPIC_API_KEY to .env
88+
# Add your ANTHROPIC_API_KEY and DATABRICKS_TOKEN to .env
89+
```
90+
91+
### Databricks CE Setup
8292

83-
# Start services
93+
1. Sign up at [Databricks Community Edition](https://community.cloud.databricks.com)
94+
2. Import `notebooks/01_setup_delta_tables.py` as a notebook
95+
3. Attach to a cluster and run all cells — creates the `queryforge` database with Delta tables
96+
4. Generate a personal access token: User Settings > Developer > Access Tokens
97+
98+
### Run Locally
99+
100+
```bash
101+
# Start the API server
84102
docker-compose up -d
85103

86104
# Run evaluation
@@ -91,11 +109,22 @@ python scripts/evaluate.py
91109

92110
```
93111
QueryForge/
94-
├── src/ # Application source code
112+
├── src/
113+
│ ├── api/ # FastAPI endpoints
114+
│ │ └── main.py
115+
│ ├── core/ # Business logic
116+
│ │ ├── config.py # Settings and env vars
117+
│ │ ├── sql_generator.py # Claude-powered NL2SQL
118+
│ │ └── mlflow_tracker.py # MLflow experiment logging
119+
│ └── eval/ # Evaluation framework
120+
│ └── evaluator.py # RAGAS + accuracy benchmarks
121+
├── notebooks/ # Databricks CE notebooks
122+
│ └── 01_setup_delta_tables.py
95123
├── tests/ # Test suite
96-
├── scripts/ # Utility and evaluation scripts
97-
├── data/ # Schema definitions and sample data
98-
├── docker-compose.yml # Service orchestration
124+
├── scripts/ # CLI utilities
125+
│ └── evaluate.py
126+
├── docker-compose.yml # API service orchestration
127+
├── Dockerfile
99128
├── requirements.txt # Python dependencies
100129
└── .github/workflows/ # CI/CD pipelines
101130
```

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
queryforge-api:
3+
build: .
4+
ports:
5+
- "8000:8000"
6+
env_file:
7+
- .env
8+
volumes:
9+
- ./src:/app/src
10+
- ./data:/app/data
11+
command: uvicorn src.api.main:app --host 0.0.0.0 --port 8000 --reload

notebooks/01_setup_delta_tables.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Databricks notebook source
2+
# MAGIC %md
3+
# MAGIC # QueryForge - Delta Table Setup
4+
# MAGIC Run this notebook in Databricks Community Edition to create the financial data schemas as Delta tables.
5+
6+
# COMMAND ----------
7+
8+
# MAGIC %md
9+
# MAGIC ## Create Database
10+
11+
# COMMAND ----------
12+
13+
spark.sql("CREATE DATABASE IF NOT EXISTS queryforge")
14+
spark.sql("USE queryforge")
15+
16+
# COMMAND ----------
17+
18+
# MAGIC %md
19+
# MAGIC ## Accounts Table
20+
21+
# COMMAND ----------
22+
23+
from pyspark.sql.types import StructType, StructField, StringType, DecimalType, DateType
24+
25+
accounts_data = [
26+
("ACC001", "Alice Chen", "checking", 125000.50, "2020-03-15", "NYC001", "active"),
27+
("ACC002", "Bob Martinez", "savings", 89000.00, "2019-07-22", "LA002", "active"),
28+
("ACC003", "Carol Williams", "loan", 250000.00, "2021-01-10", "CHI003", "active"),
29+
("ACC004", "David Kim", "credit", 15000.75, "2018-11-05", "NYC001", "active"),
30+
("ACC005", "Eva Patel", "checking", 340000.00, "2022-06-18", "SF004", "active"),
31+
("ACC006", "Frank Johnson", "savings", 45000.00, "2017-02-28", "LA002", "closed"),
32+
("ACC007", "Grace Lee", "checking", 178000.25, "2020-09-14", "CHI003", "active"),
33+
("ACC008", "Henry Brown", "loan", 500000.00, "2023-03-01", "SF004", "active"),
34+
("ACC009", "Irene Davis", "credit", 8500.00, "2021-08-20", "NYC001", "frozen"),
35+
("ACC010", "Jack Wilson", "checking", 92000.00, "2019-12-03", "LA002", "active"),
36+
]
37+
38+
accounts_df = spark.createDataFrame(accounts_data, ["account_id", "customer_name", "account_type", "balance", "open_date", "branch_code", "status"])
39+
accounts_df = accounts_df.withColumn("balance", accounts_df.balance.cast(DecimalType(18, 2)))
40+
accounts_df = accounts_df.withColumn("open_date", accounts_df.open_date.cast(DateType()))
41+
42+
accounts_df.write.format("delta").mode("overwrite").saveAsTable("queryforge.accounts")
43+
print(f"Accounts table created with {accounts_df.count()} rows")
44+
45+
# COMMAND ----------
46+
47+
# MAGIC %md
48+
# MAGIC ## Transactions Table
49+
50+
# COMMAND ----------
51+
52+
transactions_data = [
53+
("TXN001", "ACC001", "2024-01-15 09:30:00", 5000.00, "credit", "salary", "Monthly salary deposit"),
54+
("TXN002", "ACC001", "2024-01-16 14:22:00", 150.00, "debit", "utilities", "Electric bill payment"),
55+
("TXN003", "ACC002", "2024-01-15 10:00:00", 2000.00, "credit", "transfer", "Transfer from checking"),
56+
("TXN004", "ACC003", "2024-01-20 08:00:00", 3500.00, "debit", "loan_payment", "Monthly loan payment"),
57+
("TXN005", "ACC004", "2024-01-18 16:45:00", 250.00, "debit", "shopping", "Online purchase"),
58+
("TXN006", "ACC005", "2024-01-15 09:00:00", 12000.00, "credit", "salary", "Monthly salary deposit"),
59+
("TXN007", "ACC005", "2024-01-22 11:30:00", 800.00, "debit", "dining", "Restaurant payment"),
60+
("TXN008", "ACC007", "2024-01-15 09:15:00", 8500.00, "credit", "salary", "Monthly salary deposit"),
61+
("TXN009", "ACC008", "2024-01-25 08:00:00", 5000.00, "debit", "loan_payment", "Monthly loan payment"),
62+
("TXN010", "ACC010", "2024-01-15 09:45:00", 6000.00, "credit", "salary", "Monthly salary deposit"),
63+
("TXN011", "ACC001", "2024-02-15 09:30:00", 5000.00, "credit", "salary", "Monthly salary deposit"),
64+
("TXN012", "ACC001", "2024-02-20 13:10:00", 2200.00, "debit", "rent", "Monthly rent payment"),
65+
("TXN013", "ACC005", "2024-02-15 09:00:00", 12000.00, "credit", "salary", "Monthly salary deposit"),
66+
("TXN014", "ACC007", "2024-02-15 09:15:00", 8500.00, "credit", "salary", "Monthly salary deposit"),
67+
("TXN015", "ACC010", "2024-02-15 09:45:00", 6000.00, "credit", "salary", "Monthly salary deposit"),
68+
]
69+
70+
from pyspark.sql.types import TimestampType
71+
72+
transactions_df = spark.createDataFrame(transactions_data, ["txn_id", "account_id", "txn_date", "amount", "txn_type", "category", "description"])
73+
transactions_df = transactions_df.withColumn("amount", transactions_df.amount.cast(DecimalType(18, 2)))
74+
transactions_df = transactions_df.withColumn("txn_date", transactions_df.txn_date.cast(TimestampType()))
75+
76+
transactions_df.write.format("delta").mode("overwrite").saveAsTable("queryforge.transactions")
77+
print(f"Transactions table created with {transactions_df.count()} rows")
78+
79+
# COMMAND ----------
80+
81+
# MAGIC %md
82+
# MAGIC ## Risk Metrics Table
83+
84+
# COMMAND ----------
85+
86+
risk_data = [
87+
("ACC001", "2024-01-31", 750, "low", 0.0120, 0.3500),
88+
("ACC002", "2024-01-31", 680, "medium", 0.0450, 0.4000),
89+
("ACC003", "2024-01-31", 620, "high", 0.0890, 0.5500),
90+
("ACC004", "2024-01-31", 580, "high", 0.1200, 0.6000),
91+
("ACC005", "2024-01-31", 800, "low", 0.0050, 0.2500),
92+
("ACC007", "2024-01-31", 720, "low", 0.0180, 0.3200),
93+
("ACC008", "2024-01-31", 550, "critical", 0.1800, 0.7500),
94+
("ACC009", "2024-01-31", 490, "critical", 0.2500, 0.8500),
95+
("ACC010", "2024-01-31", 700, "medium", 0.0350, 0.3800),
96+
]
97+
98+
from pyspark.sql.types import IntegerType
99+
100+
risk_df = spark.createDataFrame(risk_data, ["account_id", "metric_date", "credit_score", "risk_rating", "probability_of_default", "loss_given_default"])
101+
risk_df = risk_df.withColumn("metric_date", risk_df.metric_date.cast(DateType()))
102+
risk_df = risk_df.withColumn("credit_score", risk_df.credit_score.cast(IntegerType()))
103+
risk_df = risk_df.withColumn("probability_of_default", risk_df.probability_of_default.cast(DecimalType(5, 4)))
104+
risk_df = risk_df.withColumn("loss_given_default", risk_df.loss_given_default.cast(DecimalType(5, 4)))
105+
106+
risk_df.write.format("delta").mode("overwrite").saveAsTable("queryforge.risk_metrics")
107+
print(f"Risk metrics table created with {risk_df.count()} rows")
108+
109+
# COMMAND ----------
110+
111+
# MAGIC %md
112+
# MAGIC ## Model Inventory Table
113+
114+
# COMMAND ----------
115+
116+
model_data = [
117+
("MDL001", "Credit Scoring v3", "classification", "2023-06-15", "Risk Team", "active", "2024-01-15"),
118+
("MDL002", "Fraud Detection v2", "anomaly_detection", "2023-09-01", "Fraud Team", "active", "2024-01-20"),
119+
("MDL003", "Churn Predictor", "classification", "2022-11-10", "Marketing", "retired", "2023-06-10"),
120+
("MDL004", "LGD Model v1", "regression", "2024-01-05", "Risk Team", "validation", "2024-01-05"),
121+
("MDL005", "Transaction Classifier", "classification", "2023-03-20", "Operations", "active", "2023-12-15"),
122+
]
123+
124+
model_df = spark.createDataFrame(model_data, ["model_id", "model_name", "model_type", "deployment_date", "owner", "status", "last_validation_date"])
125+
model_df = model_df.withColumn("deployment_date", model_df.deployment_date.cast(DateType()))
126+
model_df = model_df.withColumn("last_validation_date", model_df.last_validation_date.cast(DateType()))
127+
128+
model_df.write.format("delta").mode("overwrite").saveAsTable("queryforge.model_inventory")
129+
print(f"Model inventory table created with {model_df.count()} rows")
130+
131+
# COMMAND ----------
132+
133+
# MAGIC %md
134+
# MAGIC ## Verify All Tables
135+
136+
# COMMAND ----------
137+
138+
for table in ["accounts", "transactions", "risk_metrics", "model_inventory"]:
139+
count = spark.sql(f"SELECT COUNT(*) FROM queryforge.{table}").collect()[0][0]
140+
print(f"queryforge.{table}: {count} rows")

requirements.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
anthropic>=0.42.0
2+
fastapi>=0.115.0
3+
uvicorn>=0.34.0
4+
mlflow>=2.19.0
5+
databricks-sdk>=0.38.0
6+
delta-spark>=3.3.0
7+
pyspark>=3.5.0
8+
ragas>=0.2.0
9+
langchain>=0.3.0
10+
langchain-anthropic>=0.3.0
11+
pydantic>=2.10.0
12+
pydantic-settings>=2.7.0
13+
python-dotenv>=1.0.0
14+
httpx>=0.28.0

scripts/evaluate.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Run QueryForge evaluation suite from CLI."""
2+
import sys
3+
import json
4+
from pathlib import Path
5+
6+
sys.path.insert(0, str(Path(__file__).parent.parent))
7+
8+
from src.eval.evaluator import run_evaluation
9+
10+
11+
def main():
12+
version = sys.argv[1] if len(sys.argv) > 1 else "v1"
13+
print(f"Running QueryForge evaluation (prompt version: {version})...")
14+
15+
results = run_evaluation(prompt_version=version)
16+
17+
print(f"\n{'='*60}")
18+
print(f"Evaluation Complete — Prompt Version: {results['prompt_version']}")
19+
print(f"{'='*60}")
20+
print(f"Total Questions: {results['metrics']['total_questions']}")
21+
print(f"Total Tokens: {results['metrics']['total_tokens']}")
22+
print(f"Avg Tokens/Query: {results['metrics']['avg_tokens_per_query']:.0f}")
23+
24+
for r in results["results"]:
25+
print(f"\nQ: {r['question']}")
26+
print(f" Generated: {r['generated_sql'][:80]}...")
27+
28+
print(f"\nFull results logged to MLflow experiment: {version}")
29+
30+
31+
if __name__ == "__main__":
32+
main()

src/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)