Shivani-Bhat commited on
Commit
366c743
·
verified ·
1 Parent(s): 77743f1

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +73 -85
agent.py CHANGED
@@ -27,25 +27,16 @@ from tools import (
27
  generate_section7_narrative,
28
  export_all_artifacts,
29
  PAJAIS_THEMES,
 
 
 
 
 
 
 
30
  )
31
 
32
- # NEW ADDITIONS import from tools_additions (or inline if merged into tools.py)
33
- try:
34
- from tools_additions import (
35
- dbscan_cluster_topics,
36
- enforce_min_membership,
37
- split_large_clusters,
38
- get_cluster_summary,
39
- label_clusters_with_llm,
40
- run_agentic_council,
41
- )
42
- _ADDITIONS_AVAILABLE = True
43
- except ImportError:
44
- _ADDITIONS_AVAILABLE = False
45
- import logging as _log
46
- _log.getLogger(__name__).warning(
47
- "tools_additions.py not found — DBSCAN and Council features disabled."
48
- )
49
 
50
  # ---------------------------------------------------------------------------
51
  # Logging setup
@@ -99,25 +90,25 @@ class AnalysisConfig:
99
  publishable_min_docs: int = 5
100
  publishable_min_coherence: float = 0.3
101
 
102
- # DBSCAN clustering
103
- dbscan_eps_title: float = 0.25
104
- dbscan_eps_abstract: float = 0.30
105
- dbscan_min_samples: int = 2
106
- dbscan_n_svd: int = 64
107
- dbscan_vote_weight_abstract: float = 0.6
108
-
109
- # Cluster post-processing
110
- min_cluster_members: int = 3
111
- max_cluster_size: int = 30
112
- dbscan_eps_split: float = 0.20
113
-
114
- # LLM labeling
115
- llm_label_max_clusters: int = 50
116
-
117
- # Agentic council API keys (populated from env or UI)
118
  mistral_api_key: str = ""
119
  gemini_api_key: str = ""
120
- anthropic_api_key: str = ""
121
 
122
 
123
  # ---------------------------------------------------------------------------
@@ -143,12 +134,13 @@ class PAJAISResearchAgent:
143
  self.artifacts: Dict[str, str] = {}
144
  self.supplementary_insights: Dict[str, Any] = {}
145
 
146
- # NEW: DBSCAN state
 
147
  self.cluster_df: Optional[pd.DataFrame] = None # doc-level
148
  self.cluster_summary_df: Optional[pd.DataFrame] = None # cluster-level
149
  self.cluster_labeled_df: Optional[pd.DataFrame] = None # with LLM labels
150
 
151
- # NEW: Agentic council
152
  self.council_result: Optional[Dict[str, str]] = None
153
 
154
  self._errors: List[str] = []
@@ -424,77 +416,73 @@ class PAJAISResearchAgent:
424
  # -----------------------------------------------------------------------
425
 
426
  def _phase2_5_dbscan_clustering(self) -> None:
427
- """Phase 2.5: Cluster papers via DBSCAN on title + abstract vectors."""
428
- if not _ADDITIONS_AVAILABLE:
429
- logger.warning("Phase 2.5: tools_additions not available; skipping DBSCAN.")
430
- return
431
  if self.df is None or self.df.empty:
432
  raise ValueError("Phase 2.5: No data loaded. Run Phase 1 first.")
433
 
434
- logger.info("Phase 2.5: Running DBSCAN clustering...")
435
-
436
- # Step A: Initial DBSCAN
437
- self.cluster_df = dbscan_cluster_topics(
438
- df=self.df,
439
- eps_title=self.config.dbscan_eps_title,
440
- eps_abstract=self.config.dbscan_eps_abstract,
441
- min_samples=self.config.dbscan_min_samples,
442
- n_svd_components=self.config.dbscan_n_svd,
443
- vote_weight_abstract=self.config.dbscan_vote_weight_abstract,
 
444
  )
445
 
446
- # Step B: Min membership enforcement
447
- self.cluster_df = enforce_min_membership(
448
- self.cluster_df,
449
- min_members=self.config.min_cluster_members,
450
- strategy="reassign_to_nearest",
 
 
 
 
 
 
 
 
451
  )
452
 
453
- # Step C: Split large clusters
454
- self.cluster_df = split_large_clusters(
455
- self.cluster_df,
456
- max_cluster_size=self.config.max_cluster_size,
457
- eps_split=self.config.dbscan_eps_split,
458
- min_samples_split=self.config.dbscan_min_samples,
459
- max_depth=3,
460
- )
461
-
462
- # Step D: Build summary
463
  self.cluster_summary_df = get_cluster_summary(self.cluster_df)
