Asish Karthikeya Gogineni commited on
Commit
511ccc3
Β·
1 Parent(s): 5f1e9e9

feat: Add Groq-optimized prompts, vector DB fallback, improved ignore patterns

Browse files

- Added Groq-specific prompts with explicit step-by-step instructions
- Implemented vector DB fallback logic (Chroma -> FAISS)
- Added package-lock.json and other lock files to ignore patterns
- Fixed universal_ingestor.py to skip lock/config files during indexing
- Enhanced code generation prompt for better output quality
- Wired get_prompt_for_provider() for automatic prompt selection

code_chatbot/config.py CHANGED
@@ -80,7 +80,13 @@ class IndexingConfig:
80
 
81
  ignore_patterns: List[str] = field(default_factory=lambda: [
82
  '*.pyc', '__pycache__/*', '.git/*', 'node_modules/*',
83
- '.venv/*', 'venv/*', '*.egg-info/*', 'dist/*', 'build/*'
 
 
 
 
 
 
84
  ])
85
  """File patterns to ignore during indexing"""
86
 
 
80
 
81
  ignore_patterns: List[str] = field(default_factory=lambda: [
82
  '*.pyc', '__pycache__/*', '.git/*', 'node_modules/*',
83
+ '.venv/*', 'venv/*', '*.egg-info/*', 'dist/*', 'build/*',
84
+ # Non-code files that pollute search results
85
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
86
+ '*.lock', '*.log', '*.sqlite3', '*.db',
87
+ '*.min.js', '*.min.css', '*.map',
88
+ '.env*', '*.pem', '*.key',
89
+ 'coverage/*', '.coverage', '.nyc_output/*'
90
  ])
91
  """File patterns to ignore during indexing"""
92
 
code_chatbot/indexer.py CHANGED
@@ -13,6 +13,39 @@ import logging
13
 
14
  logger = logging.getLogger(__name__)
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # Global ChromaDB client cache to avoid "different settings" error
17
  _chroma_clients = {}
18
 
@@ -23,7 +56,13 @@ def reset_chroma_clients():
23
  logger.info("Reset ChromaDB client cache")
24
 
25
  def get_chroma_client(persist_directory: str):
26
- """Get or create a shared ChromaDB client for a given path."""
 
 
 
 
 
 
27
  global _chroma_clients
28
 
29
  # Ensure directory exists
@@ -33,28 +72,64 @@ def get_chroma_client(persist_directory: str):
33
  import chromadb
34
  from chromadb.config import Settings
35
 
36
- try:
37
- _chroma_clients[persist_directory] = chromadb.PersistentClient(
 
38
  path=persist_directory,
39
  settings=Settings(
40
  anonymized_telemetry=False,
41
  allow_reset=True
42
  )
43
  )
44
- except Exception as e:
45
- logger.error(f"Failed to create ChromaDB client: {e}")
46
- # Try to reset and create fresh
 
47
  import shutil
48
  if os.path.exists(persist_directory):
49
  shutil.rmtree(persist_directory)
50
  os.makedirs(persist_directory, exist_ok=True)
51
- _chroma_clients[persist_directory] = chromadb.PersistentClient(
52
- path=persist_directory,
53
- settings=Settings(
54
- anonymized_telemetry=False,
55
- allow_reset=True
56
- )
57
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  return _chroma_clients[persist_directory]
60
 
@@ -155,23 +230,44 @@ class Indexer:
155
 
156
  all_chunks = filter_complex_metadata(all_chunks)
157
 
158
- if vector_db_type == "chroma":
159
- # Use shared client to avoid "different settings" error
160
- chroma_client = get_chroma_client(self.persist_directory)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- vectordb = Chroma(
163
- client=chroma_client,
164
- embedding_function=self.embedding_function,
165
- collection_name=collection_name
166
- )
167
- elif vector_db_type == "faiss":
168
- from langchain_community.vectorstores import FAISS
169
- # FAISS is in-memory by default, we'll save it to disk later
170
- vectordb = None # We build it in the loop
171
- elif vector_db_type == "qdrant":
172
- vectordb = None # Built in bulk later
173
- else:
174
- raise ValueError(f"Unsupported Vector DB: {vector_db_type}")
175
 
176
  # Batch processing - smaller batches to avoid rate limits
177
  batch_size = 20 # Reduced for free tier rate limits
@@ -183,11 +279,14 @@ class Indexer:
183
  import time
184
 
185
  # FAISS handles batching poorly if we want to save incrementally, so we build a list first for FAISS or use from_documents
186
- if vector_db_type == "faiss":
187
  from langchain_community.vectorstores import FAISS
188
  # For FAISS, it's faster to just do it all at once or in big batches
 
189
  vectordb = FAISS.from_documents(all_chunks, self.embedding_function)
190
  vectordb.save_local(folder_path=self.persist_directory, index_name=collection_name)
 
 
191
  return vectordb
192
 
193
  elif vector_db_type == "qdrant":
@@ -240,9 +339,85 @@ class Indexer:
240
  return vectordb
241
 
242
  def get_retriever(self, collection_name: str = "codebase", k: int = 10, vector_db_type: str = "chroma"):
243
- """Get a retriever for the specified collection. Default k=10 for comprehensive results."""
 
 
 
 
 
 
 
 
 
 
 
 
244
  logger.info(f"Creating retriever for collection '{collection_name}' from {self.persist_directory}")
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  if vector_db_type == "chroma":
247
  # Use shared client to avoid "different settings" error
248
  chroma_client = get_chroma_client(self.persist_directory)
@@ -254,49 +429,105 @@ class Indexer:
254
  embedding_function=self.embedding_function,
255
  )
256
 
257
- # Log collection info
258
  try:
259
  collection = vector_store._collection
