James Edmunds commited on
Commit ·
d147321
1
Parent(s): 0cc0152
Checkpoint! local working probably. new embeddings, added to readme, additional scripts, updated process_lyrics and upload_embeddings and added some testscripts.
Browse files- config/settings.py +1 -1
- docs/PROJECT_README.md +53 -1
- scripts/check_collections.py +63 -0
- scripts/cleanup_old_embeddings.py +53 -0
- scripts/process_lyrics.py +31 -1
- scripts/upload_embeddings.py +77 -33
config/settings.py
CHANGED
|
@@ -29,7 +29,7 @@ class Settings:
|
|
| 29 |
LLM_MODEL = "gpt-4"
|
| 30 |
|
| 31 |
# ChromaDB Settings
|
| 32 |
-
CHROMA_COLLECTION_NAME = "
|
| 33 |
|
| 34 |
@classmethod
|
| 35 |
def is_huggingface(cls) -> bool:
|
|
|
|
| 29 |
LLM_MODEL = "gpt-4"
|
| 30 |
|
| 31 |
# ChromaDB Settings
|
| 32 |
+
CHROMA_COLLECTION_NAME = "lyrics_v1"
|
| 33 |
|
| 34 |
@classmethod
|
| 35 |
def is_huggingface(cls) -> bool:
|
docs/PROJECT_README.md
CHANGED
|
@@ -70,4 +70,56 @@ python scripts/test_embeddings.py
|
|
| 70 |
python scripts/test_semantic.py
|
| 71 |
```
|
| 72 |
!
|
| 73 |
-
For troubleshooting and known issues, see TROUBLESHOOTING.md
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
python scripts/test_semantic.py
|
| 71 |
```
|
| 72 |
!
|
| 73 |
+
For troubleshooting and known issues, see TROUBLESHOOTING.md
|
| 74 |
+
|
| 75 |
+
## Data Processing & Loading Workflow
|
| 76 |
+
|
| 77 |
+
### 1. Lyrics Processing (`scripts/process_lyrics.py`)
|
| 78 |
+
- Handles initial lyrics processing and embedding creation
|
| 79 |
+
- Features:
|
| 80 |
+
- Batch processing with rate limiting
|
| 81 |
+
- Recursive text splitting (300 chars, 75 overlap)
|
| 82 |
+
- Automatic retry on API limits
|
| 83 |
+
- Comprehensive metadata tracking
|
| 84 |
+
- Progress monitoring with tqdm
|
| 85 |
+
|
| 86 |
+
### 2. Data Loading (`src/utils/data_loader.py`)
|
| 87 |
+
- Manages raw lyrics loading and cleaning
|
| 88 |
+
- Features:
|
| 89 |
+
- Multi-encoding support (utf-8, latin-1, cp1252)
|
| 90 |
+
- Section marker detection ([Verse], [Chorus], etc.)
|
| 91 |
+
- Intelligent line breaking
|
| 92 |
+
- Metadata preservation (artist, song title)
|
| 93 |
+
- Robust validation checks
|
| 94 |
+
|
| 95 |
+
### 3. Workflow Steps
|
| 96 |
+
```bash
|
| 97 |
+
# 1. Load and clean lyrics
|
| 98 |
+
python scripts/process_lyrics.py
|
| 99 |
+
# Creates cleaned, chunked documents with metadata
|
| 100 |
+
|
| 101 |
+
# 2. Generate embeddings
|
| 102 |
+
# (Handled automatically by process_lyrics.py)
|
| 103 |
+
# Stores in data/processed/embeddings/chroma
|
| 104 |
+
|
| 105 |
+
# 3. Upload to HuggingFace
|
| 106 |
+
python scripts/upload_embeddings.py
|
| 107 |
+
# Verifies and uploads to SongLift/LyrGen2_DB
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Data Flow
|
| 111 |
+
```
|
| 112 |
+
Raw Lyrics (txt)
|
| 113 |
+
↓
|
| 114 |
+
Data Loader (cleaning)
|
| 115 |
+
↓
|
| 116 |
+
Text Splitter (chunking)
|
| 117 |
+
↓
|
| 118 |
+
OpenAI Embeddings
|
| 119 |
+
↓
|
| 120 |
+
ChromaDB Storage
|
| 121 |
+
↓
|
| 122 |
+
HuggingFace Dataset
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
Each step includes validation and error handling to ensure data integrity. The process is designed to be resumable and maintains consistency between local and deployed environments.
|
scripts/check_collections.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
# Add project root to path
|
| 6 |
+
project_root = Path(__file__).parent.parent
|
| 7 |
+
sys.path.append(str(project_root))
|
| 8 |
+
|
| 9 |
+
from config.settings import Settings
|
| 10 |
+
|
| 11 |
+
def check_collections():
|
| 12 |
+
"""Check collections in ChromaDB"""
|
| 13 |
+
chroma_path = Settings.get_chroma_path()
|
| 14 |
+
sqlite_file = chroma_path / "chroma.sqlite3"
|
| 15 |
+
|
| 16 |
+
print(f"\nExamining: {sqlite_file}")
|
| 17 |
+
conn = sqlite3.connect(sqlite_file)
|
| 18 |
+
cursor = conn.cursor()
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
# List collections
|
| 22 |
+
cursor.execute("SELECT name, id FROM collections;")
|
| 23 |
+
collections = cursor.fetchall()
|
| 24 |
+
|
| 25 |
+
print("\nCollections found:")
|
| 26 |
+
for name, coll_id in collections:
|
| 27 |
+
print(f"Name: {name}")
|
| 28 |
+
print(f"ID: {coll_id}")
|
| 29 |
+
|
| 30 |
+
# Get count of embeddings using correct schema
|
| 31 |
+
try:
|
| 32 |
+
cursor.execute("""
|
| 33 |
+
SELECT COUNT(*)
|
| 34 |
+
FROM embeddings e
|
| 35 |
+
JOIN segments s ON e.segment_id = s.id
|
| 36 |
+
WHERE s.collection = ?
|
| 37 |
+
""", (coll_id,))
|
| 38 |
+
count = cursor.fetchone()[0]
|
| 39 |
+
print(f"Embeddings count: {count}")
|
| 40 |
+
except sqlite3.OperationalError as e:
|
| 41 |
+
print(f"Could not get count: {e}")
|
| 42 |
+
|
| 43 |
+
# Get metadata using correct schema
|
| 44 |
+
try:
|
| 45 |
+
cursor.execute("""
|
| 46 |
+
SELECT key, str_value
|
| 47 |
+
FROM collection_metadata
|
| 48 |
+
WHERE collection_id = ?
|
| 49 |
+
""", (coll_id,))
|
| 50 |
+
metadata = cursor.fetchall()
|
| 51 |
+
if metadata:
|
| 52 |
+
print("Collection Metadata:")
|
| 53 |
+
for key, value in metadata:
|
| 54 |
+
print(f" {key}: {value}")
|
| 55 |
+
except sqlite3.OperationalError as e:
|
| 56 |
+
print(f"Could not get metadata: {e}")
|
| 57 |
+
print()
|
| 58 |
+
|
| 59 |
+
finally:
|
| 60 |
+
conn.close()
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
check_collections()
|
scripts/cleanup_old_embeddings.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import shutil
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
# Add project root to path
|
| 7 |
+
project_root = Path(__file__).parent.parent
|
| 8 |
+
sys.path.append(str(project_root))
|
| 9 |
+
|
| 10 |
+
from config.settings import Settings
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def cleanup_old_embeddings():
|
| 14 |
+
"""Clean up old embedding directories"""
|
| 15 |
+
chroma_path = Settings.get_chroma_path()
|
| 16 |
+
|
| 17 |
+
# List all UUID directories
|
| 18 |
+
collection_dirs = list(chroma_path.glob("*-*-*-*-*"))
|
| 19 |
+
|
| 20 |
+
print("\nFound collection directories:")
|
| 21 |
+
for dir_path in collection_dirs:
|
| 22 |
+
print(f"- {dir_path.name}")
|
| 23 |
+
|
| 24 |
+
# Get current collection info from database
|
| 25 |
+
sqlite_file = chroma_path / "chroma.sqlite3"
|
| 26 |
+
conn = sqlite3.connect(sqlite_file)
|
| 27 |
+
cursor = conn.cursor()
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
# Get all active collection IDs
|
| 31 |
+
cursor.execute("SELECT id FROM collections")
|
| 32 |
+
active_ids = {row[0] for row in cursor.fetchall()}
|
| 33 |
+
print("\nActive collection IDs:")
|
| 34 |
+
for id in active_ids:
|
| 35 |
+
print(f"- {id}")
|
| 36 |
+
|
| 37 |
+
# Find directories that don't match any active collection
|
| 38 |
+
for dir_path in collection_dirs:
|
| 39 |
+
if dir_path.name not in active_ids:
|
| 40 |
+
print(f"\nFound unused collection directory: {dir_path.name}")
|
| 41 |
+
response = input("Delete this directory? (y/N): ")
|
| 42 |
+
if response.lower() == 'y':
|
| 43 |
+
shutil.rmtree(dir_path)
|
| 44 |
+
print(f"Deleted: {dir_path}")
|
| 45 |
+
else:
|
| 46 |
+
print("Skipped deletion")
|
| 47 |
+
|
| 48 |
+
finally:
|
| 49 |
+
conn.close()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
cleanup_old_embeddings()
|
scripts/process_lyrics.py
CHANGED
|
@@ -3,6 +3,8 @@ import sys
|
|
| 3 |
import time
|
| 4 |
from datetime import datetime
|
| 5 |
from pathlib import Path
|
|
|
|
|
|
|
| 6 |
|
| 7 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 8 |
from langchain_community.vectorstores import Chroma
|
|
@@ -35,7 +37,9 @@ class LyricsProcessor:
|
|
| 35 |
self.output_dir = Path(output_dir)
|
| 36 |
self.batch_size = batch_size
|
| 37 |
self.embeddings = OpenAIEmbeddings()
|
| 38 |
-
self.collection_name =
|
|
|
|
|
|
|
| 39 |
|
| 40 |
# Configure text splitter for lyrics
|
| 41 |
self.text_splitter = RecursiveCharacterTextSplitter(
|
|
@@ -83,6 +87,32 @@ class LyricsProcessor:
|
|
| 83 |
print("Validating configuration...")
|
| 84 |
self.validate_text_splitter()
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
# Load all lyrics documents
|
| 87 |
print("Loading lyrics files...")
|
| 88 |
documents = self.loader.load_lyrics()
|
|
|
|
| 3 |
import time
|
| 4 |
from datetime import datetime
|
| 5 |
from pathlib import Path
|
| 6 |
+
import sqlite3
|
| 7 |
+
import shutil
|
| 8 |
|
| 9 |
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 10 |
from langchain_community.vectorstores import Chroma
|
|
|
|
| 37 |
self.output_dir = Path(output_dir)
|
| 38 |
self.batch_size = batch_size
|
| 39 |
self.embeddings = OpenAIEmbeddings()
|
| 40 |
+
self.collection_name = Settings.CHROMA_COLLECTION_NAME
|
| 41 |
+
|
| 42 |
+
print(f"Using collection name: {self.collection_name}")
|
| 43 |
|
| 44 |
# Configure text splitter for lyrics
|
| 45 |
self.text_splitter = RecursiveCharacterTextSplitter(
|
|
|
|
| 87 |
print("Validating configuration...")
|
| 88 |
self.validate_text_splitter()
|
| 89 |
|
| 90 |
+
# Check for existing collection
|
| 91 |
+
chroma_dir = Path(self.output_dir) / "chroma"
|
| 92 |
+
if chroma_dir.exists():
|
| 93 |
+
sqlite_file = chroma_dir / "chroma.sqlite3"
|
| 94 |
+
if sqlite_file.exists():
|
| 95 |
+
try:
|
| 96 |
+
conn = sqlite3.connect(sqlite_file)
|
| 97 |
+
cursor = conn.cursor()
|
| 98 |
+
cursor.execute("SELECT name FROM collections WHERE name = ?",
|
| 99 |
+
(self.collection_name,))
|
| 100 |
+
if cursor.fetchone():
|
| 101 |
+
response = input(
|
| 102 |
+
f"\nWarning: Collection '{self.collection_name}' already exists.\n"
|
| 103 |
+
"Do you want to delete and recreate? (y/N): "
|
| 104 |
+
)
|
| 105 |
+
if response.lower() != 'y':
|
| 106 |
+
print("Aborting.")
|
| 107 |
+
return
|
| 108 |
+
print("Removing existing collection...")
|
| 109 |
+
shutil.rmtree(chroma_dir)
|
| 110 |
+
chroma_dir.mkdir(parents=True)
|
| 111 |
+
conn.close()
|
| 112 |
+
except Exception as e:
|
| 113 |
+
print(f"Error checking existing collection: {e}")
|
| 114 |
+
print("Continuing with processing...")
|
| 115 |
+
|
| 116 |
# Load all lyrics documents
|
| 117 |
print("Loading lyrics files...")
|
| 118 |
documents = self.loader.load_lyrics()
|
scripts/upload_embeddings.py
CHANGED
|
@@ -2,8 +2,7 @@
|
|
| 2 |
import sys
|
| 3 |
from pathlib import Path
|
| 4 |
from huggingface_hub import HfApi
|
| 5 |
-
import
|
| 6 |
-
import shutil
|
| 7 |
|
| 8 |
# Add parent directory to path to locate 'config' module
|
| 9 |
sys.path.append(str(Path(__file__).parent.parent))
|
|
@@ -31,41 +30,86 @@ def verify_space_access():
|
|
| 31 |
print(f"Error verifying space access: {str(e)}")
|
| 32 |
return False
|
| 33 |
|
| 34 |
-
def
|
| 35 |
-
"""
|
| 36 |
-
|
| 37 |
-
raise ValueError("HF_TOKEN not found in environment variables")
|
| 38 |
-
|
| 39 |
-
print("Verifying Space access...")
|
| 40 |
-
if not verify_space_access():
|
| 41 |
-
raise RuntimeError("Failed to verify Space access")
|
| 42 |
-
|
| 43 |
-
print("Starting upload process...")
|
| 44 |
|
| 45 |
-
#
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
)
|
| 49 |
-
|
| 50 |
|
| 51 |
-
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
print(f"Uploading to Space: {Settings.HF_SPACE}...")
|
| 54 |
try:
|
| 55 |
-
#
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
)
|
| 62 |
-
print("
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
print("
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
if __name__ == "__main__":
|
| 71 |
-
|
|
|
|
| 2 |
import sys
|
| 3 |
from pathlib import Path
|
| 4 |
from huggingface_hub import HfApi
|
| 5 |
+
import sqlite3
|
|
|
|
| 6 |
|
| 7 |
# Add parent directory to path to locate 'config' module
|
| 8 |
sys.path.append(str(Path(__file__).parent.parent))
|
|
|
|
| 30 |
print(f"Error verifying space access: {str(e)}")
|
| 31 |
return False
|
| 32 |
|
| 33 |
+
def verify_and_upload():
|
| 34 |
+
"""Verify local embeddings and upload to HuggingFace"""
|
| 35 |
+
local_chroma = Settings.get_chroma_path()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
# Verify local files first
|
| 38 |
+
print("\nVerifying local embeddings...")
|
| 39 |
+
sqlite_file = local_chroma / "chroma.sqlite3"
|
| 40 |
+
if not sqlite_file.exists():
|
| 41 |
+
raise RuntimeError(f"SQLite database not found at {sqlite_file}")
|
| 42 |
|
| 43 |
+
# Get collection info
|
| 44 |
+
conn = sqlite3.connect(sqlite_file)
|
| 45 |
+
cursor = conn.cursor()
|
| 46 |
|
|
|
|
| 47 |
try:
|
| 48 |
+
# Verify collection
|
| 49 |
+
cursor.execute("SELECT name, id FROM collections;")
|
| 50 |
+
collections = cursor.fetchall()
|
| 51 |
+
|
| 52 |
+
if not collections:
|
| 53 |
+
raise RuntimeError("No collections found in database")
|
| 54 |
+
|
| 55 |
+
print("\nCollections in database:")
|
| 56 |
+
for name, coll_id in collections:
|
| 57 |
+
# Get embeddings count
|
| 58 |
+
cursor.execute("""
|
| 59 |
+
SELECT COUNT(*)
|
| 60 |
+
FROM embeddings e
|
| 61 |
+
JOIN segments s ON e.segment_id = s.id
|
| 62 |
+
WHERE s.collection = ?
|
| 63 |
+
""", (coll_id,))
|
| 64 |
+
count = cursor.fetchone()[0]
|
| 65 |
+
print(f"Collection: {name}")
|
| 66 |
+
print(f"ID: {coll_id}")
|
| 67 |
+
print(f"Embeddings: {count}")
|
| 68 |
+
|
| 69 |
+
# Verify expected collection exists
|
| 70 |
+
collection_names = [c[0] for c in collections]
|
| 71 |
+
if Settings.CHROMA_COLLECTION_NAME not in collection_names:
|
| 72 |
+
raise RuntimeError(
|
| 73 |
+
f"Expected collection '{Settings.CHROMA_COLLECTION_NAME}' not found. "
|
| 74 |
+
f"Found: {collection_names}"
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
# Calculate total size
|
| 78 |
+
total_size = sum(
|
| 79 |
+
f.stat().st_size for f in local_chroma.glob('**/*') if f.is_file()
|
| 80 |
)
|
| 81 |
+
print(f"\nTotal size to upload: {total_size / (1024*1024):.2f} MB")
|
| 82 |
+
|
| 83 |
+
# Confirm before upload
|
| 84 |
+
response = input("\nReady to upload to HuggingFace. Continue? (y/N): ")
|
| 85 |
+
if response.lower() != 'y':
|
| 86 |
+
print("Upload cancelled.")
|
| 87 |
+
return
|
| 88 |
+
|
| 89 |
+
print("\nUploading to HuggingFace...")
|
| 90 |
+
api = HfApi(token=Settings.HF_TOKEN)
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
# Upload files to the existing repo
|
| 94 |
+
api.upload_folder(
|
| 95 |
+
folder_path=str(local_chroma),
|
| 96 |
+
repo_id=Settings.HF_DATASET,
|
| 97 |
+
repo_type="dataset",
|
| 98 |
+
path_in_repo="chroma",
|
| 99 |
+
ignore_patterns=["*.pyc", "__pycache__/", ".DS_Store"]
|
| 100 |
+
)
|
| 101 |
+
print("Upload complete!")
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"Error during upload: {e}")
|
| 105 |
+
print("\nNOTE: If this was a storage limitation error:")
|
| 106 |
+
print("1. Verify Git LFS is enabled")
|
| 107 |
+
print("2. Check HuggingFace storage quota")
|
| 108 |
+
print("3. Ensure all files are under size limits")
|
| 109 |
+
raise
|
| 110 |
+
|
| 111 |
+
finally:
|
| 112 |
+
conn.close()
|
| 113 |
|
| 114 |
if __name__ == "__main__":
|
| 115 |
+
verify_and_upload()
|