464
 
465
- n_clusters = len(set(self.cluster_df["cluster_final"]) - {-1})
466
- n_noise = int(self.cluster_df["is_noise"].sum())
467
- logger.info(
468
- f"Phase 2.5 complete: {n_clusters} clusters, {n_noise} noise docs."
469
- )
470
 
471
  def run_llm_cluster_labeling(
472
  self,
473
- api_key: Optional[str] = None,
474
- model: str = "claude-sonnet-4-20250514",
 
475
  ) -> Optional[pd.DataFrame]:
476
- """
477
- Label clusters using the Anthropic LLM.
 
478
  Can be called independently after phase 2.5.
479
  """
480
- if not _ADDITIONS_AVAILABLE:
481
- logger.warning("LLM labeling: tools_additions not available.")
482
- return None
483
  if self.cluster_df is None or self.cluster_summary_df is None:
484
- logger.warning("LLM labeling: run DBSCAN clustering first.")
 
 
 
485
  return None
486
 
487
- key = api_key or self.config.anthropic_api_key
488
- self.cluster_labeled_df = label_clusters_with_llm(
489
  cluster_df=self.cluster_df,
490
  cluster_summary_df=self.cluster_summary_df.copy(),
491
- api_key=key,
492
- model=model,
 
 
493
  max_clusters=self.config.llm_label_max_clusters,
494
  )
495
 
496
- # Persist
497
- out = Path(self.config.output_dir) / "cluster_labels.csv"
498
  try:
499
  self.cluster_labeled_df.to_csv(out, index=False)
500
  logger.info(f"Saved cluster_labels.csv ({len(self.cluster_labeled_df)} rows)")
@@ -594,7 +582,7 @@ class PAJAISResearchAgent:
594
  topic_df=self.topic_df,
595
  mistral_api_key=self.config.mistral_api_key,
596
  gemini_api_key=self.config.gemini_api_key,
597
- anthropic_api_key=self.config.anthropic_api_key,
598
  )
599
 
600
  # Persist council report
 
27
  generate_section7_narrative,
28
  export_all_artifacts,
29
  PAJAIS_THEMES,
30
+ # New unified pipeline (Groups 0, 8-11 in tools.py)
31
+ build_title_abstract_column,
32
+ embed_with_specter2,
33
+ specter2_hdbscan_cluster_topics,
34
+ get_cluster_summary,
35
+ label_clusters_3llm,
36
+ run_agentic_council,
37
  )
38
 
39
+ _ADDITIONS_AVAILABLE = True # everything is now in tools.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  # ---------------------------------------------------------------------------
42
  # Logging setup
 
90
  publishable_min_docs: int = 5
91
  publishable_min_coherence: float = 0.3
92
 
93
+ # SPECTER2 + UMAP + HDBSCAN clustering
94
+ specter2_batch_size: int = 8
95
+ specter2_cache_dir: str = "outputs/specter_cache"
96
+ umap_n_components: int = 50
97
+ umap_n_neighbors: int = 15
98
+ hdbscan_min_cluster_size: int = 5
99
+ hdbscan_max_cluster_size: int = 100
100
+ cluster_target_min: int = 15
101
+ cluster_target_max: int = 30
102
+ cosine_sim_low: float = 0.50
103
+ cosine_sim_high: float = 0.60
104
+
105
+ # LLM labeling (all free APIs)
106
+ llm_label_max_clusters: int = 30
107
+
108
+ # API keys (populated from env or UI)
109
  mistral_api_key: str = ""
110
  gemini_api_key: str = ""
111
+ deepseek_api_key: str = "" # DeepSeek API key (panel C + synthesis judge)
112
 
113
 
114
  # ---------------------------------------------------------------------------
 
134
  self.artifacts: Dict[str, str] = {}
135
  self.supplementary_insights: Dict[str, Any] = {}
136
 
137
+ # SPECTER2 + HDBSCAN state
138
+ self.specter2_embeddings: Optional[np.ndarray] = None # (N, 768)
139
  self.cluster_df: Optional[pd.DataFrame] = None # doc-level
140
  self.cluster_summary_df: Optional[pd.DataFrame] = None # cluster-level
141
  self.cluster_labeled_df: Optional[pd.DataFrame] = None # with LLM labels