260
  count = collection.count()
261
  logger.info(f"Collection '{collection_name}' has {count} documents")
 
 
 
 
262
  except Exception as e:
263
- logger.warning(f"Could not get collection count: {e}")
 
 
 
264
 
265
  elif vector_db_type == "faiss":
266
  from langchain_community.vectorstores import FAISS
267
- try:
268
- vector_store = FAISS.load_local(
269
- folder_path=self.persist_directory,
270
- embeddings=self.embedding_function,
271
- index_name=collection_name,
272
- allow_dangerous_deserialization=True # Codebase trust assumed for local use
273
- )
274
- logger.info(f"Loaded FAISS index from {self.persist_directory}")
275
- except Exception as e:
276
- logger.error(f"Failed to load FAISS index: {e}")
277
- # Create empty store if failed? Or raise?
278
- raise e
 
 
 
 
 
 
 
 
 
 
 
 
279
  elif vector_db_type == "qdrant":
280
- from langchain_qdrant import QdrantVectorStore
281
-
282
- url = os.getenv("QDRANT_URL")
283
- api_key = os.getenv("QDRANT_API_KEY")
284
-
285
- vector_store = QdrantVectorStore(
286
- client=None, # It will create one from url/api_key
287
- collection_name=collection_name,
288
- embedding=self.embedding_function,
289
- url=url,
290
- api_key=api_key,
291
- )
292
- logger.info(f"Connected to Qdrant at {url}")
293
-
 
294
  else:
295
- raise ValueError(f"Unsupported Vector DB: {vector_db_type}")
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
- retriever = vector_store.as_retriever(search_kwargs={"k": k})
298
- logger.info(f"Retriever created with k={k}")
299
- return retriever
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
  # Add incremental indexing methods to the Indexer class
302
  from code_chatbot.incremental_indexing import add_incremental_indexing_methods
 
13
 
14
  logger = logging.getLogger(__name__)
15
 
16
+ # Vector database fallback priority order
17
+ # When primary DB fails, automatically try the next in list
18
+ VECTOR_DB_FALLBACK_ORDER = ["chroma", "faiss"]
19
+
20
+ # Track which vector DB is currently active (for automatic fallback)
21
+ _active_vector_db = {"type": "chroma", "fallback_count": 0}
22
+
23
+ def get_active_vector_db() -> str:
24
+ """Get the currently active vector database type."""
25
+ return _active_vector_db["type"]
26
+
27
+ def set_active_vector_db(db_type: str):
28
+ """Set the active vector database type."""
29
+ _active_vector_db["type"] = db_type
30
+ logger.info(f"Active vector database set to: {db_type}")
31
+
32
+ def get_next_fallback_db(current_db: str) -> Optional[str]:
33
+ """Get the next fallback vector database in the priority order.
34
+
35
+ Args:
36
+ current_db: Current vector database type that failed
37
+
38
+ Returns:
39
+ Next vector database type to try, or None if no more fallbacks
40
+ """
41
+ try:
42
+ current_idx = VECTOR_DB_FALLBACK_ORDER.index(current_db)
43
+ if current_idx + 1 < len(VECTOR_DB_FALLBACK_ORDER):
44
+ return VECTOR_DB_FALLBACK_ORDER[current_idx + 1]
45
+ except ValueError:
46
+ pass
47
+ return None
48
+
49
  # Global ChromaDB client cache to avoid "different settings" error
50
  _chroma_clients = {}
51
 
 
56
  logger.info("Reset ChromaDB client cache")
57
 
58
  def get_chroma_client(persist_directory: str):
59
+ """Get or create a shared ChromaDB client for a given path.
60
+
61
+ Includes automatic recovery for common ChromaDB errors:
62
+ - tenant default_tenant connection errors
63
+ - Database corruption
64
+ - Version mismatch issues
65
+ """
66
  global _chroma_clients
67
 
68
  # Ensure directory exists
 
72
  import chromadb
73
  from chromadb.config import Settings
74
 
75
+ def create_client():
76
+ """Helper to create a new ChromaDB client."""
77
+ return chromadb.PersistentClient(
78
  path=persist_directory,
79
  settings=Settings(
80
  anonymized_telemetry=False,
81
  allow_reset=True
82
  )
83
  )
84
+
85
+ def clear_and_recreate():
86
+ """Clear corrupted database and create fresh client."""
87
+ logger.warning(f"Clearing corrupted ChromaDB at {persist_directory} and recreating...")
88
  import shutil
89
  if os.path.exists(persist_directory):
90
  shutil.rmtree(persist_directory)
91
  os.makedirs(persist_directory, exist_ok=True)
92
+ return create_client()
93
+
94
+ def is_corruption_error(error: Exception) -> bool:
95
+ """Check if error indicates database corruption."""
96
+ error_str = str(error).lower()
97
+ corruption_indicators = [
98
+ 'tenant', # "Could not connect to tenant default_tenant"
99
+ 'default_tenant',
100
+ 'sqlite', # SQLite database issues
101
+ 'database',
102
+ 'corrupt',
103
+ 'no such table',
104
+ 'disk i/o error',
105
+ 'malformed',
106
+ 'locked',
107
+ ]
108
+ return any(indicator in error_str for indicator in corruption_indicators)
109
+
110
+ try:
111
+ _chroma_clients[persist_directory] = create_client()
112
+ # Verify the client works by attempting a simple operation
113
+ try:
114
+ _chroma_clients[persist_directory].heartbeat()
115
+ except Exception as verify_error:
116
+ if is_corruption_error(verify_error):
117
+ logger.error(f"ChromaDB verification failed: {verify_error}")
118
+ del _chroma_clients[persist_directory]
119
+ _chroma_clients[persist_directory] = clear_and_recreate()
120
+ else:
121
+ raise
122
+ except Exception as e:
123
+ logger.error(f"Failed to create ChromaDB client: {e}")
124
+ if is_corruption_error(e):
125
+ _chroma_clients[persist_directory] = clear_and_recreate()
126
+ else:
127
+ # For non-corruption errors, still try to recover
128
+ try:
129
+ _chroma_clients[persist_directory] = clear_and_recreate()
130
+ except Exception as recovery_error:
131
+ logger.error(f"Recovery also failed: {recovery_error}")
132
+ raise recovery_error
133
 
