Fix Modal vector index building and storage
Browse files- Update Modal app to use remote function calls for proper volume access
- Add fallback storage paths for Modal functions (/storage, /tmp, .)
- Fix Modal client request format to match API expectations
- Remove authentication from Modal API requests
- Add comprehensive error handling and debug logging
- Deploy Modal app with corrected storage configuration
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- modal_app/main.py +54 -13
- server/modal-client.ts +7 -8
modal_app/main.py
CHANGED
|
@@ -168,16 +168,46 @@ def build_vector_index(documents: List[Dict[str, Any]], index_name: str = "main_
|
|
| 168 |
faiss.normalize_L2(embeddings)
|
| 169 |
index.add(embeddings)
|
| 170 |
|
| 171 |
-
#
|
| 172 |
-
|
| 173 |
-
|
|
|
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
faiss.write_index(index, index_path)
|
| 176 |
|
|
|
|
| 177 |
with open(metadata_path, 'wb') as f:
|
| 178 |
pickle.dump(doc_metadata, f)
|
| 179 |
|
| 180 |
-
volume
|
|
|
|
|
|
|
| 181 |
|
| 182 |
task_id = f"index_{index_name}_{hashlib.md5(str(documents).encode()).hexdigest()[:8]}"
|
| 183 |
return {
|
|
@@ -217,14 +247,25 @@ def vector_search(query: str, index_name: str = "main_index", max_results: int =
|
|
| 217 |
# Load embedding model
|
| 218 |
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 219 |
|
| 220 |
-
#
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
-
if not
|
| 225 |
return {
|
| 226 |
'status': 'failed',
|
| 227 |
-
'error': f'Index {index_name} not found. Please build index first.',
|
| 228 |
'results': []
|
| 229 |
}
|
| 230 |
|
|
@@ -365,7 +406,7 @@ web_app = FastAPI(title="KnowledgeBridge Modal API")
|
|
| 365 |
@web_app.post("/vector-search")
|
| 366 |
async def api_vector_search(request: VectorSearchRequest):
|
| 367 |
try:
|
| 368 |
-
result = vector_search.
|
| 369 |
return result
|
| 370 |
except Exception as e:
|
| 371 |
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -373,7 +414,7 @@ async def api_vector_search(request: VectorSearchRequest):
|
|
| 373 |
@web_app.post("/extract-text")
|
| 374 |
async def api_extract_text(request: DocumentRequest):
|
| 375 |
try:
|
| 376 |
-
result = extract_text_from_documents.
|
| 377 |
return result
|
| 378 |
except Exception as e:
|
| 379 |
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -381,7 +422,7 @@ async def api_extract_text(request: DocumentRequest):
|
|
| 381 |
@web_app.post("/build-index")
|
| 382 |
async def api_build_index(request: IndexRequest):
|
| 383 |
try:
|
| 384 |
-
result = build_vector_index.
|
| 385 |
return result
|
| 386 |
except Exception as e:
|
| 387 |
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -389,7 +430,7 @@ async def api_build_index(request: IndexRequest):
|
|
| 389 |
@web_app.post("/batch-process")
|
| 390 |
async def api_batch_process(request: BatchRequest):
|
| 391 |
try:
|
| 392 |
-
result = batch_process_documents.
|
| 393 |
"documents": request.documents,
|
| 394 |
"operations": request.operations,
|
| 395 |
"index_name": request.index_name
|
|
|
|
| 168 |
faiss.normalize_L2(embeddings)
|
| 169 |
index.add(embeddings)
|
| 170 |
|
| 171 |
+
# Try multiple storage locations with fallbacks
|
| 172 |
+
storage_paths = ["/storage", "/tmp", "."]
|
| 173 |
+
index_path = None
|
| 174 |
+
metadata_path = None
|
| 175 |
|
| 176 |
+
for storage_dir in storage_paths:
|
| 177 |
+
try:
|
| 178 |
+
os.makedirs(storage_dir, exist_ok=True)
|
| 179 |
+
test_index_path = f"{storage_dir}/{index_name}.index"
|
| 180 |
+
test_metadata_path = f"{storage_dir}/{index_name}_metadata.pkl"
|
| 181 |
+
|
| 182 |
+
# Test write permissions
|
| 183 |
+
test_file = f"{storage_dir}/test_write_{index_name}.tmp"
|
| 184 |
+
with open(test_file, 'w') as f:
|
| 185 |
+
f.write("test")
|
| 186 |
+
os.remove(test_file)
|
| 187 |
+
|
| 188 |
+
# If we get here, we can write to this directory
|
| 189 |
+
index_path = test_index_path
|
| 190 |
+
metadata_path = test_metadata_path
|
| 191 |
+
print(f"Using storage directory: {storage_dir}")
|
| 192 |
+
break
|
| 193 |
+
|
| 194 |
+
except Exception as e:
|
| 195 |
+
print(f"Cannot write to {storage_dir}: {e}")
|
| 196 |
+
continue
|
| 197 |
+
|
| 198 |
+
if not index_path:
|
| 199 |
+
raise Exception("No writable storage directory found")
|
| 200 |
+
|
| 201 |
+
print(f"Writing index to: {index_path}")
|
| 202 |
faiss.write_index(index, index_path)
|
| 203 |
|
| 204 |
+
print(f"Writing metadata to: {metadata_path}")
|
| 205 |
with open(metadata_path, 'wb') as f:
|
| 206 |
pickle.dump(doc_metadata, f)
|
| 207 |
|
| 208 |
+
# Only commit volume if we used /storage
|
| 209 |
+
if index_path.startswith("/storage"):
|
| 210 |
+
volume.commit()
|
| 211 |
|
| 212 |
task_id = f"index_{index_name}_{hashlib.md5(str(documents).encode()).hexdigest()[:8]}"
|
| 213 |
return {
|
|
|
|
| 247 |
# Load embedding model
|
| 248 |
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 249 |
|
| 250 |
+
# Try to find index in multiple storage locations
|
| 251 |
+
storage_paths = ["/storage", "/tmp", "."]
|
| 252 |
+
index_path = None
|
| 253 |
+
metadata_path = None
|
| 254 |
+
|
| 255 |
+
for storage_dir in storage_paths:
|
| 256 |
+
test_index_path = f"{storage_dir}/{index_name}.index"
|
| 257 |
+
test_metadata_path = f"{storage_dir}/{index_name}_metadata.pkl"
|
| 258 |
+
|
| 259 |
+
if os.path.exists(test_index_path) and os.path.exists(test_metadata_path):
|
| 260 |
+
index_path = test_index_path
|
| 261 |
+
metadata_path = test_metadata_path
|
| 262 |
+
print(f"Found index in: {storage_dir}")
|
| 263 |
+
break
|
| 264 |
|
| 265 |
+
if not index_path or not metadata_path:
|
| 266 |
return {
|
| 267 |
'status': 'failed',
|
| 268 |
+
'error': f'Index {index_name} not found in any storage location. Please build index first.',
|
| 269 |
'results': []
|
| 270 |
}
|
| 271 |
|
|
|
|
| 406 |
@web_app.post("/vector-search")
|
| 407 |
async def api_vector_search(request: VectorSearchRequest):
|
| 408 |
try:
|
| 409 |
+
result = vector_search.remote(request.query, request.index_name, request.max_results)
|
| 410 |
return result
|
| 411 |
except Exception as e:
|
| 412 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 414 |
@web_app.post("/extract-text")
|
| 415 |
async def api_extract_text(request: DocumentRequest):
|
| 416 |
try:
|
| 417 |
+
result = extract_text_from_documents.remote(request.documents)
|
| 418 |
return result
|
| 419 |
except Exception as e:
|
| 420 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 422 |
@web_app.post("/build-index")
|
| 423 |
async def api_build_index(request: IndexRequest):
|
| 424 |
try:
|
| 425 |
+
result = build_vector_index.remote(request.documents, request.index_name)
|
| 426 |
return result
|
| 427 |
except Exception as e:
|
| 428 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 430 |
@web_app.post("/batch-process")
|
| 431 |
async def api_batch_process(request: BatchRequest):
|
| 432 |
try:
|
| 433 |
+
result = batch_process_documents.remote({
|
| 434 |
"documents": request.documents,
|
| 435 |
"operations": request.operations,
|
| 436 |
"index_name": request.index_name
|
server/modal-client.ts
CHANGED
|
@@ -41,7 +41,7 @@ class ModalClient {
|
|
| 41 |
this.config = {
|
| 42 |
tokenId,
|
| 43 |
tokenSecret,
|
| 44 |
-
baseUrl: process.env.MODAL_BASE_URL || 'https://fazeelusmani18--knowledgebridge-main.modal.run'
|
| 45 |
};
|
| 46 |
|
| 47 |
// Create base64 encoded auth token
|
|
@@ -51,14 +51,17 @@ class ModalClient {
|
|
| 51 |
private async makeRequest(endpoint: string, options: RequestInit = {}) {
|
| 52 |
const url = `${this.config.baseUrl}${endpoint}`;
|
| 53 |
|
|
|
|
|
|
|
| 54 |
const response = await fetch(url, {
|
| 55 |
...options,
|
| 56 |
headers: {
|
| 57 |
-
'Authorization': `Basic ${this.authToken}`,
|
| 58 |
'Content-Type': 'application/json',
|
| 59 |
...options.headers,
|
| 60 |
},
|
| 61 |
});
|
|
|
|
|
|
|
| 62 |
|
| 63 |
if (!response.ok) {
|
| 64 |
throw new Error(`Modal API request failed: ${response.status} ${response.statusText}`);
|
|
@@ -98,16 +101,12 @@ class ModalClient {
|
|
| 98 |
* Build FAISS index using Modal's distributed computing
|
| 99 |
*/
|
| 100 |
async buildVectorIndex(documents: any[], indexConfig?: any): Promise<DocumentProcessingTask> {
|
|
|
|
| 101 |
return this.makeRequest('/build-index', {
|
| 102 |
method: 'POST',
|
| 103 |
body: JSON.stringify({
|
| 104 |
documents,
|
| 105 |
-
|
| 106 |
-
dimension: 1536,
|
| 107 |
-
indexType: 'IVF',
|
| 108 |
-
nlist: 100,
|
| 109 |
-
...indexConfig
|
| 110 |
-
}
|
| 111 |
})
|
| 112 |
});
|
| 113 |
}
|
|
|
|
| 41 |
this.config = {
|
| 42 |
tokenId,
|
| 43 |
tokenSecret,
|
| 44 |
+
baseUrl: process.env.MODAL_BASE_URL || 'https://fazeelusmani18--knowledgebridge-main-fastapi-app.modal.run'
|
| 45 |
};
|
| 46 |
|
| 47 |
// Create base64 encoded auth token
|
|
|
|
| 51 |
private async makeRequest(endpoint: string, options: RequestInit = {}) {
|
| 52 |
const url = `${this.config.baseUrl}${endpoint}`;
|
| 53 |
|
| 54 |
+
console.log(`Modal API request: ${options.method || 'GET'} ${url}`);
|
| 55 |
+
|
| 56 |
const response = await fetch(url, {
|
| 57 |
...options,
|
| 58 |
headers: {
|
|
|
|
| 59 |
'Content-Type': 'application/json',
|
| 60 |
...options.headers,
|
| 61 |
},
|
| 62 |
});
|
| 63 |
+
|
| 64 |
+
console.log(`Modal API response: ${response.status} ${response.statusText}`);
|
| 65 |
|
| 66 |
if (!response.ok) {
|
| 67 |
throw new Error(`Modal API request failed: ${response.status} ${response.statusText}`);
|
|
|
|
| 101 |
* Build FAISS index using Modal's distributed computing
|
| 102 |
*/
|
| 103 |
async buildVectorIndex(documents: any[], indexConfig?: any): Promise<DocumentProcessingTask> {
|
| 104 |
+
const indexName = indexConfig?.indexName || 'main_index';
|
| 105 |
return this.makeRequest('/build-index', {
|
| 106 |
method: 'POST',
|
| 107 |
body: JSON.stringify({
|
| 108 |
documents,
|
| 109 |
+
index_name: indexName
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
})
|
| 111 |
});
|
| 112 |
}
|