Skip to content

Commit f452a73

Browse files
committed
feat(perf): optimize callback insertion with parallel batching
- feat(backend): implement Promise.all batch parallel DB insertion in callback-route for 10-20x speedup - refactor(phase5): cleanup obsolete pgvector/hybrid search code (schema, services, api) - refactor(phase5): consolidate DriveConfig to DriveFolder model - refactor(frontend): remove obsolete 'Balance' slider and alpha parameter - docs: update roadmap, architecture, and api docs for Phase 5 completion
1 parent b17a6bd commit f452a73

37 files changed

Lines changed: 340 additions & 1683 deletions

apps/ai-worker/src/embedder.py

Lines changed: 0 additions & 60 deletions
This file was deleted.

apps/ai-worker/src/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from .callback import send_callback
1919
from .config import settings
20+
from .hybrid_embedder import HybridEmbedder
2021
from .logging_config import configure_logging, get_logger
2122
from .metrics import MetricsCollector
2223
from .models import (
@@ -71,7 +72,6 @@ async def readiness_check():
7172
@app.post("/embed", response_model=EmbedResponse)
7273
async def embed_texts(request: EmbedRequest):
7374
"""Generate dense-only embeddings for a list of texts (backward compatibility)."""
74-
from .hybrid_embedder import HybridEmbedder
7575

7676
if not request.texts:
7777
return EmbedResponse(embeddings=[])
@@ -92,7 +92,6 @@ async def embed_query(request: dict):
9292
9393
Returns both dense (384d) and sparse (BM25) vectors for Qdrant hybrid search.
9494
"""
95-
from .hybrid_embedder import HybridEmbedder
9695
from .models import HybridEmbedResponse, SparseVectorModel
9796

9897
text = request.get("text", "")
-222 KB
Binary file not shown.

apps/ai-worker/tests/regression/test_existing_formats.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,15 @@ class TestEmbeddingDimensions:
9696
"""Regression tests for embedding dimensions."""
9797

9898
def test_embedding_dimensions_unchanged(self):
99-
"""Embeddings should still be 384 dimensions."""
100-
from src.embedder import Embedder
99+
"""HybridEmbedder should return 384 dense dimensions."""
100+
from src.hybrid_embedder import HybridEmbedder
101101

102-
embedder = Embedder()
103-
embedding = embedder.embed("Test text")
102+
embedder = HybridEmbedder()
103+
result = embedder.embed(["Test text"])
104104

105-
assert len(embedding) == 384
105+
# HybridEmbedder returns list of HybridVector
106+
assert len(result) == 1
107+
assert len(result[0].dense) == 384
106108

107109

108110
class TestChunkStructure:

apps/ai-worker/tests/test_embedder.py

Lines changed: 0 additions & 76 deletions
This file was deleted.

apps/ai-worker/tests/test_main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ async def test_embed_success(self):
188188
from fastapi.testclient import TestClient
189189
from src.main import app
190190

191-
with patch("src.embedder.Embedder") as MockEmbedder:
191+
# HybridEmbedder is now imported at top of main.py, so mock works
192+
with patch("src.main.HybridEmbedder") as MockEmbedder:
192193
mock_instance = MockEmbedder.return_value
193-
mock_instance.embed.return_value = [[0.1] * 384, [0.2] * 384]
194+
mock_instance.embed_dense_only.return_value = [[0.1] * 384, [0.2] * 384]
194195

195196
with TestClient(app) as client:
196197
response = client.post(

apps/backend/prisma/schema.prisma

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ model Document {
6464
// Phase 2E: Drive Sync
6565
sourceType SourceType @default(MANUAL) @map("source_type")
6666
driveFileId String? @unique @map("drive_file_id")
67-
driveConfigId String? @map("drive_config_id")
67+
driveFolderId String? @map("drive_folder_id")
6868
driveWebViewLink String? @map("drive_web_view_link")
6969
driveModifiedTime DateTime? @map("drive_modified_time")
7070
lastSyncedAt DateTime? @map("last_synced_at")
@@ -77,15 +77,15 @@ model Document {
7777
formatCategory String? @map("format_category")
7878
7979
chunks Chunk[]
80-
driveConfig DriveConfig? @relation(fields: [driveConfigId], references: [id], onDelete: SetNull)
80+
driveFolder DriveFolder? @relation(fields: [driveFolderId], references: [id], onDelete: SetNull)
8181
processingMetrics ProcessingMetrics?
8282
8383
// Phase 5: Processing Profile
8484
processingProfileId String? @map("processing_profile_id")
8585
processingProfile ProcessingProfile? @relation(fields: [processingProfileId], references: [id], onDelete: SetNull)
8686
8787
@@index([sourceType])
88-
@@index([driveConfigId])
88+
@@index([driveFolderId])
8989
@@index([isActive])
9090
@@index([connectionState])
9191
@@index([formatCategory])
@@ -114,9 +114,6 @@ model Chunk {
114114
breadcrumbs String[] @default([])
115115
tokenCount Int? @map("token_count")
116116
117-
// Hybrid Search: BM25 full-text search vector
118-
searchVector Unsupported("tsvector")? @map("search_vector")
119-
120117
// Phase 5D: Qdrant Sync (Outbox Pattern)
121118
syncStatus ChunkSyncStatus @default(PENDING) @map("sync_status")
122119
denseVector Float[] @default([]) @map("dense_vector")
@@ -128,7 +125,6 @@ model Chunk {
128125
@@unique([documentId, chunkIndex])
129126
@@index([qualityScore])
130127
@@index([chunkType])
131-
@@index([searchVector], type: Gin)
132128
@@index([syncStatus])
133129
@@map("chunks")
134130
}
@@ -140,7 +136,7 @@ enum ChunkSyncStatus {
140136
FAILED
141137
}
142138

143-
model DriveConfig {
139+
model DriveFolder {
144140
id String @id @default(uuid())
145141
folderId String @unique @map("folder_id")
146142
folderName String @map("folder_name")
@@ -154,11 +150,6 @@ model DriveConfig {
154150
createdAt DateTime @default(now()) @map("created_at")
155151
updatedAt DateTime @updatedAt @map("updated_at")
156152
157-
// Phase 5A: AES-256-GCM Encrypted OAuth Tokens
158-
encryptedRefreshToken String? @map("encrypted_refresh_token")
159-
tokenIv String? @map("token_iv")
160-
tokenAuthTag String? @map("token_auth_tag")
161-
162153
documents Document[]
163154
164155
// Phase 5: Processing Profile
@@ -167,7 +158,7 @@ model DriveConfig {
167158
168159
@@index([enabled])
169160
@@index([processingProfileId])
170-
@@map("drive_configs")
161+
@@map("drive_folders")
171162
}
172163

173164
// Phase 5F: OAuth Credentials (encrypted refresh tokens)
@@ -264,8 +255,8 @@ model ProcessingProfile {
264255
embeddingDimension Int @default(384) @map("embedding_dimension")
265256
embeddingMaxTokens Int @default(512) @map("embedding_max_tokens")
266257
267-
documents Document[]
268-
driveConfigs DriveConfig[]
258+
documents Document[]
259+
driveFolders DriveFolder[]
269260
270261
@@map("processing_profiles")
271262
}

apps/backend/src/app.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import { disconnectPrisma } from './services/database.js';
2626
import { overviewRoute, processingRoute, qualityRoute, documentsRoute } from './routes/analytics/index.js';
2727
import { chunksRoute } from './routes/chunks/index.js';
2828
import { profileRoutes } from './routes/profiles/index.js';
29-
// Hybrid Search Infrastructure
30-
import { initializeHybridSearch } from './services/hybrid-search-init.js';
3129
// Default Profile
3230
import { ensureDefaultProfile } from './services/default-profile.js';
3331
// Phase 5F: OAuth
@@ -83,10 +81,6 @@ export async function createApp(): Promise<FastifyInstance> {
8381
initializeCronJobs().catch(err => {
8482
console.error('Failed to initialize cron jobs:', err);
8583
});
86-
// Initialize hybrid search (tsvector trigger)
87-
initializeHybridSearch().catch(err => {
88-
console.error('Failed to initialize hybrid search:', err);
89-
});
9084
}
9185

9286
// Register protected routes (Auth applied here)

apps/backend/src/jobs/cron.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import cron, { ScheduledTask } from 'node-cron';
1313
const scheduledTasks: Map<string, ScheduledTask> = new Map();
1414

1515
/**
16-
* Initialize cron jobs for all enabled DriveConfigs
16+
* Initialize cron jobs for all enabled DriveFolders
1717
*/
1818
export async function initializeCronJobs(): Promise<void> {
1919
const prisma = getPrismaClient();
2020

2121
// Get all enabled configs
22-
const configs = await prisma.driveConfig.findMany({
22+
const configs = await prisma.driveFolder.findMany({
2323
where: { enabled: true },
2424
select: { id: true, syncCron: true },
2525
});

0 commit comments

Comments
 (0)