134
  return _chroma_clients[persist_directory]
135
 
 
230
 
231
  all_chunks = filter_complex_metadata(all_chunks)
232
 
233
+ # Attempt indexing with fallback support
234
+ attempted_db = vector_db_type
235
+ fallback_triggered = False
236
+
237
+ try:
238
+ if vector_db_type == "chroma":
239
+ # Use shared client to avoid "different settings" error
240
+ chroma_client = get_chroma_client(self.persist_directory)
241
+
242
+ vectordb = Chroma(
243
+ client=chroma_client,
244
+ embedding_function=self.embedding_function,
245
+ collection_name=collection_name
246
+ )
247
+ elif vector_db_type == "faiss":
248
+ from langchain_community.vectorstores import FAISS
249
+ # FAISS is in-memory by default, we'll save it to disk later
250
+ vectordb = None # We build it in the loop
251
+ elif vector_db_type == "qdrant":
252
+ vectordb = None # Built in bulk later
253
+ else:
254
+ raise ValueError(f"Unsupported Vector DB: {vector_db_type}")
255
+ except Exception as e:
256
+ error_str = str(e).lower()
257
+ is_chroma_error = any(indicator in error_str for indicator in [
258
+ 'tenant', 'default_tenant', 'sqlite', 'corrupt',
259
+ 'no such table', 'locked', 'database'
260
+ ])
261
 
262
+ if is_chroma_error and vector_db_type == "chroma":
263
+ logger.warning(f"Chroma indexing failed: {e}. Falling back to FAISS...")
264
+ fallback_triggered = True
265
+ attempted_db = "faiss"
266
+ # Clear the corrupted chroma first
267
+ reset_chroma_clients()
268
+ vectordb = None # Will use FAISS path
269
+ else:
270
+ raise
 
 
 
 
271
 
272
  # Batch processing - smaller batches to avoid rate limits
273
  batch_size = 20 # Reduced for free tier rate limits
 
279
  import time
280
 
281
  # FAISS handles batching poorly if we want to save incrementally, so we build a list first for FAISS or use from_documents
282
+ if vector_db_type == "faiss" or (fallback_triggered and attempted_db == "faiss"):
283
  from langchain_community.vectorstores import FAISS
284
  # For FAISS, it's faster to just do it all at once or in big batches
285
+ logger.info(f"Indexing with FAISS (fallback={fallback_triggered})...")
286
  vectordb = FAISS.from_documents(all_chunks, self.embedding_function)
287
  vectordb.save_local(folder_path=self.persist_directory, index_name=collection_name)
288
+ set_active_vector_db("faiss")
289
+ logger.info(f"Saved FAISS index to {self.persist_directory}/{collection_name}")
290
  return vectordb
291
 
292
  elif vector_db_type == "qdrant":
 
339
  return vectordb
340
 
341
  def get_retriever(self, collection_name: str = "codebase", k: int = 10, vector_db_type: str = "chroma"):
342
+ """Get a retriever for the specified collection with automatic fallback.
343
+
344
+ When the primary vector database fails, automatically attempts the next
345
+ database in the fallback order (chroma -> faiss).
346
+
347
+ Args:
348
+ collection_name: Name of the collection to retrieve from
349
+ k: Number of results to return (default 10)
350
+ vector_db_type: Primary vector database type to try
351
+
352
+ Returns:
353
+ Configured retriever with fallback protection
354
+ """
355
  logger.info(f"Creating retriever for collection '{collection_name}' from {self.persist_directory}")
356
 
357
+ # Track attempts for fallback
358
+ attempted_dbs = []
359
+ last_error = None
360
+ current_db = vector_db_type
361
+
362
+ while current_db and current_db not in attempted_dbs:
363
+ attempted_dbs.append(current_db)
364
+
365
+ try:
366
+ vector_store = self._create_vector_store(current_db, collection_name)
367
+
368
+ if vector_store:
369
+ # Success! Update active DB and return retriever
370
+ set_active_vector_db(current_db)
371
+ retriever = vector_store.as_retriever(search_kwargs={"k": k})
372
+ logger.info(f"Retriever created with k={k} using {current_db}")
373
+ return retriever
374
+
375
+ except Exception as e:
376
+ last_error = e
377
+ error_str = str(e).lower()
378
+
379
+ # Check if this is a recoverable error that warrants fallback
380
+ is_chroma_error = any(indicator in error_str for indicator in [
381
+ 'tenant', 'default_tenant', 'sqlite', 'corrupt',
382
+ 'no such table', 'locked', 'database'
383
+ ])
384
+
385
+ if is_chroma_error or 'chroma' in error_str:
386
+ logger.warning(f"Vector DB '{current_db}' failed: {e}")
387
+
388
+ # Try next fallback
389
+ next_db = get_next_fallback_db(current_db)
390
+ if next_db:
391
+ logger.info(f"Attempting fallback to '{next_db}'...")
392
+ current_db = next_db
393
+ continue
394
+
395
+ # Non-recoverable error
396
+ logger.error(f"Vector DB '{current_db}' failed with non-recoverable error: {e}")
397
+ break
398
+
399
+ # All fallbacks exhausted
400
+ if last_error:
401
+ raise RuntimeError(
402
+ f"All vector database options failed. Attempted: {attempted_dbs}. "
403
+ f"Last error: {last_error}"
404
+ )
405
+ else:
406
+ raise ValueError(f"No valid vector database available. Attempted: {attempted_dbs}")
407
+
408
+ def _create_vector_store(self, vector_db_type: str, collection_name: str):
409
+ """Create a vector store instance for the given database type.
410
+
411
+ Args:
412
+ vector_db_type: Type of vector database (chroma, faiss, qdrant)
413
+ collection_name: Name of the collection
414
+
415
+ Returns:
416
+ Vector store instance
417
+
418
+ Raises:
419
+ Exception: If vector store creation fails
420
+ """
421
  if vector_db_type == "chroma":
