Datavision / backend /core /agent_swarm.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
6.18 kB
import json
import logging
import asyncio
from typing import Dict, Any, Optional
import pandas as pd
from datetime import datetime
from core.llm import chat
from core.autonomous_data_ops import AutonomousDataOps
from api.v1.endpoints.charts import get_user_data, _set_cached_df
logger = logging.getLogger(__name__)
class CollaborationSwarm:
"""
Agent Swarm for the Collaboration Hub.
Routes intents and performs data analysis or data cleaning (autofix) in real-time.
"""
def __init__(self):
self.data_agent = AutonomousDataOps()
def _get_context(self, user_id: str) -> Optional[pd.DataFrame]:
df = get_user_data(user_id)
if df is None or df.empty:
return None
return df
async def process_message(self, user_id: str, message: str) -> Dict[str, Any]:
"""
Process an @ai message and return an insight dictionary.
"""
df = self._get_context(user_id)
if df is None:
return {
"id": str(int(datetime.now().timestamp() * 1000) + 1),
"user": "DataVision AI",
"avatar": "✨",
"message": "I don't see any uploaded data yet. Please upload a dataset in the Data Hub first, then I can analyze it for you!",
"time": "Just now",
"isAi": True,
}
source_file = df['_source_file'].iloc[0] if '_source_file' in df.columns else "your data"
# Simple Intent Routing
msg_lower = message.lower()
if "autofix" in msg_lower or "clean" in msg_lower or "fix" in msg_lower:
return await self._run_autofix_agent(user_id, df, source_file)
else:
return await self._run_data_analyst_agent(user_id, df, source_file, message)
async def _run_autofix_agent(self, user_id: str, df: pd.DataFrame, source_file: str) -> Dict[str, Any]:
"""Run the Data Engineer / Autofix Agent"""
try:
logger.info("Triggering Autofix Agent...")
fixed_df, report = self.data_agent.auto_fix(df)
# Save the fixed dataframe back to the user cache
_set_cached_df(user_id, fixed_df)
# Construct a rich response
message = f"✅ **Autofix Complete for {source_file}**\n\n"
message += f"I analyzed {report.original_rows} rows and {report.original_cols} columns. Here is what I fixed:\n"
if report.fixes_applied:
for fix in report.fixes_applied[:5]:
message += f"- {fix.action_taken}: {fix.details}\n"
if len(report.fixes_applied) > 5:
message += f"- ...and {len(report.fixes_applied) - 5} more fixes.\n"
else:
message += "- The data was already clean! No major fixes were needed.\n"
if report.enrichments_added:
message += "\n**Enrichments Added:**\n"
for enr in report.enrichments_added:
message += f"- {enr}\n"
message += f"\nQuality Score improved to **{report.quality_score_after}/100**."
return {
"id": str(int(datetime.now().timestamp() * 1000) + 1),
"user": "Autofix Agent",
"avatar": "🛠️",
"message": message,
"time": "Just now",
"isAi": True,
}
except Exception as e:
logger.error(f"Autofix Agent error: {e}")
return {
"id": str(int(datetime.now().timestamp() * 1000) + 1),
"user": "Autofix Agent",
"avatar": "🛠️",
"message": f"I encountered an issue while trying to autofix your data: {e}",
"time": "Just now",
"isAi": True,
}
async def _run_data_analyst_agent(self, user_id: str, df: pd.DataFrame, source_file: str, query: str) -> Dict[str, Any]:
"""Run the Data Analyst Agent using the LLM orchestrator"""
try:
# Prepare data context for LLM
import numpy as np
numeric_cols = [c for c in df.select_dtypes(include=[np.number]).columns if not c.startswith('_')]
categorical_cols = [c for c in df.select_dtypes(include=['object', 'category']).columns if not c.startswith('_')]
sample_data = df.head(3).to_markdown()
context = f"""
You are the Data Analyst Agent in a Collaboration Hub swarm.
The user is asking a question about their dataset.
Dataset Context:
- Source File: {source_file}
- Total Rows: {len(df)}
- Numeric Columns: {numeric_cols}
- Categorical Columns: {categorical_cols}
Sample Data (first 3 rows):
{sample_data}
Answer the user's question clearly, concisely, and professionally based on the data context provided.
If the question is unrelated to the data, answer it to the best of your ability as an AI assistant.
"""
# We use loop.run_in_executor to avoid blocking the asyncio event loop since chat() is synchronous
loop = asyncio.get_event_loop()
llm_response = await loop.run_in_executor(None, lambda: chat(
messages=query,
system=context,
temperature=0.3
))
return {
"id": str(int(datetime.now().timestamp() * 1000) + 1),
"user": "Data Analyst Agent",
"avatar": "🧠",
"message": llm_response,
"time": "Just now",
"isAi": True,
}
except Exception as e:
logger.error(f"Data Analyst Agent error: {e}")
return {
"id": str(int(datetime.now().timestamp() * 1000) + 1),
"user": "Data Analyst Agent",
"avatar": "🧠",
"message": f"I encountered an issue analyzing your data: {e}",
"time": "Just now",
"isAi": True,
}