Spaces:
Sleeping
Sleeping
| """ | |
| orchestrator.py | |
| ---------------- | |
| This is the "multi-agent system" coordination layer. | |
| Flow: | |
| 1. Keyword pre-filter on the raw topic (fast, cheap safety gate) | |
| 2. Run Overview, Video, and Paper agents CONCURRENTLY (they don't | |
| depend on each other's output) | |
| 3. Pass all 3 results into the Aggregator Agent to merge into one report | |
| 4. Run an LLM-based safety check on the FINAL report before returning it | |
| 5. Return structured status per agent + the final report | |
| """ | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| from backend import config | |
| from backend.security import keyword_prefilter, llm_safety_check | |
| from backend.agents.overview_agent import build_overview_agent, run_overview_agent | |
| from backend.agents.video_agent import run_video_agent | |
| from backend.agents.paper_agent import run_paper_agent | |
| from backend.agents.aggregator_agent import run_aggregator_agent | |
| def run_research_pipeline(topic: str) -> dict: | |
| # --- Step 1: fast pre-filter ------------------------------------------------- | |
| if not keyword_prefilter(topic): | |
| return { | |
| "status": "blocked", | |
| "reason": "Topic blocked by pre-filter safety rule.", | |
| "report": None, | |
| "agent_log": [], | |
| } | |
| agent_log = [] | |
| # Shared Gemini model instance for Overview + Aggregator + Safety check. | |
| # (Video/Paper agents use their own separate, scoped API keys.) | |
| gemini_model = build_overview_agent(config.GEMINI_API_KEY, config.GEMINI_MODEL) | |
| # --- Step 2: run the 3 specialist agents concurrently ------------------------ | |
| results = {} | |
| with ThreadPoolExecutor(max_workers=3) as executor: | |
| futures = { | |
| executor.submit(run_overview_agent, gemini_model, topic): "overview", | |
| executor.submit(run_video_agent, config.YOUTUBE_API_KEY, topic): "videos", | |
| executor.submit(run_paper_agent, "", topic, 10): "papers", | |
| } | |
| for future in as_completed(futures): | |
| key = futures[future] | |
| try: | |
| results[key] = future.result() | |
| agent_log.append(f"β {key}_agent completed successfully") | |
| except Exception as e: | |
| results[key] = {"error": str(e)} | |
| agent_log.append(f"β {key}_agent failed: {e}") | |
| # Guard against a completely failed overview (nothing to aggregate) | |
| if "content" not in results.get("overview", {}): | |
| return { | |
| "status": "error", | |
| "reason": "Overview agent failed β cannot build report.", | |
| "report": None, | |
| "agent_log": agent_log, | |
| } | |
| if "error" in results.get("overview", {}): | |
| agent_log.append("β οΈ overview_agent rate-limited; loaded degraded fallback overview") | |
| # --- Step 3: aggregate everything into one report --------------------------- | |
| final_report = run_aggregator_agent( | |
| gemini_model, | |
| topic, | |
| overview=results.get("overview", {}), | |
| videos=results.get("videos", {}), | |
| papers=results.get("papers", {}), | |
| ) | |
| agent_log.append("β aggregator_agent merged all results") | |
| # --- Step 4: safety gate on the FINAL report --------------------------------- | |
| is_safe, reason = llm_safety_check(gemini_model, final_report) | |
| if not is_safe: | |
| agent_log.append(f"β safety_gate blocked final report: {reason}") | |
| return { | |
| "status": "blocked", | |
| "reason": reason, | |
| "report": None, | |
| "agent_log": agent_log, | |
| } | |
| if reason and "Bypassed" in reason: | |
| agent_log.append(f"β οΈ safety_gate bypassed: {reason}") | |
| else: | |
| agent_log.append("β safety_gate passed") | |
| return { | |
| "status": "success", | |
| "reason": None, | |
| "report": final_report, | |
| "agent_log": agent_log, | |
| } | |