422
  # Use shared client to avoid "different settings" error
423
  chroma_client = get_chroma_client(self.persist_directory)
 
429
  embedding_function=self.embedding_function,
430
  )
431
 
432
+ # Verify the store works by getting count
433
  try:
434
  collection = vector_store._collection
435
  count = collection.count()
436
  logger.info(f"Collection '{collection_name}' has {count} documents")
437
+
438
+ if count == 0:
439
+ logger.warning(f"Chroma collection '{collection_name}' is empty!")
440
+
441
  except Exception as e:
442
+ # Re-raise to trigger fallback
443
+ raise RuntimeError(f"Chroma verification failed: {e}")
444
+
445
+ return vector_store
446
 
447
  elif vector_db_type == "faiss":
448
  from langchain_community.vectorstores import FAISS
449
+
450
+ faiss_index_path = os.path.join(self.persist_directory, f"{collection_name}.faiss")
451
+ faiss_pkl_path = os.path.join(self.persist_directory, f"{collection_name}.pkl")
452
+
453
+ # Check if FAISS index exists
454
+ if not os.path.exists(faiss_index_path) and not os.path.exists(faiss_pkl_path):
455
+ # Try default naming convention
456
+ faiss_index_path = os.path.join(self.persist_directory, "index.faiss")
457
+ faiss_pkl_path = os.path.join(self.persist_directory, "index.pkl")
458
+
459
+ if not os.path.exists(faiss_index_path):
460
+ logger.warning(f"No FAISS index found at {self.persist_directory}, will need to re-index")
461
+ # We could trigger re-indexing here or raise to try next fallback
462
+ raise FileNotFoundError(f"FAISS index not found at {self.persist_directory}")
463
+
464
+ vector_store = FAISS.load_local(
465
+ folder_path=self.persist_directory,
466
+ embeddings=self.embedding_function,
467
+ index_name=collection_name,
468
+ allow_dangerous_deserialization=True
469
+ )
470
+ logger.info(f"Loaded FAISS index from {self.persist_directory}")
471
+ return vector_store
472
+
473
  elif vector_db_type == "qdrant":
474
+ from langchain_qdrant import QdrantVectorStore
475
+
476
+ url = os.getenv("QDRANT_URL")
477
+ api_key = os.getenv("QDRANT_API_KEY")
478
+
479
+ vector_store = QdrantVectorStore(
480
+ client=None,
481
+ collection_name=collection_name,
482
+ embedding=self.embedding_function,
483
+ url=url,
484
+ api_key=api_key,
485
+ )
486
+ logger.info(f"Connected to Qdrant at {url}")
487
+ return vector_store
488
+
489
  else:
490
+ raise ValueError(f"Unsupported Vector DB: {vector_db_type}")
491
+
492
+ def get_retriever_with_reindex_fallback(
493
+ self,
494
+ documents: List[Document] = None,
495
+ collection_name: str = "codebase",
496
+ k: int = 10,
497
+ vector_db_type: str = "chroma"
498
+ ):
499
+ """Get retriever with automatic re-indexing fallback.
500
+
501
+ If the primary vector DB fails and fallback also fails to load,
502
+ this method will automatically re-index the documents using
503
+ the fallback database.
504
 
505
+ Args:
506
+ documents: Documents to re-index if needed (optional)
507
+ collection_name: Collection name
508
+ k: Number of results
509
+ vector_db_type: Primary DB type
510
+
511
+ Returns:
512
+ Configured retriever
513
+ """
514
+ try:
515
+ return self.get_retriever(collection_name, k, vector_db_type)
516
+ except (RuntimeError, FileNotFoundError) as e:
517
+ if documents:
518
+ logger.warning(f"Retriever creation failed, attempting re-index with fallback DB: {e}")
519
+
520
+ # Get fallback DB
521
+ fallback_db = get_next_fallback_db(vector_db_type) or "faiss"
522
+
523
+ # Re-index with fallback
524
+ logger.info(f"Re-indexing {len(documents)} documents with {fallback_db}...")
525
+ self.index_documents(documents, collection_name, fallback_db)
526
+
527
+ # Try getting retriever again
528
+ return self.get_retriever(collection_name, k, fallback_db)
529
+ else:
530
+ raise
531
 
532
  # Add incremental indexing methods to the Indexer class
533
  from code_chatbot.incremental_indexing import add_incremental_indexing_methods