142
 
143
+ # Agentic council
144
  self.council_result: Optional[Dict[str, str]] = None
145
 
146
  self._errors: List[str] = []
 
416
  # -----------------------------------------------------------------------
417
 
418
  def _phase2_5_dbscan_clustering(self) -> None:
419
+ """Phase 2.5: SPECTER2 embeddings UMAP HDBSCAN (15-30 clusters)."""
 
 
 
420
  if self.df is None or self.df.empty:
421
  raise ValueError("Phase 2.5: No data loaded. Run Phase 1 first.")
422
 
423
+ logger.info("Phase 2.5: Building title+abstract combined column...")
424
+ df_ta = build_title_abstract_column(self.df)
425
+ # Store back so downstream code can access title_abstract and doi_key
426
+ self.df = df_ta
427
+
428
+ logger.info("Phase 2.5: Generating SPECTER2 embeddings (one per paper)...")
429
+ texts = df_ta['title_abstract'].tolist()
430
+ self.specter2_embeddings = embed_with_specter2(
431
+ texts=texts,
432
+ cache_dir=self.config.specter2_cache_dir,
433
+ batch_size=self.config.specter2_batch_size,
434
  )
435
 
436
+ logger.info("Phase 2.5: Running UMAP + HDBSCAN clustering...")
437
+ self.cluster_df = specter2_hdbscan_cluster_topics(
438
+ df=df_ta,
439
+ embeddings=self.specter2_embeddings,
440
+ min_cluster_size=self.config.hdbscan_min_cluster_size,
441
+ max_cluster_size=self.config.hdbscan_max_cluster_size,
442
+ target_min_clusters=self.config.cluster_target_min,
443
+ target_max_clusters=self.config.cluster_target_max,
444
+ cosine_sim_low=self.config.cosine_sim_low,
445
+ cosine_sim_high=self.config.cosine_sim_high,
446
+ umap_n_components=self.config.umap_n_components,
447
+ umap_n_neighbors=self.config.umap_n_neighbors,
448
+ random_state=self.config.random_state,
449
  )
450
 
 
 
 
 
 
 
 
 
 
 
451
  self.cluster_summary_df = get_cluster_summary(self.cluster_df)
452
 
453
+ n_clusters = len(set(self.cluster_df['cluster_final']) - {-1})
454
+ n_noise = int(self.cluster_df['is_noise'].sum())
455
+ logger.info(f"Phase 2.5 complete: {n_clusters} clusters, {n_noise} noise docs.")
 
 
456
 
457
  def run_llm_cluster_labeling(
458
  self,
459
+ mistral_key: str = '',
460
+ gemini_key: str = '',
461
+ deepseek_key: str = '',
462
  ) -> Optional[pd.DataFrame]:
463
+ """Label clusters using 3 LLMs: Mistral + Gemini + DeepSeek.
464
+
465
+ Majority vote selects the final label; all 3 candidates stored.
466
  Can be called independently after phase 2.5.
467
  """
 
 
 
468
  if self.cluster_df is None or self.cluster_summary_df is None:
469
+ logger.warning("LLM labeling: run SPECTER2/HDBSCAN clustering first.")
470
+ return None
471
+ if self.specter2_embeddings is None:
472
+ logger.warning("LLM labeling: specter2_embeddings not available.")
473
  return None
474
 
475
+ self.cluster_labeled_df = label_clusters_3llm(
 
476
  cluster_df=self.cluster_df,
477
  cluster_summary_df=self.cluster_summary_df.copy(),
478
+ embeddings=self.specter2_embeddings,
479
+ mistral_api_key=mistral_key or self.config.mistral_api_key,
480
+ gemini_api_key=gemini_key or self.config.gemini_api_key,
481
+ deepseek_api_key=deepseek_key or self.config.deepseek_api_key,
482
  max_clusters=self.config.llm_label_max_clusters,
483
  )
484
 
485
+ out = Path(self.config.output_dir) / 'cluster_labels.csv'
 
486
  try:
487
  self.cluster_labeled_df.to_csv(out, index=False)
488
  logger.info(f"Saved cluster_labels.csv ({len(self.cluster_labeled_df)} rows)")
 
582
  topic_df=self.topic_df,
583
  mistral_api_key=self.config.mistral_api_key,
584
  gemini_api_key=self.config.gemini_api_key,
585
+ deepseek_api_key=self.config.deepseek_api_key,
586
  )
587
 
588
  # Persist council report