code_chatbot/prompts.py CHANGED
@@ -338,4 +338,268 @@ ARCHITECTURE_EXPLANATION_PROMPT = """Explain the architecture and design pattern
338
  5. **Diagram** (text-based): Visual representation of component relationships
339
 
340
  Format with clear sections and reference specific files.
341
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  5. **Diagram** (text-based): Visual representation of component relationships
339
 
340
  Format with clear sections and reference specific files.
341
+ """
342
+
343
+ # =============================================================================
344
+ # GROQ-OPTIMIZED PROMPTS (For Llama and smaller models)
345
+ # =============================================================================
346
+ # These prompts are specifically designed for smaller LLMs that need:
347
+ # - More explicit, step-by-step instructions
348
+ # - Clearer output format specifications
349
+ # - More examples and constraints
350
+ # - Simpler language and shorter sections
351
+
352
+ GROQ_SYSTEM_PROMPT_AGENT = """You are a code assistant for the repository: {repo_name}.
353
+
354
+ YOUR JOB: Help developers understand their codebase by searching code and explaining it clearly.
355
+
356
+ AVAILABLE TOOLS:
357
+ 1. search_codebase(query) - Search for code. USE THIS FIRST for any question.
358
+ 2. read_file(file_path) - Read a complete file for more context.
359
+ 3. list_files(directory) - See what files exist in a folder.
360
+ 4. find_callers(function_name) - Who calls this function?
361
+ 5. find_callees(function_name) - What does this function call?
362
+
363
+ RULES (FOLLOW EXACTLY):
364
+ 1. ALWAYS search first before answering
365
+ 2. ALWAYS cite file paths in your answer
366
+ 3. ALWAYS show code snippets from the codebase
367
+ 4. NEVER make up code - only use what you find
368
+ 5. Keep answers focused and under 500 words unless asked for more
369
+
370
+ HOW TO ANSWER:
371
+
372
+ Step 1: Read the user's question carefully
373
+ Step 2: Use search_codebase with relevant keywords
374
+ Step 3: If needed, use read_file to get full file content
375
+ Step 4: Write your answer following this format:
376
+
377
+ ## Answer
378
+ [2-3 sentences directly answering the question]
379
+
380
+ ## Code Location
381
+ File: `path/to/file.py`
382
+ Lines: X-Y
383
+
384
+ ## Code
385
+ ```python
386
+ [Actual code from the codebase]
387
+ ```
388
+
389
+ ## Explanation
390
+ [Point-by-point explanation of how the code works]
391
+
392
+ EXAMPLE GOOD ANSWER:
393
+ User asks: "How does login work?"
394
+
395
+ ## Answer
396
+ Login is handled by the `authenticate()` function in `src/auth.py`. It validates the username/password and creates a session token.
397
+
398
+ ## Code Location
399
+ File: `src/auth.py`
400
+ Lines: 45-67
401
+
402
+ ## Code
403
+ ```python
404
+ def authenticate(username, password):
405
+ user = db.get_user(username)
406
+ if user and check_password(password, user.hash):
407
+ return create_token(user.id)
408
+ return None
409
+ ```
410
+
411
+ ## Explanation
412
+ 1. Gets user from database by username
413
+ 2. Checks if password matches stored hash
414
+ 3. If valid, creates and returns JWT token
415
+ 4. If invalid, returns None
416
+
417
+ REMEMBER: Short, clear, accurate answers with real code from the codebase.
418
+ """
419
+
420
+ GROQ_SYSTEM_PROMPT_LINEAR_RAG = """You are a code expert answering questions about: {repo_name}
421
+
422
+ I will give you code snippets from the codebase. Use ONLY these snippets to answer.
423
+
424
+ IMPORTANT - FOCUS ON SOURCE CODE:
425
+ - PRIORITIZE files ending in: .py, .js, .ts, .jsx, .tsx, .java, .go, .rs
426
+ - IGNORE config files like: package-lock.json, yarn.lock, *.json (unless specifically asked)
427
+ - IGNORE: node_modules, .git, __pycache__, dist, build folders
428
+ - Focus on: functions, classes, API endpoints, business logic
429
+
430
+ YOUR TASK:
431
+ 1. Read the code snippets below carefully
432
+ 2. Focus on ACTUAL SOURCE CODE files, not config/lock files
433
+ 3. Find functions, classes, and logic that answer the question
434
+ 4. Write a clear, organized answer
435
+
436
+ RULES:
437
+ - ONLY use information from the provided code snippets
438
+ - ALWAYS include file paths: `path/to/file.py`
439
+ - ALWAYS show relevant code with ```python or ```javascript blocks
440
+ - NEVER guess or make up code that isn't shown
441
+ - If you only see config files (package.json, etc.), say "The search didn't return relevant source code. Please ask about specific functions or features."
442
+ - If the snippets don't answer the question, say "The provided code doesn't contain information about [topic]"
443
+
444
+ CODE SNIPPETS FROM CODEBASE:
445
+ {context}
446
+
447
+ ---
448
+
449
+ ANSWER FORMAT:
450
+
451
+ ## Summary
452
+ [1-2 sentences answering the question directly based on SOURCE CODE, not config files]
453
+
454
+ ## Implementation Details
455
+ [Explain the ACTUAL CODE logic - functions, classes, how they work]
456
+
457
+ ## Relevant Code
458
+ ```python
459
+ # From: path/to/source_file.py (NOT config files)
460
+ [paste the actual function/class code]
461
+ ```
462
+
463
+ ## How It Works
464
+ 1. [First step of the logic]
465
+ 2. [Second step]
466
+ 3. [Third step]
467
+
468
+ Keep your answer under 400 words. Focus on source code, not configurations.
469
+ """
470
+
471
+ GROQ_QUERY_EXPANSION_PROMPT = """Turn this question into 3 search queries for a code search engine.
472
+
473
+ Question: {question}
474
+
475
+ Rules:
476
+ - Make queries short (2-5 words each)
477
+ - Include function/class names if mentioned
478
+ - Mix technical terms and simple descriptions
479
+
480
+ Output exactly 3 queries, one per line:
481
+ """
482
+
483
+ GROQ_ANSWER_SYNTHESIS_PROMPT = """Combine these code search results into one clear answer.
484
+
485
+ USER QUESTION: {question}
486
+
487
+ SEARCH RESULTS:
488
+ {retrieved_context}
489
+
490
+ INSTRUCTIONS:
491
+ 1. Read all the search results
492
+ 2. Find the most relevant code for the question
493
+ 3. Write ONE unified answer
494
+
495
+ FORMAT YOUR ANSWER EXACTLY LIKE THIS:
496
+
497
+ ## Direct Answer
498
+ [2-3 sentences answering the question]
499
+
500
+ ## Key Files
501
+ - `file1.py` - [what it does]
502
+ - `file2.py` - [what it does]
503
+
504
+ ## Main Code
505
+ ```python
506
+ [most relevant code snippet]
507
+ ```
508
+
509
+ ## How It Works
510
+ 1. [Step 1]
511
+ 2. [Step 2]
512
+ 3. [Step 3]
513
+
514
+ RULES:
515
+ - Keep answer under 300 words
516
+ - Only use code from the search results
517
+ - Be specific about file names and line numbers
518
+ """
519
+
520
+ GROQ_CODE_MODIFICATION_PROMPT = """You need to suggest code changes for: {repo_name}
521
+
522
+ USER REQUEST: {user_request}
523
+
524
+ EXISTING CODE:
525
+ {existing_code}
526
+
527
+ INSTRUCTIONS:
528
+ 1. Look at the existing code style
529
+ 2. Write new code that matches the style
530
+ 3. Explain where to put the new code
531
+
532
+ OUTPUT FORMAT:
533
+
534
+ ## What I'll Change
535
+ [1 sentence summary]
536
+
537
+ ## New Code
538
+ ```python
539
+ # Add to: path/to/file.py
540
+
541
+ [your code here - match existing style]
542
+ ```
543
+
544
+ ## Where to Add It
545
+ - File: `path/to/file.py`
546
+ - Location: After line X / In function Y / At the end
547
+
548
+ ## What It Does
549
+ 1. [First thing]
550
+ 2. [Second thing]
551
+
552
+ RULES:
553
+ - Match the existing code style exactly
554
+ - Include all necessary imports
555
+ - Handle errors properly
556
+ """
557
+
558
+ # =============================================================================
559
+ # PROMPT SELECTOR FUNCTION
560
+ # =============================================================================
561
+
562
+ def get_prompt_for_provider(prompt_name: str, provider: str = "gemini") -> str:
563
+ """Get the appropriate prompt based on LLM provider.
564
+
565
+ Args:
566
+ prompt_name: Name of the prompt (e.g., "system_agent", "linear_rag")
567
+ provider: LLM provider ("gemini", "groq", etc.)
568
+
569
+ Returns:
570
+ The appropriate prompt string for the provider
571
+ """
572
+ # Prompt mapping for different providers
573
+ prompt_map = {
574
+ "system_agent": {
575
+ "gemini": SYSTEM_PROMPT_AGENT,
576
+ "groq": GROQ_SYSTEM_PROMPT_AGENT,
577
+ "default": SYSTEM_PROMPT_AGENT
578
+ },
579
+ "linear_rag": {
580
+ "gemini": SYSTEM_PROMPT_LINEAR_RAG,
581
+ "groq": GROQ_SYSTEM_PROMPT_LINEAR_RAG,
582
+ "default": SYSTEM_PROMPT_LINEAR_RAG
583
+ },
584
+ "query_expansion": {
585
+ "gemini": QUERY_EXPANSION_PROMPT,
586
+ "groq": GROQ_QUERY_EXPANSION_PROMPT,
587
+ "default": QUERY_EXPANSION_PROMPT
588
+ },
589
+ "answer_synthesis": {
590
+ "gemini": ANSWER_SYNTHESIS_PROMPT,
591
+ "groq": GROQ_ANSWER_SYNTHESIS_PROMPT,
592
+ "default": ANSWER_SYNTHESIS_PROMPT
593
+ },
594
+ "code_modification": {
595
+ "gemini": CODE_MODIFICATION_PROMPT,
596
+ "groq": GROQ_CODE_MODIFICATION_PROMPT,
597
+ "default": CODE_MODIFICATION_PROMPT
598
+ }
599
+ }
600
+
601
+ if prompt_name not in prompt_map:
602
+ raise ValueError(f"Unknown prompt name: {prompt_name}")
603
+
604
+ prompts = prompt_map[prompt_name]
605
+ return prompts.get(provider, prompts["default"])
code_chatbot/rag.py CHANGED
@@ -288,8 +288,8 @@ class ChatEngine:
288
 
289
  # Contextualize with history
290
  # Use comprehensive system prompt for high-quality answers
291
- from code_chatbot.prompts import SYSTEM_PROMPT_AGENT
292
- sys_content = SYSTEM_PROMPT_AGENT.format(repo_name=self.repo_name)
293
  system_msg = SystemMessage(content=sys_content)
294
 
295
  # Token Optimization: Only pass last 4 messages (2 turns) to keep context light.
@@ -396,15 +396,12 @@ class ChatEngine:
396
  "url": doc.metadata.get("url", f"file://{file_path}"),
397
  })
398
 
399
- # Build prompt with history
400
- qa_system_prompt = (
401
- f"You are a Code Chatbot, an expert software engineering assistant helping me quickly understand "
402
- f"a codebase called {self.repo_name}.\n"
403
- "Assume I am an advanced developer and answer my questions in the most succinct way possible.\n"
404
- "Always provide code examples where relevant.\n"
405
- "Link your answers to specific files if possible.\n\n"
406
- "Here are some snippets from the codebase:\n\n"
407
- f"{context_text}"
408
  )
409
 
410
  # Build messages with history
 
288
 
289
  # Contextualize with history
290
  # Use comprehensive system prompt for high-quality answers
291
+ from code_chatbot.prompts import get_prompt_for_provider
292
+ sys_content = get_prompt_for_provider("system_agent", self.provider).format(repo_name=self.repo_name)
293
  system_msg = SystemMessage(content=sys_content)
294
 
295
  # Token Optimization: Only pass last 4 messages (2 turns) to keep context light.
 
396
  "url": doc.metadata.get("url", f"file://{file_path}"),
397
  })
398
 
399
+ # Build prompt with history - use provider-specific prompt
400
+ from code_chatbot.prompts import get_prompt_for_provider
401
+ base_prompt = get_prompt_for_provider("linear_rag", self.provider)
402
+ qa_system_prompt = base_prompt.format(
403
+ repo_name=self.repo_name,
404
+ context=context_text
 
 
 
405
  )
406
 
407
  # Build messages with history
code_chatbot/universal_ingestor.py CHANGED
@@ -156,10 +156,16 @@ class ZIPFileManager(DataManager):
156
  if not os.path.exists(self.path):
157
  return
158
 
159
- IGNORE_DIRS = {'__pycache__', '.git', 'node_modules', 'venv', '.venv', '.env'}
160
  IGNORE_EXTENSIONS = {
161
  '.pyc', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.mp4', '.mov',
162
- '.zip', '.tar', '.gz', '.pdf', '.exe', '.bin', '.pkl', '.npy', '.pt', '.pth'
 
 
 
 
 
 
163
  }
164
 
165
  for root, dirs, files in os.walk(self.path):
@@ -169,6 +175,10 @@ class ZIPFileManager(DataManager):
169
  if file.startswith('.'):
170
  continue
171
 
 
 
 
 
172
  file_path = os.path.join(root, file)
173
  _, ext = os.path.splitext(file)
174
  if ext.lower() in IGNORE_EXTENSIONS:
@@ -204,10 +214,16 @@ class LocalDirectoryManager(DataManager):
204
 
205
  def walk(self, get_content: bool = True) -> Generator[Tuple[Any, Dict], None, None]:
206
  """Walks local directory."""
207
- IGNORE_DIRS = {'__pycache__', '.git', 'node_modules', 'venv', '.venv', '.env'}
208
  IGNORE_EXTENSIONS = {
209
  '.pyc', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.mp4', '.mov',
210
- '.zip', '.tar', '.gz', '.pdf', '.exe', '.bin', '.pkl', '.npy', '.pt', '.pth'
 
 
 
 
 
 
211
  }
212
 
213
  for root, dirs, files in os.walk(self.path):
@@ -217,6 +233,10 @@ class LocalDirectoryManager(DataManager):
217
  if file.startswith('.'):
218
  continue
219
 
 
 
 
 
220
  file_path = os.path.join(root, file)
221
  _, ext = os.path.splitext(file)
222
  if ext.lower() in IGNORE_EXTENSIONS:
 
156
  if not os.path.exists(self.path):
157
  return
158
 
159
+ IGNORE_DIRS = {'__pycache__', '.git', 'node_modules', 'venv', '.venv', '.env', 'dist', 'build'}
160
  IGNORE_EXTENSIONS = {
161
  '.pyc', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.mp4', '.mov',
162
+ '.zip', '.tar', '.gz', '.pdf', '.exe', '.bin', '.pkl', '.npy', '.pt', '.pth',
163
+ '.lock', '.log', '.sqlite3', '.db', '.min.js', '.min.css', '.map'
164
+ }
165
+ # Files to ignore by exact name (lock files, etc.)
166
+ IGNORE_FILES = {
167
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'poetry.lock',
168
+ 'Pipfile.lock', 'composer.lock', 'Gemfile.lock', 'Cargo.lock'
169
  }
170
 
171
  for root, dirs, files in os.walk(self.path):
 
175
  if file.startswith('.'):
176
  continue
177
 
178
+ # Skip ignored files by name
179
+ if file in IGNORE_FILES:
180
+ continue
181
+
182
  file_path = os.path.join(root, file)
183
  _, ext = os.path.splitext(file)
184
  if ext.lower() in IGNORE_EXTENSIONS:
 
214
 
215
  def walk(self, get_content: bool = True) -> Generator[Tuple[Any, Dict], None, None]:
216
  """Walks local directory."""
217
+ IGNORE_DIRS = {'__pycache__', '.git', 'node_modules', 'venv', '.venv', '.env', 'dist', 'build'}
218
  IGNORE_EXTENSIONS = {
219
  '.pyc', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.mp4', '.mov',
220
+ '.zip', '.tar', '.gz', '.pdf', '.exe', '.bin', '.pkl', '.npy', '.pt', '.pth',
221
+ '.lock', '.log', '.sqlite3', '.db', '.min.js', '.min.css', '.map'
222
+ }
223
+ # Files to ignore by exact name (lock files, etc.)
224
+ IGNORE_FILES = {
225
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'poetry.lock',
226
+ 'Pipfile.lock', 'composer.lock', 'Gemfile.lock', 'Cargo.lock'
227
  }
228
 
229
  for root, dirs, files in os.walk(self.path):
 
233
  if file.startswith('.'):
234
  continue
235
 
236
+ # Skip ignored files by name
237
+ if file in IGNORE_FILES:
238
+ continue
239
+
240
  file_path = os.path.join(root, file)
241
  _, ext = os.path.splitext(file)
242
  if ext.lower() in IGNORE_EXTENSIONS:
components/multi_mode.py CHANGED
@@ -359,33 +359,129 @@ def render_generate_mode(chat_engine):
359
 
360
  with st.spinner("πŸ€– Generating feature... (this may take 30-60 seconds)"):
361
  try:
362
- # Build comprehensive prompt
363
- prompt = f"""Generate a complete implementation for this feature:
364
 
365
- **Feature Request:**
 
 
 
 
 
366
  {feature_desc}
367
 
368
- **Requirements:**
369
- - Framework: {framework}
370
- - Include tests: {include_tests}
371
- - Include documentation: {include_docs}
372
- - Include examples: {include_examples}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
- **Please provide:**
375
- 1. A clear file structure showing all files to create
376
- 2. Complete, production-ready code for each file
377
- 3. Clear comments explaining the code
378
- 4. Setup/installation instructions
379
- 5. Usage examples
 
380
 
381
- Format each file like this:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
  ### `path/to/filename.py`
384
  ```python
385
- # Code here
 
 
 
 
 
386
  ```
387
 
388
- Make sure the code follows best practices and matches the existing codebase style."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
  # Use chat engine
391
  answer, sources = chat_engine.chat(prompt)
 
359
 
360
  with st.spinner("πŸ€– Generating feature... (this may take 30-60 seconds)"):
361
  try:
362
+ # Build comprehensive AI Engineer prompt
363
+ prompt = f"""You are a **Senior AI/Software Engineer** with 15+ years of experience building production systems at top tech companies. Your expertise spans system design, security, scalability, and clean code architecture.
364
 
365
+ ## 🎯 MISSION
366
+ Analyze the existing codebase and generate a **production-ready, enterprise-grade** implementation for the requested feature.
367
+
368
+ ---
369
+
370
+ ## πŸ“‹ FEATURE REQUEST
371
  {feature_desc}
372
 
373
+ ---
374
+
375
+ ## βš™οΈ CONFIGURATION
376
+ | Setting | Value |
377
+ |---------|-------|
378
+ | **Framework** | {framework} |
379
+ | **Include Tests** | {include_tests} |
380
+ | **Include Documentation** | {include_docs} |
381
+ | **Include Examples** | {include_examples} |
382
+
383
+ ---
384
+
385
+ ## 🧠 YOUR APPROACH (Follow This Process)
386
+
387
+ ### Phase 1: Architecture Analysis
388
+ Before writing code, analyze the existing codebase to understand:
389
+ - **Project structure** and conventions
390
+ - **Naming patterns** (snake_case, camelCase, etc.)
391
+ - **Import style** and module organization
392
+ - **Error handling** patterns
393
+ - **Logging** approach
394
+ - **Configuration** management style
395
+
396
+ ### Phase 2: Design the Solution
397
+ - Choose appropriate **design patterns** (Factory, Repository, Service Layer, etc.)
398
+ - Plan **database schema** changes if needed
399
+ - Define **API contracts** (request/response schemas)
400
+ - Consider **edge cases** and error scenarios
401
+ - Plan for **scalability** and performance
402
+
403
+ ### Phase 3: Implementation
404
+ Generate code that includes:
405
+
406
+ 1. **πŸ—οΈ Architecture Overview**
407
+ - High-level system diagram (ASCII or Mermaid)
408
+ - Component relationships and data flow
409
+
410
+ 2. **πŸ“ File Structure**
411
+ ```
412
+ feature_name/
413
+ β”œβ”€β”€ __init__.py
414
+ β”œβ”€β”€ models.py # Data models/schemas
415
+ β”œβ”€β”€ service.py # Business logic
416
+ β”œβ”€β”€ routes.py # API endpoints (if applicable)
417
+ β”œβ”€β”€ utils.py # Helper functions
418
+ └── tests/
419
+ β”œβ”€β”€ test_service.py
420
+ └── test_routes.py
421
+ ```
422
 
423
+ 3. **πŸ’» Complete Code** for each file with:
424
+ - **Type hints** on all functions
425
+ - **Docstrings** with Args, Returns, Raises
426
+ - **Input validation** and sanitization
427
+ - **Error handling** with custom exceptions
428
+ - **Logging** at appropriate levels
429
+ - **Security** considerations (auth, injection prevention, etc.)
430
 
431
+ 4. **πŸ§ͺ Test Suite** (if enabled):
432
+ - Unit tests with pytest
433
+ - Edge case coverage
434
+ - Mock external dependencies
435
+ - Minimum 80% code coverage target
436
+
437
+ 5. **πŸ“– Documentation** (if enabled):
438
+ - API documentation with examples
439
+ - Usage guide
440
+ - Configuration options
441
+
442
+ 6. **πŸš€ Integration Guide**:
443
+ - Step-by-step setup instructions
444
+ - Environment variables needed
445
+ - Dependencies to install
446
+ - How to integrate with existing code
447
+
448
+ ---
449
+
450
+ ## πŸ“ CODE FILE FORMAT
451
+ For each file, use this exact format:
452
 
453
  ### `path/to/filename.py`
454
  ```python
455
+ \"\"\"
456
+ Module docstring explaining purpose.
457
+ \"\"\"
458
+ # imports here
459
+
460
+ # code here with full implementation
461
  ```
462
 
463
+ ---
464
+
465
+ ## βœ… QUALITY CHECKLIST
466
+ Ensure your code:
467
+ - [ ] Follows existing codebase conventions
468
+ - [ ] Has no hardcoded values (use config/env vars)
469
+ - [ ] Handles all error cases gracefully
470
+ - [ ] Is thread-safe if applicable
471
+ - [ ] Has no security vulnerabilities
472
+ - [ ] Is optimized for performance
473
+ - [ ] Is maintainable and readable
474
+
475
+ ---
476
+
477
+ ## 🎨 STYLE REQUIREMENTS
478
+ - Clean, readable code over clever code
479
+ - Self-documenting function/variable names
480
+ - Comments for complex logic only
481
+ - Consistent formatting with project style
482
+ - DRY (Don't Repeat Yourself) principle
483
+
484
+ Now generate the complete, production-ready implementation:"""
485
 
486
  # Use chat engine
487
  answer, sources = chat_engine.chat(prompt)