Update src/agents/agents.py

#1
This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .dockerignore +0 -2
  2. .env-template +6 -5
  3. .gitattributes +0 -1
  4. .gitignore +3 -27
  5. .huggingface.yaml +0 -1
  6. Dockerfile +1 -22
  7. Procfile +0 -1
  8. agents_config.json +0 -147
  9. app.py +418 -2486
  10. chat_database.db +3 -0
  11. docs/README.md +0 -251
  12. docs/api/routes/analytics.md +0 -562
  13. docs/api/routes/code.md +0 -182
  14. docs/api/routes/deep_analysis.md +0 -348
  15. docs/api/routes/feedback.md +0 -153
  16. docs/api/routes/session.md +0 -273
  17. docs/api/routes/templates.md +0 -363
  18. docs/architecture/architecture.md +0 -427
  19. docs/db_schema.md +74 -0
  20. docs/development/development_workflow.md +0 -506
  21. docs/{api/README.md → endpoints.md} +3 -16
  22. docs/getting_started.md +0 -273
  23. docs/routes/analytics.md +200 -0
  24. docs/{api/routes → routes}/chats.md +109 -33
  25. docs/routes/core.md +242 -0
  26. docs/schema.md +48 -0
  27. docs/{system/shared_dataframe.md → shared_dataframe.md} +0 -0
  28. docs/system/database-schema.md +0 -289
  29. docs/troubleshooting/troubleshooting.md +0 -537
  30. entrypoint_local.sh +0 -175
  31. images/Auto-Analyst Banner.png +3 -0
  32. images/Auto-analysts icon small.png +0 -0
  33. images/auto-analyst logo.png +0 -0
  34. requirements.txt +23 -31
  35. sample_output.txt +72 -0
  36. scripts/create_test_user.py +45 -0
  37. scripts/format_response.py +120 -704
  38. scripts/generate_test_data.py +90 -0
  39. scripts/init_production_db.py +0 -191
  40. scripts/populate_agent_templates.py +0 -508
  41. scripts/setup_analytics_data.py +114 -0
  42. scripts/test_agent_parsing.py +42 -0
  43. scripts/test_model_usage.py +114 -0
  44. scripts/test_token_counting.py +176 -0
  45. scripts/tier_maker.py +6 -27
  46. scripts/verify_session_state.py +76 -0
  47. src/agents/agents.py +0 -0
  48. src/agents/deep_agents.py +0 -1110
  49. src/agents/retrievers/retrievers.py +89 -3
  50. src/agents/test_agent.py +1223 -0
.dockerignore CHANGED
@@ -13,6 +13,4 @@ notebooks/
13
  .idea/
14
  .vscode/
15
  .DS_Store
16
- # Exclude most JSON files but allow agents_config.json
17
  *.json
18
- !agents_config.json
 
13
  .idea/
14
  .vscode/
15
  .DS_Store
 
16
  *.json
 
.env-template CHANGED
@@ -7,9 +7,10 @@ GROQ_API_KEY=your-groq-api-key-here
7
  ANTHROPIC_API_KEY=your-anthropic-api-key-here
8
  GEMINI_API_KEY=your-gemini-api-key-here
9
 
10
- ADMIN_API_KEY=admin123
 
 
 
 
11
 
12
- DATABASE_URL=sqlite:///chat_database.db
13
- ENVIRONMENT="development"
14
-
15
- FRONTEND_URL="http://localhost:3000/"
 
7
  ANTHROPIC_API_KEY=your-anthropic-api-key-here
8
  GEMINI_API_KEY=your-gemini-api-key-here
9
 
10
+ DATABASE_URL=postgresql://dbadmin:admin123@auto-analyst-db.xxxxxxxxxxxxx.us-east-1.rds.amazonaws.com:5432/autoanalyst
11
+ DB_HOST=auto-analyst-db.xxxxxxxxxxxxx.us-east-1.rds.amazonaws.com
12
+ DB_NAME=autoanalyst
13
+ DB_USER=dbadmin
14
+ DB_PASSWORD=admin123
15
 
16
+ ENV="development"
 
 
 
.gitattributes CHANGED
@@ -35,4 +35,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  images/Auto-Analyst[[:space:]]Banner.png filter=lfs diff=lfs merge=lfs -text
37
  chat_database.db filter=lfs diff=lfs merge=lfs -text
38
- *.png filter=lfs diff=lfs merge=lfs -text
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  images/Auto-Analyst[[:space:]]Banner.png filter=lfs diff=lfs merge=lfs -text
37
  chat_database.db filter=lfs diff=lfs merge=lfs -text
 
.gitignore CHANGED
@@ -11,12 +11,14 @@ venv/
11
 
12
  try*
13
 
 
 
14
  logs/
15
 
16
  updated_code.py
17
  sample_code.py
18
 
19
- *.duckdb
20
  *.dump
21
 
22
  migrations/
@@ -24,29 +26,3 @@ migrations/
24
  *.pyc
25
  alembic.ini
26
 
27
-
28
- *.db
29
-
30
- schema*.md
31
-
32
- # agent_config.json
33
-
34
-
35
- notebooks/
36
-
37
- *.xlsx
38
- *.xls
39
- *.xlsm
40
- *.xlsb
41
-
42
-
43
- testing.ipynb
44
- redis_index.json
45
- email_to_userid_mapping.json
46
- redis_backup_20250906_143859.json
47
- "*.db"
48
- "*.sqlite"
49
- "*.sqlite3"
50
- "venv/"
51
- "__pycache__/"
52
- "*.pyc"
 
11
 
12
  try*
13
 
14
+ chat_database_temp.db
15
+
16
  logs/
17
 
18
  updated_code.py
19
  sample_code.py
20
 
21
+
22
  *.dump
23
 
24
  migrations/
 
26
  *.pyc
27
  alembic.ini
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.huggingface.yaml DELETED
@@ -1 +0,0 @@
1
- sdk: docker
 
 
Dockerfile CHANGED
@@ -6,29 +6,8 @@ ENV PATH="/home/user/.local/bin:$PATH"
6
 
7
  WORKDIR /app
8
 
9
-
10
-
11
  COPY --chown=user ./requirements.txt requirements.txt
12
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
13
 
14
  COPY --chown=user . /app
15
-
16
- # Verify agents_config.json was copied (it should be in the backend directory)
17
- RUN if [ -f "/app/agents_config.json" ]; then \
18
- echo "✅ agents_config.json found in container"; \
19
- ls -la /app/agents_config.json; \
20
- else \
21
- echo "⚠️ agents_config.json not found in container - will use fallback templates"; \
22
- ls -la /app/ | grep -E "agents|config" || echo "No config files found"; \
23
- fi
24
-
25
- # Make entrypoint script executable
26
- USER root
27
- RUN chmod +x /app/entrypoint_local.sh
28
- # Make populate script executable
29
- RUN chmod +x /app/scripts/populate_agent_templates.py
30
-
31
- USER user
32
-
33
- # Use the entrypoint script instead of directly running uvicorn
34
- CMD ["/app/entrypoint_local.sh"]
 
6
 
7
  WORKDIR /app
8
 
 
 
9
  COPY --chown=user ./requirements.txt requirements.txt
10
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Procfile DELETED
@@ -1 +0,0 @@
1
- web: (python scripts/init_production_db.py || echo "DB init failed") && (python scripts/populate_agent_templates.py || echo "Template init failed") && uvicorn app:app --host 0.0.0.0 --port $PORT
 
 
agents_config.json DELETED
@@ -1,147 +0,0 @@
1
- {
2
- "templates": [
3
- {
4
- "template_name": "preprocessing_agent",
5
- "display_name": "Data Preprocessing Agent",
6
- "description": "Cleans and prepares a DataFrame using Pandas and NumPy—handles missing values, detects column types, and converts date strings to datetime",
7
- "icon_url": "/icons/templates/preprocessing_agent.svg",
8
- "category": "Data Manipulation",
9
- "is_premium_only": false,
10
- "variant_type": "individual",
11
- "base_agent": "preprocessing_agent",
12
- "is_active": true,
13
- "prompt_template": "You are a AI data-preprocessing agent. Generate clean and efficient Python code using NumPy and Pandas to perform introductory data preprocessing on a pre-loaded DataFrame df, based on the user's analysis goals.\nPreprocessing Requirements:\n1. Identify Column Types\n- Separate columns into numeric and categorical using:\n categorical_columns = df.select_dtypes(include=[object, 'category']).columns.tolist()\n numeric_columns = df.select_dtypes(include=[np.number]).columns.tolist()\n2. Handle Missing Values\n- Numeric columns: Impute missing values using the mean of each column\n- Categorical columns: Impute missing values using the mode of each column\n3. Convert Date Strings to Datetime\n- For any column suspected to represent dates (in string format), convert it to datetime using:\n def safe_to_datetime(date):\n try:\n return pd.to_datetime(date, errors='coerce', cache=False)\n except (ValueError, TypeError):\n return pd.NaT\n df['datetime_column'] = df['datetime_column'].apply(safe_to_datetime)\n- Replace 'datetime_column' with the actual column names containing date-like strings\nImportant Notes:\n- Do NOT create a correlation matrix — correlation analysis is outside the scope of preprocessing\n- Do NOT generate any plots or visualizations\nOutput Instructions:\n1. Include the full preprocessing Python code\n2. Provide a brief bullet-point summary of the steps performed. Example:\n• Identified 5 numeric and 4 categorical columns\n• Filled missing numeric values with column means\n• Filled missing categorical values with column modes\n• Converted 1 date column to datetime format\n Respond in the user's language for all summary and reasoning but keep the code in english"
14
- },
15
- {
16
- "template_name": "planner_preprocessing_agent",
17
- "display_name": "Data Preprocessing Agent",
18
- "description": "Multi-agent planner variant: Cleans and prepares a DataFrame using Pandas and NumPy—handles missing values, detects column types, and converts date strings to datetime",
19
- "icon_url": "/icons/templates/preprocessing_agent.svg",
20
- "category": "Data Manipulation",
21
- "is_premium_only": false,
22
- "variant_type": "planner",
23
- "base_agent": "preprocessing_agent",
24
- "is_active": true,
25
- "prompt_template": "You are a data preprocessing agent optimized for multi-agent data analytics pipelines.\n\nYou are given:\n* A raw dataset (often just uploaded or loaded).\n* A user-defined goal (e.g., clean data for analysis, prepare for modeling).\n***plan_instructions** containing:\n ***'create'**: Variables you must create (e.g., ['df_cleaned', 'preprocessing_summary', 'column_types'])\n ***'use'**: Variables you must use (e.g., ['df', 'raw_data'])\n * **'instruction'**: Specific preprocessing instructions\n\n### Your Planner-Optimized Responsibilities:\n* **ALWAYS follow plan_instructions** - essential for pipeline data flow\n* Create ONLY the variables specified in plan_instructions['create']\n* Use ONLY the variables specified in plan_instructions['use']\n* Apply preprocessing as per plan_instructions['instruction']\n* Ensure cleaned data integrates seamlessly with downstream agents\n\n### Core Preprocessing Techniques:\n* Identify and categorize column types (numeric, categorical, datetime)\n* Handle missing values appropriately:\n - Numeric: impute with mean, median, or specified strategy\n - Categorical: impute with mode or specified strategy\n* Convert date strings to datetime format with proper error handling\n* Remove duplicates and handle data quality issues\n* Apply data type optimizations for memory efficiency\n* Create preprocessing summaries for pipeline transparency\n\n### Multi-Agent Best Practices:\n* Use exact variable names from plan_instructions['create']\n* Ensure data format compatibility for downstream agents\n* Maintain data integrity and schema consistency\n* Document preprocessing steps for pipeline reproducibility\n\n### Output:\n* Python code implementing preprocessing per plan_instructions\n* Summary of data cleaning and transformation operations\n* Focus on seamless integration with analysis and modeling agents\n\nRespond in the user's language for all summary and reasoning but keep the code in english"
26
- },
27
- {
28
- "template_name": "statistical_analytics_agent",
29
- "display_name": "Statistical Analytics Agent",
30
- "description": "Performs statistical analysis (e.g., regression, seasonal decomposition) using statsmodels, with proper handling of categorical data and missing values",
31
- "icon_url": "/icons/templates/statsmodel.svg",
32
- "category": "Data Modelling",
33
- "is_premium_only": false,
34
- "variant_type": "individual",
35
- "base_agent": "statistical_analytics_agent",
36
- "is_active": true,
37
- "prompt_template": "You are a statistical analytics agent. Your task is to take a dataset and a user-defined goal and output Python code that performs the appropriate statistical analysis to achieve that goal. Follow these guidelines:\nIMPORTANT: You may be provided with previous interaction history. The section marked \"### Current Query:\" contains the user's current request. Any text in \"### Previous Interaction History:\" is for context only and is NOT part of the current request.\nData Handling:\nAlways handle strings as categorical variables in a regression using statsmodels C(string_column).\nDo not change the index of the DataFrame.\nConvert X and y into float when fitting a model.\nError Handling:\nAlways check for missing values and handle them appropriately.\nEnsure that categorical variables are correctly processed.\nProvide clear error messages if the model fitting fails.\nRegression:\nFor regression, use statsmodels and ensure that a constant term is added to the predictor using sm.add_constant(X).\nHandle categorical variables using C(column_name) in the model formula.\nFit the model with model = sm.OLS(y.astype(float), X.astype(float)).fit().\nSeasonal Decomposition:\nEnsure the period is set correctly when performing seasonal decomposition.\nVerify the number of observations works for the decomposition.\nOutput:\nEnsure the code is executable and as intended.\nAlso choose the correct type of model for the problem\nAvoid adding data visualization code.\nProvide a concise bullet-point summary of the statistical analysis performed.\n\nExample Summary:\n• Applied linear regression with OLS to predict house prices based on 5 features\n• Model achieved R-squared of 0.78\n• Significant predictors include square footage (p<0.001) and number of bathrooms (p<0.01)\n• Detected strong seasonal pattern with 12-month periodicity\n• Forecast shows 15% growth trend over next quarter\nRespond in the user's language for all summary and reasoning but keep the code in english"
38
- },
39
- {
40
- "template_name": "planner_statistical_analytics_agent",
41
- "display_name": "Statistical Analytics Agent",
42
- "description": "Multi-agent planner variant: Performs statistical analysis (e.g., regression, seasonal decomposition) using statsmodels, with proper handling of categorical data and missing values",
43
- "icon_url": "/icons/templates/statsmodel.svg",
44
- "category": "Data Modelling",
45
- "is_premium_only": false,
46
- "variant_type": "planner",
47
- "base_agent": "statistical_analytics_agent",
48
- "is_active": true,
49
- "prompt_template": "You are tasked with performing statistical analysis on datasets based on provided structured inputs. Ensure comprehensive results by following these detailed instructions carefully:\n\n### Input Format:\nYou will receive structured input, which includes:\n1. **Dataset Description**:\n - Overview of the dataset, including its purpose and key columns (types, etc.).\n - Specific preprocessing instructions for each column, particularly for data type conversions and missing value handling.\n\n2. **Analytical Goal**:\n - A clearly defined goal, such as generating specific insights, performing calculations, or summarizing the data.\n\n3. **Plan Instructions**:\n - Detailed actions that should be taken, outlining what variables to create, which existing variables to use, and any other necessary processing steps.\n\n### Key Responsibilities:\n1. **Data Preprocessing**:\n - Inspect columns for needed preprocessing according to the dataset description provided.\n - Implement preprocessing as specified, including handling categorical variables with appropriate encoding (e.g., one-hot encoding).\n\n2. **Statistical Analysis**:\n - Conduct analysis based on the defined goal, which may involve:\n - Descriptive statistics (means, medians, etc.).\n - Correlation analysis to understand relationships among numerical variables.\n - Calculation of specific metrics described in the task.\n - Utilize libraries such as `pandas` for data manipulation and `numpy` for numerical operations.\n\n3. **Output**:\n - Results must be presented in a structured and organized text format, integrating all specified variables into the final report.\n - Avoid creating any intermediates that are not specified in the plan instructions.\n\n4. **Error Handling**:\n - Integrate error checks to confirm that all requisite variables are well defined and valid prior to executing operations.\n - Address edge cases, including situations where DataFrames may be empty or lack the necessary columns.\n\n5. **Documentation**:\n - Summarize all findings succinctly, detailing:\n - Key statistical outcomes, highlighting identifiable trends or relationships.\n - Potential data quality issues, such as missing values or outliers.\n\n### Analytical Methodology:\n- Always start with data cleaning, ensuring that missing values are handled as specified (e.g., filling with mean or median) and outlier checks are sufficient.\n- When performing statistical analysis, use measures that facilitate understanding of data distributions, such as means, medians, and standard deviations, as well as categorizations based on quantitative thresholds.\n- Implement segmentation strategies based on calculated scores, specify the thresholds clearly for different segments, and ensure that insights can lead to actionable outcomes.\n- Include plots where required, and ensure they are prepared in a separate stage, if indicated in the plan.\n\n### Important Notes:\n- Do not modify data indexes unless instructed; maintain the integrity of the dataset structure throughout.\n- Ensure all numerical data is converted to the appropriate types prior to analysis.\n- In the event that visualizations are indicated, prepare these in a separate task as per the capabilities outlined.\n\nBy adhering to these instructions meticulously, you will deliver consistent and high-quality analytical insights tailored to the provided datasets." },
50
- {
51
- "template_name": "data_viz_agent",
52
- "display_name": "Data Visualization Agent",
53
- "description": "Creates interactive data visualizations using Plotly with advanced styling and formatting options",
54
- "icon_url": "/icons/templates/plotly.svg",
55
- "category": "Data Visualization",
56
- "is_premium_only": false,
57
- "variant_type": "individual",
58
- "base_agent": "data_viz_agent",
59
- "is_active": true,
60
- "prompt_template": "You are an AI agent responsible for generating interactive data visualizations using Plotly.\nIMPORTANT Instructions:\n- The section marked \"### Current Query:\" contains the user's request. Any text in \"### Previous Interaction History:\" is for context only and should NOT be treated as part of the current request.\n- You must only use the tools provided to you. This agent handles visualization only.\n- If len(df) > 50000, always sample the dataset before visualization using: \nif len(df) > 50000: \n df = df.sample(50000, random_state=1)\n- Each visualization must be generated as a **separate figure** using go.Figure(). \nDo NOT use subplots under any circumstances.\n- Each figure must be returned individually using: \nfig.to_html(full_html=False)\n- Use update_layout with xaxis and yaxis **only once per figure**.\n- Enhance readability and clarity by: \n• Using low opacity (0.4-0.7) where appropriate \n• Applying visually distinct colors for different elements or categories \n- Make sure the visual **answers the user's specific goal**: \n• Identify what insight or comparison the user is trying to achieve \n• Choose the visualization type and features (e.g., color, size, grouping) to emphasize that goal \n• For example, if the user asks for \"trends in revenue,\" use a time series line chart; if they ask for \"top-performing categories,\" use a bar chart sorted by value \n• Prioritize highlighting patterns, outliers, or comparisons relevant to the question\n- Never include the dataset or styling index in the output.\n- If there are no relevant columns for the requested visualization, respond with: \n\"No relevant columns found to generate this visualization.\"\n- Use only one number format consistently: either 'K', 'M', or comma-separated values like 1,000/1,000,000. Do not mix formats.\n- Only include trendlines in scatter plots if the user explicitly asks for them.\n- Output only the code and a concise bullet-point summary of what the visualization reveals.\n- Always end each visualization with: \nfig.to_html(full_html=False)\nRespond in the user's language for all summary and reasoning but keep the code in english"
61
- },
62
- {
63
- "template_name": "sk_learn_agent",
64
- "display_name": "Machine Learning Agent",
65
- "description": "Trains and evaluates machine learning models using scikit-learn, including classification, regression, and clustering with feature importance insights",
66
- "icon_url": "/icons/templates/sk_learn_agent.svg",
67
- "category": "Data Modelling",
68
- "is_premium_only": false,
69
- "variant_type": "individual",
70
- "base_agent": "sk_learn_agent",
71
- "is_active": true,
72
- "prompt_template": "You are a machine learning agent. \nYour task is to take a dataset and a user-defined goal, and output Python code that performs the appropriate machine learning analysis to achieve that goal. \nYou should use the scikit-learn library.\nIMPORTANT: You may be provided with previous interaction history. The section marked \"### Current Query:\" contains the user's current request. Any text in \"### Previous Interaction History:\" is for context only and is NOT part of the current request.\nMake sure your output is as intended!\nProvide a concise bullet-point summary of the machine learning operations performed.\n\nExample Summary:\n• Trained a Random Forest classifier on customer churn data with 80/20 train-test split\n• Model achieved 92% accuracy and 88% F1-score\n• Feature importance analysis revealed that contract length and monthly charges are the strongest predictors of churn\n• Implemented K-means clustering (k=4) on customer shopping behaviors\n• Identified distinct segments: high-value frequent shoppers (22%), occasional big spenders (35%), budget-conscious regulars (28%), and rare visitors (15%)\nRespond in the user's language for all summary and reasoning but keep the code in english"
73
- },
74
- {
75
- "template_name": "planner_data_viz_agent",
76
- "display_name": "Data Visualization Agent",
77
- "description": "Multi-agent planner variant: Creates interactive data visualizations using Plotly with advanced styling and formatting options",
78
- "icon_url": "/icons/templates/plotly.svg",
79
- "category": "Data Visualization",
80
- "is_premium_only": false,
81
- "variant_type": "planner",
82
- "base_agent": "data_viz_agent",
83
- "is_active": true,
84
- "prompt_template": "You are a data visualization agent designed to generate effective visualizations based on user-defined goals and specific datasets provided in a structured format. Your enhanced responsibilities and necessary details for best practices are as follows:\n\n### Input Format:\n1. **Dataset**: Provided in JSON or Pandas DataFrame format, detailing its structure and attributes, including column types, preprocessing requirements, and guidelines on handling missing values.\n\n2. **Goal**: A clear statement that defines the analytical objectives for visualization (e.g., performance analysis, relationship discovery, or data clustering).\n\n3. **Plan Instructions**: Specific directives from an analytical planner regarding analysis creation, dataset usage, and additional plotting notes.\n\n4. **Styling Index**: Contains visual preferences for the plots, axis specifications, formatting requirements, and any template references.\n\n### Responsibilities:\n1. **Data Handling**:\n - Confirm the presence of necessary data variables before proceeding.\n - If datasets exceed 50,000 rows, sample them down to 5,000 rows for efficiency.\n - Check for missing values in crucial columns and address them according to preprocessing instructions (e.g., mean or median imputation).\n - Ensure that columns have consistent lengths, especially those involved in calculations.\n\n2. **Visualization Creation**:\n - Utilize Plotly and Matplotlib for visualization, focusing on user-defined goals and creation instructions from the plan.\n - Generate multiple relevant visualizations based on specific goals, potentially including bar charts, histograms, scatter plots, word clouds, or heatmaps as dictated by the task requirements.\n - Implement text processing techniques for natural language data (e.g., removing special characters while preserving language integrity).\n - For datasets comprising categorical variables, ensure they are handled correctly, including appropriate encoding of categorical features and filling in missing data with default categories.\n\n3. **Layout and Styling**:\n - Follow the provided styling index for clarity and aesthetics, ensuring cohesive axis formatting and color usage.\n - Use `update_yaxes` and `update_xaxes` for effective axis presentation, maintaining a uniform look across visualizations.\n\n4. **Error Handling**:\n - If essential variables are missing or if there are mismatched array lengths, return clear error messages indicating the specific issues (e.g., DataFrame not defined Column missing).\n - Address any ambiguities in input formats and expectations proactively rather than making unfounded assumptions.\n\n5. **Output**:\n - Visualizations must be displayed using the appropriate methods such as `.show()` or `fig.to_html(full_html=False)` for seamless HTML rendering.\n - Each visualization should include comprehensive legends or annotations where applicable, helping to clarify complex data stories.\n\n### Domain-Specific Considerations:\n- **Text Data**: When handling natural language data, particularly in non-English languages, use regular expressions to efficiently clean and preprocess text while preserving linguistic characteristics. This includes maintaining sentiments or specific keywords.\n- **Performance Metrics Analysis**: For performance-related KPI analysis, include methods for detecting outliers and normalizing scores to facilitate comparisons across different datasets or campaigns.\n- **Word Cloud Creation**: When generating word clouds, ensure to create distinct visual representations for different categories (questions vs. answers) and apply suitable color schemes to enhance differentiation.\n\n### Performance and Clarity:\n- Clean and preprocess data according to the details provided in the input descriptions.\n- Aim to visualize insights simply and clearly, emphasizing ease of understanding.\n- Strictly adhere to any specific instructions from the styling index, keeping the target audience's comprehension in mind when designing visual representations."},
85
- {
86
- "template_name": "planner_sk_learn_agent",
87
- "display_name": "Machine Learning Agent",
88
- "description": "Multi-agent planner variant: Trains and evaluates machine learning models using scikit-learn, including classification, regression, and clustering with feature importance insights",
89
- "icon_url": "/icons/templates/sk_learn_agent.svg",
90
- "category": "Data Modelling",
91
- "is_premium_only": false,
92
- "variant_type": "planner",
93
- "base_agent": "sk_learn_agent",
94
- "is_active": true,
95
- "prompt_template": "**Agent Definition:**\nYou are a machine learning agent in a multi-agent data analytics pipeline.\nYou are given:\n* A dataset (often cleaned and feature-engineered).\n* A user-defined goal (e.g., classification, regression, clustering).\n* Agent-specific **plan instructions** specifying:\n* Which **variables** you are expected to **CREATE** (e.g., `trained_model`, `predictions`).\n* Which **variables** you will **USE** (e.g., `df_cleaned`, `target_variable`, `feature_columns`).\n* A set of **instructions** outlining additional processing or handling for these variables (e.g., handling missing values, applying transformations, or other task-specific guidelines).\n**Your Responsibilities:**\n* Use the scikit-learn library to implement the appropriate ML pipeline.\n* Always split data into training and testing sets where applicable.\n* Use `print()` for all outputs.\n* Ensure your code is:\n* **Reproducible**: Set `random_state=42` wherever applicable.\n* **Modular**: Avoid deeply nested code.\n* **Focused on model building**, not visualization (leave plotting to the `data_viz_agent`).\n**You must not:**\n* Visualize anything (that's another agent's job).\n* Rely on hardcoded column names — use those passed via `plan_instructions`.\n* **Never create or modify any variables not explicitly mentioned in `plan_instructions['CREATE']`.**\n* **Never create the `df` variable**. You will **only** work with the variables passed via the `plan_instructions`.\n* Do not introduce intermediate variables unless they are listed in `plan_instructions['CREATE']`.\n**Instructions to Follow:**\n1. **CREATE** only the variables specified in the `plan_instructions['CREATE']` list. Do not create any intermediate or new variables.\n2. **USE** only the variables specified in the `plan_instructions['USE']` list. You are **not allowed** to create or modify any variables not listed in the plan instructions.\n3. Follow any **processing instructions** in the `plan_instructions['INSTRUCTIONS']` list. This might include tasks like handling missing values, scaling features, or encoding categorical variables. Always perform these steps on the variables specified in the `plan_instructions`.\n4. Do **not reassign or modify** any variables passed via `plan_instructions`. These should be used as-is.\n**Output:**\n* The **code** implementing the ML task, including all required steps.\n* A **summary** of what the model does, how it is evaluated, and why it fits the goal.\n* Respond in the user's language for all summary and reasoning but keep the code in english"
96
- },
97
- {
98
- "template_name": "feature_engineering_agent",
99
- "display_name": "Feature Engineering Agent",
100
- "description": "Advanced feature creation and selection for machine learning pipelines using various encoding and transformation techniques",
101
- "icon_url": "/icons/templates/feature-engineering.png",
102
- "category": "Data Modelling",
103
- "is_premium_only": true,
104
- "variant_type": "individual",
105
- "base_agent": "feature_engineering_agent",
106
- "is_active": true,
107
- "prompt_template": "You are a feature engineering expert for machine learning pipelines. Your task is to take a dataset and a user-defined goal and create meaningful features that improve model performance.\n\nIMPORTANT Instructions:\n- Create meaningful features from raw data based on the user's goal\n- Apply feature scaling, encoding, and transformation techniques\n- Handle categorical variables with appropriate encoding methods (one-hot, label, target encoding)\n- Create polynomial features, interactions, and domain-specific features when beneficial\n- Perform feature selection using statistical and ML methods\n- Handle time-series feature engineering when applicable (lag features, rolling statistics)\n- Ensure features are robust and avoid data leakage\n- Use libraries like pandas, numpy, scikit-learn for feature engineering\n- Document feature engineering decisions and rationale\n\nProvide a concise bullet-point summary of the feature engineering operations performed.\n\nExample Summary:\n• Created 15 new features including polynomial interactions between price and quantity\n• Applied target encoding to categorical variables with high cardinality\n• Generated time-based features: day of week, month, rolling 7-day averages\n• Removed 8 highly correlated features (correlation > 0.95)\n• Applied StandardScaler to numerical features for model compatibility\n• Final feature set: 23 features with improved signal-to-noise ratio\n\nRespond in the user's language for all summary and reasoning but keep the code in english"
108
- },
109
- {
110
- "template_name": "planner_feature_engineering_agent",
111
- "display_name": "Feature Engineering Agent",
112
- "description": "Multi-agent planner variant: Advanced feature creation and selection for machine learning pipelines using various encoding and transformation techniques",
113
- "icon_url": "/icons/templates/feature-engineering.png",
114
- "category": "Data Modelling",
115
- "is_premium_only": true,
116
- "variant_type": "planner",
117
- "base_agent": "feature_engineering_agent",
118
- "is_active": true,
119
- "prompt_template": "You are a feature engineering expert optimized for multi-agent data analytics pipelines.\n\nYou are given:\n* A dataset (often raw or lightly processed).\n* A user-defined goal (e.g., improve model performance, create specific feature types).\n* **plan_instructions** containing:\n * **'create'**: Variables you must create (e.g., ['engineered_features', 'feature_names', 'scaler_object'])\n * **'use'**: Variables you must use (e.g., ['raw_data', 'target_column'])\n * **'instruction'**: Specific feature engineering instructions\n\n### Your Planner-Optimized Responsibilities:\n* **ALWAYS follow plan_instructions** - essential for pipeline coordination\n* Create ONLY the variables specified in plan_instructions['create']\n* Use ONLY the variables specified in plan_instructions['use']\n* Apply feature engineering techniques as per plan_instructions['instruction']\n* Ensure engineered features integrate seamlessly with downstream ML agents\n\n### Feature Engineering Techniques:\n* Categorical encoding (one-hot, label, target encoding)\n* Numerical transformations (scaling, normalization, polynomial features)\n* Time-series features (lag features, rolling statistics, temporal patterns)\n* Feature selection and dimensionality reduction\n* Interaction features and domain-specific feature creation\n* Handle missing values and outliers appropriately\n\n### Multi-Agent Best Practices:\n* Use exact variable names from plan_instructions['create']\n* Ensure feature compatibility for downstream agents\n* Maintain data integrity and prevent leakage\n* Document feature engineering decisions for pipeline transparency\n\n### Output:\n* Python code implementing feature engineering per plan_instructions\n* Summary of features created and transformations applied\n* Focus on seamless integration with ML modeling agents\n\nRespond in the user's language for all summary and reasoning but keep the code in english"
120
- },
121
- {
122
- "template_name": "polars_agent",
123
- "display_name": "Polars Agent",
124
- "description": "High-performance data processing using Polars for large datasets with lazy evaluation and efficient memory usage",
125
- "icon_url": "/icons/templates/polars_github_logo_rect_dark_name.svg",
126
- "category": "Data Manipulation",
127
- "is_premium_only": true,
128
- "variant_type": "individual",
129
- "base_agent": "polars_agent",
130
- "is_active": true,
131
- "prompt_template": "You are a Polars expert for high-performance data processing. Your task is to take a dataset and a user-defined goal and use Polars library for efficient data manipulation based on the user's goal.\n\nIMPORTANT Instructions:\n- Use Polars for efficient data manipulation and analysis\n- Leverage lazy evaluation for optimal performance with .lazy() and .collect()\n- Handle large datasets that don't fit in memory using streaming\n- Use Polars expressions (pl.col, pl.when, etc.) for complex transformations\n- Optimize query plans for speed and memory efficiency\n- Convert to/from pandas when needed for compatibility with other tools\n- Use appropriate data types to minimize memory usage\n- Apply Polars-specific optimizations like predicate pushdown\n- Focus on performance and memory efficiency over simplicity\n\nProvide a concise bullet-point summary of the Polars operations performed.\n\nExample Summary:\n• Processed 10M row dataset using lazy evaluation for memory efficiency\n• Applied complex filtering and aggregations with 5x speedup vs pandas\n• Used Polars expressions for vectorized string operations\n• Implemented window functions for time-series calculations\n• Optimized memory usage by selecting appropriate dtypes (reduced from 2GB to 500MB)\n• Final output: clean, aggregated dataset ready for analysis\n\nRespond in the user's language for all summary and reasoning but keep the code in english"
132
- },
133
- {
134
- "template_name": "planner_polars_agent",
135
- "display_name": "Polars Agent",
136
- "description": "Multi-agent planner variant: High-performance data processing using Polars for large datasets with lazy evaluation and efficient memory usage",
137
- "icon_url": "https://raw.githubusercontent.com/pola-rs/polars-static/master/logos/polars_github_logo_rect_dark_name.svg",
138
- "category": "Data Manipulation",
139
- "is_premium_only": true,
140
- "variant_type": "planner",
141
- "base_agent": "polars_agent",
142
- "is_active": true,
143
- "prompt_template": "You are a Polars expert optimized for multi-agent data processing pipelines.\n\nYou are given:\n* A dataset (often large or complex).\n* A user-defined goal (e.g., data transformation, aggregation, filtering).\n* **plan_instructions** containing:\n * **'create'**: Variables you must create (e.g., ['processed_data', 'summary_stats'])\n * **'use'**: Variables you must use (e.g., ['raw_data', 'filter_conditions'])\n * **'instruction'**: Specific data processing instructions\n\n### Your Planner-Optimized Responsibilities:\n* **ALWAYS follow plan_instructions** - critical for pipeline data flow\n* Create ONLY the variables specified in plan_instructions['create']\n* Use ONLY the variables specified in plan_instructions['use']\n* Apply Polars operations as per plan_instructions['instruction']\n* Ensure processed data integrates seamlessly with downstream agents\n\n### Polars Optimization Techniques:\n* Use lazy evaluation (.lazy().collect()) for memory efficiency\n* Apply predicate pushdown and projection pushdown optimizations\n* Leverage Polars expressions for vectorized operations\n* Use appropriate data types to minimize memory footprint\n* Implement streaming for datasets larger than memory\n* Convert to pandas DataFrame only when required by downstream agents\n\n### Multi-Agent Best Practices:\n* Use exact variable names from plan_instructions['create']\n* Ensure data format compatibility for subsequent agents\n* Maintain data integrity and schema consistency\n* Optimize for both speed and memory usage in pipeline context\n\n### Output:\n* Python code implementing Polars operations per plan_instructions\n* Summary of data processing and optimizations applied\n* Focus on high-performance data flow in multi-agent pipeline\n\nRespond in the user's language for all summary and reasoning but keep the code in english"
144
- }
145
- ],
146
- "remove": []
147
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,2888 +1,820 @@
1
  # Standard library imports
2
-
3
  import asyncio
4
-
5
  import json
6
-
7
  import logging
8
-
9
  import os
10
-
11
  import time
12
-
13
  import uuid
14
-
15
  from io import StringIO
16
-
17
  from typing import List, Optional
18
 
19
- import ast
20
-
21
- import markdown
22
-
23
- from bs4 import BeautifulSoup
24
-
25
- import pandas as pd
26
-
27
- from datetime import datetime, UTC
28
-
29
  # Third-party imports
30
-
 
31
  import uvicorn
32
-
33
  from dotenv import load_dotenv
34
-
35
  from fastapi import (
36
-
37
  Depends,
38
-
39
  FastAPI,
40
-
41
  File,
42
-
43
  Form,
44
-
45
  HTTPException,
46
-
47
  Request,
48
-
49
  UploadFile
50
-
51
  )
52
-
53
  from fastapi.middleware.cors import CORSMiddleware
54
-
55
  from fastapi.responses import JSONResponse, StreamingResponse
56
-
57
  from fastapi.security import APIKeyHeader
58
-
59
  from pydantic import BaseModel
60
 
61
-
62
-
63
  # Local application imports
64
-
65
  from scripts.format_response import format_response_to_markdown
66
-
67
  from src.agents.agents import *
68
-
69
  from src.agents.retrievers.retrievers import *
70
-
71
  from src.managers.ai_manager import AI_Manager
72
-
73
  from src.managers.session_manager import SessionManager
74
-
75
- from src.managers.app_manager import AppState
76
-
77
  from src.routes.analytics_routes import router as analytics_router
78
-
79
- from src.routes.blog_routes import router as blog_router
80
-
81
  from src.routes.chat_routes import router as chat_router
82
-
83
  from src.routes.code_routes import router as code_router
84
-
85
- from src.routes.feedback_routes import router as feedback_router
86
-
87
  from src.routes.session_routes import router as session_router, get_session_id_dependency
88
-
89
- from src.routes.deep_analysis_routes import router as deep_analysis_router
90
-
91
- from src.routes.templates_routes import router as templates_router
92
-
93
- from src.schemas.query_schema import QueryRequest
94
-
95
  from src.utils.logger import Logger
96
 
97
- from src.routes.session_routes import apply_model_safeguards
98
-
99
-
100
- # Import deep analysis components directly
101
-
102
- # from src.agents.try_deep_agents import deep_analysis_module
103
-
104
- from src.agents.deep_agents import deep_analysis_module
105
-
106
- from src.utils.generate_report import generate_html_report
107
-
108
-
109
-
110
- from src.utils.model_registry import MODEL_OBJECTS
111
-
112
-
113
-
114
- logger = Logger("app", see_time=True, console_log=True)
115
 
 
116
  load_dotenv()
117
 
118
-
119
-
120
- # Request models
121
-
122
- class DeepAnalysisRequest(BaseModel):
123
-
124
- goal: str
125
-
126
-
127
-
128
- class DeepAnalysisResponse(BaseModel):
129
-
130
- goal: str
131
-
132
- deep_questions: str
133
-
134
- deep_plan: str
135
-
136
- summaries: List[str]
137
-
138
- code: str
139
-
140
- plotly_figs: List
141
-
142
- synthesis: List[str]
143
-
144
- final_conclusion: str
145
-
146
- html_report: Optional[str] = None
147
-
148
-
149
-
150
- styling_instructions = [
151
- {
152
- "category": "line_charts",
153
- "description": "Used to visualize trends and changes over time, often with multiple series.",
154
- "styling": {
155
- "template": "plotly_white",
156
- "axes_line_width": 0.2,
157
- "grid_width": 1,
158
- "title": {
159
- "bold_html": True,
160
- "include": True
161
- },
162
- "colors": "use multiple colors if more than one line",
163
- "annotations": ["min", "max"],
164
- "number_format": {
165
- "apply_k_m": True,
166
- "thresholds": {"K": 1000, "M": 100000},
167
- "percentage_decimals": 2,
168
- "percentage_sign": True
169
- },
170
- "default_size": {"height": 1200, "width": 1000}
171
- }
172
- },
173
- {
174
- "category": "bar_charts",
175
- "description": "Useful for comparing discrete categories or groups with bars representing values.",
176
- "styling": {
177
- "template": "plotly_white",
178
- "axes_line_width": 0.2,
179
- "grid_width": 1,
180
- "title": {"bold_html": True, "include": True},
181
- "annotations": ["bar values"],
182
- "number_format": {
183
- "apply_k_m": True,
184
- "thresholds": {"K": 1000, "M": 100000},
185
- "percentage_decimals": 2,
186
- "percentage_sign": True
187
- },
188
- "default_size": {"height": 1200, "width": 1000}
189
- }
190
- },
191
- {
192
- "category": "histograms",
193
- "description": "Display the distribution of a data set, useful for returns or frequency distributions.",
194
- "styling": {
195
- "template": "plotly_white",
196
- "bin_size": 50,
197
- "axes_line_width": 0.2,
198
- "grid_width": 1,
199
- "title": {"bold_html": True, "include": True},
200
- "annotations": ["x values"],
201
- "number_format": {
202
- "apply_k_m": True,
203
- "thresholds": {"K": 1000, "M": 100000},
204
- "percentage_decimals": 2,
205
- "percentage_sign": True
206
- },
207
- "default_size": {"height": 1200, "width": 1000}
208
- }
209
- },
210
- {
211
- "category": "pie_charts",
212
- "description": "Show composition or parts of a whole with slices representing categories.",
213
- "styling": {
214
- "template": "plotly_white",
215
- "top_categories_to_show": 10,
216
- "bundle_rest_as": "Others",
217
- "axes_line_width": 0.2,
218
- "grid_width": 1,
219
- "title": {"bold_html": True, "include": True},
220
- "annotations": ["x values"],
221
- "number_format": {
222
- "apply_k_m": True,
223
- "thresholds": {"K": 1000, "M": 100000},
224
- "percentage_decimals": 2,
225
- "percentage_sign": True
226
- },
227
- "default_size": {"height": 1200, "width": 1000}
228
- }
229
- },
230
- {
231
- "category": "tabular_and_generic_charts",
232
- "description": "Applies to charts where number formatting needs flexibility, including mixed or raw data.",
233
- "styling": {
234
- "template": "plotly_white",
235
- "axes_line_width": 0.2,
236
- "grid_width": 1,
237
- "title": {"bold_html": True, "include": True},
238
- "annotations": ["x values"],
239
- "number_format": {
240
- "apply_k_m": True,
241
- "thresholds": {"K": 1000, "M": 100000},
242
- "exclude_if_commas_present": True,
243
- "exclude_if_not_numeric": True,
244
- "percentage_decimals": 2,
245
- "percentage_sign": True
246
- },
247
- "default_size": {"height": 1200, "width": 1000}
248
- }
249
- },
250
- {
251
- "category": "heat_maps",
252
- "description": "Show data density or intensity using color scales on a matrix or grid.",
253
- "styling": {
254
- "template": "plotly_white",
255
- "axes_styles": {
256
- "line_color": "black",
257
- "line_width": 0.2,
258
- "grid_width": 1,
259
- "format_numbers_as_k_m": True,
260
- "exclude_non_numeric_formatting": True
261
- },
262
- "title": {"bold_html": True, "include": True},
263
- "default_size": {"height": 1200, "width": 1000}
264
- }
265
- },
266
- {
267
- "category": "histogram_distribution",
268
- "description": "Specialized histogram for return distributions with opacity control.",
269
- "styling": {
270
- "template": "plotly_white",
271
- "opacity": 0.75,
272
- "axes_styles": {
273
- "grid_width": 1,
274
- "format_numbers_as_k_m": True,
275
- "exclude_non_numeric_formatting": True
276
- },
277
- "title": {"bold_html": True, "include": True},
278
- "default_size": {"height": 1200, "width": 1000}
279
- }
280
- }
281
  ]
282
 
283
- # Convert to list of JSON strings
284
- styling_instructions = [str(chart_dict) for chart_dict in styling_instructions]
285
-
286
- # Output (just show first 2 for readability)
287
-
288
-
289
-
290
-
291
  # Add near the top of the file, after imports
292
-
293
  DEFAULT_MODEL_CONFIG = {
294
  "provider": os.getenv("MODEL_PROVIDER", "openai"),
295
- "model": os.getenv("MODEL_NAME", "gpt-5-mini"),
296
  "api_key": os.getenv("OPENAI_API_KEY"),
297
- "temperature": min(1.0, max(0.0, float(os.getenv("TEMPERATURE", "1.0")))), # Clamp to 0..1
298
- "max_tokens": int(os.getenv("MAX_TOKENS", 6000)), "cache": False
299
-
300
  }
301
 
302
-
303
-
304
  # Create default LM config but don't set it globally
305
-
306
-
307
-
308
- default_lm = MODEL_OBJECTS[DEFAULT_MODEL_CONFIG['model']]
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
- dspy.configure(lm=default_lm, async_max_workers=1000)
317
-
318
-
 
 
 
 
 
 
 
319
 
320
  # Function to get model config from session or use default
321
-
322
  def get_session_lm(session_state):
323
-
324
  """Get the appropriate LM instance for a session, or default if not configured"""
325
-
326
  # First check if we have a valid session-specific model config
327
-
328
  if session_state and isinstance(session_state, dict) and "model_config" in session_state:
329
-
330
  model_config = session_state["model_config"]
331
-
332
  if model_config and isinstance(model_config, dict) and "model" in model_config:
333
-
334
  # Found valid session-specific model config, use it
335
-
336
  provider = model_config.get("provider", "openai").lower()
337
-
338
- model_name = model_config.get("model", DEFAULT_MODEL_CONFIG["model"])
339
-
340
- # Import and apply centralized safeguards (temperature + max_tokens)
341
-
342
-
343
- requested_temp = model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"])
344
- requested_max = model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
345
-
346
- safe_params = apply_model_safeguards(
347
- model_name=model_name,
348
- provider=provider,
349
- temperature=requested_temp,
350
- max_tokens=requested_max,
351
- )
352
-
353
- # Apply the safeguarded parameters
354
- MODEL_OBJECTS[model_name].__dict__['kwargs']['max_tokens'] = safe_params["max_tokens"]
355
- MODEL_OBJECTS[model_name].__dict__['kwargs']['temperature'] = safe_params["temperature"]
356
-
357
-
358
-
 
 
 
 
 
 
359
 
360
-
361
  # If no valid session config, use default
362
-
363
- return MODEL_OBJECTS[model_name]
364
-
365
-
366
 
367
  # Initialize retrievers with empty data first
368
-
369
-
 
 
 
 
 
 
370
 
371
  # clear console
372
-
373
  def clear_console():
374
-
375
  os.system('cls' if os.name == 'nt' else 'clear')
376
 
377
 
378
-
379
-
380
-
381
  # Check for Housing.csv
382
-
383
  housing_csv_path = "Housing.csv"
384
-
385
  if not os.path.exists(housing_csv_path):
386
-
387
  logger.log_message(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}", level=logging.ERROR)
388
-
389
  raise FileNotFoundError(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}")
390
 
 
 
 
 
 
 
391
 
392
-
393
- # All agents are now loaded from database - no hardcoded dictionaries needed
394
-
395
-
 
 
396
 
397
  # Add session header
398
-
399
  X_SESSION_ID = APIKeyHeader(name="X-Session-ID", auto_error=False)
400
 
401
-
402
-
403
  # Update AppState class to use SessionManager
404
-
405
- # The AppState class is now in src.managers.app_manager
406
-
407
-
408
-
409
- # Initialize FastAPI app with state
410
-
411
- app = FastAPI(title="AI Analytics API", version="1.0")
412
-
413
- # Pass required parameters to AppState
414
- app.state = AppState(styling_instructions, chat_history_name_agent, DEFAULT_MODEL_CONFIG)
415
-
416
-
417
-
418
-
419
-
420
- # Configure middleware
421
-
422
- # Use a wildcard for local development or read from environment
423
-
424
- is_development = os.getenv("ENVIRONMENT", "development").lower() == "development"
425
-
426
-
427
-
428
- allowed_origins = []
429
-
430
- frontend_url = os.getenv("FRONTEND_URL", "").strip()
431
-
432
- print(f"FRONTEND_URL: {frontend_url}")
433
-
434
- if is_development:
435
-
436
- allowed_origins = ["*"]
437
-
438
- elif frontend_url:
439
-
440
- allowed_origins = [frontend_url]
441
-
442
- else:
443
-
444
- logger.log_message("CORS misconfigured: FRONTEND_URL not set", level=logging.ERROR)
445
-
446
- allowed_origins = [] # or set a default safe origin
447
-
448
-
449
-
450
- # Add a strict origin verification middleware
451
-
452
- @app.middleware("http")
453
-
454
- async def verify_origin_middleware(request: Request, call_next):
455
-
456
- # Skip origin check in development mode
457
-
458
- if is_development:
459
-
460
- return await call_next(request)
461
-
462
 
 
 
 
463
 
464
- # Get the origin from the request headers
 
 
465
 
466
- origin = request.headers.get("origin")
 
 
467
 
 
 
 
468
 
469
-
470
- # Log the origin for debugging
471
-
472
- if origin:
473
-
474
- print(f"Request from origin: {origin}")
475
-
476
 
477
-
478
- # If no origin header or origin not in allowed list, reject the request
479
-
480
- if origin and frontend_url and origin != frontend_url:
481
-
482
- print(f"Blocked request from unauthorized origin: {origin}")
483
-
484
- return JSONResponse(
485
-
486
- status_code=403,
487
-
488
- content={"detail": "Not authorized"}
489
-
490
- )
491
-
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
 
494
- # Continue processing the request if origin is allowed
495
-
496
- return await call_next(request)
497
-
498
-
499
-
500
- # CORS middleware (still needed for browser preflight)
501
 
 
502
  app.add_middleware(
503
-
504
  CORSMiddleware,
505
-
506
- allow_origins=allowed_origins,
507
-
508
- allow_origin_regex=None,
509
-
510
  allow_credentials=True,
511
-
512
  allow_methods=["*"],
513
-
514
  allow_headers=["*"],
515
-
516
- expose_headers=["*"],
517
-
518
- max_age=600 # Cache preflight requests for 10 minutes (for performance)
519
-
520
  )
521
 
522
-
523
-
524
  # Add these constants at the top of the file with other imports/constants
525
-
526
  RESPONSE_ERROR_INVALID_QUERY = "Please provide a valid query..."
527
-
528
  RESPONSE_ERROR_NO_DATASET = "No dataset is currently loaded. Please link a dataset before proceeding with your analysis."
529
-
530
  DEFAULT_TOKEN_RATIO = 1.5
531
-
532
- REQUEST_TIMEOUT_SECONDS = 90 # Timeout for LLM requests
533
-
534
- MAX_RECENT_MESSAGES = 5
535
-
536
  DB_BATCH_SIZE = 10 # For future batch DB operations
537
 
538
-
539
-
540
  @app.post("/chat/{agent_name}", response_model=dict)
541
-
542
  async def chat_with_agent(
543
-
544
  agent_name: str,
545
-
546
  request: QueryRequest,
547
-
548
  request_obj: Request,
549
-
550
  session_id: str = Depends(get_session_id_dependency)
551
-
552
  ):
553
-
554
  session_state = app.state.get_session_state(session_id)
555
-
556
- logger.log_message(f"[DEBUG] chat_with_agent called with agent: '{agent_name}', query: '{request.query[:100]}...'", level=logging.DEBUG)
557
-
558
 
559
-
560
  try:
561
-
562
  # Extract and validate query parameters
563
-
564
- logger.log_message(f"[DEBUG] Updating session from query params", level=logging.DEBUG)
565
-
566
  _update_session_from_query_params(request_obj, session_state)
567
-
568
- logger.log_message(f"[DEBUG] Session state after query params: user_id={session_state.get('user_id')}, chat_id={session_state.get('chat_id')}", level=logging.DEBUG)
569
-
570
 
571
-
572
  # Validate dataset and agent name
 
 
573
 
574
- if session_state["datasets"] is None:
575
- logger.log_message(f"[DEBUG] No dataset loaded", level=logging.DEBUG)
576
-
577
- raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
578
-
579
-
580
-
581
- # Log the dataset being used for analysis with detailed information
582
- datasets = session_state["datasets"]
583
- dataset_names = list(datasets.keys())
584
- if dataset_names:
585
- current_dataset_name = dataset_names[-1] # Get the last (most recent) dataset
586
- dataset_shape = datasets[current_dataset_name].shape
587
-
588
- # Check if this is the default dataset and explain why
589
- session_name = session_state.get("name", "")
590
- is_default_dataset = (current_dataset_name == "df" and session_name == "Housing.csv") or current_dataset_name == "Housing.csv"
591
-
592
- if is_default_dataset:
593
- logger.log_message(f"[ANALYSIS] Using DEFAULT dataset 'Housing.csv' for analysis (shape: {dataset_shape[0]} rows, {dataset_shape[1]} columns)", level=logging.INFO)
594
- logger.log_message(f"[ANALYSIS] Reason: No custom dataset uploaded yet - using default Housing.csv dataset", level=logging.INFO)
595
- else:
596
- logger.log_message(f"[ANALYSIS] Using CUSTOM dataset '{current_dataset_name}' for analysis (shape: {dataset_shape[0]} rows, {dataset_shape[1]} columns)", level=logging.INFO)
597
- logger.log_message(f"[ANALYSIS] This is a user-uploaded dataset, not the default", level=logging.INFO)
598
- else:
599
- logger.log_message(f"[ANALYSIS] No datasets available in session {session_id}", level=logging.WARNING)
600
-
601
- logger.log_message(f"[DEBUG] About to validate agent name: '{agent_name}'", level=logging.DEBUG)
602
-
603
- _validate_agent_name(agent_name, session_state)
604
-
605
- logger.log_message(f"[DEBUG] Agent validation completed successfully", level=logging.DEBUG)
606
-
607
 
608
-
609
  # Record start time for timing
610
-
611
  start_time = time.time()
612
-
613
 
614
-
615
  # Get chat context and prepare query
616
-
617
- logger.log_message(f"[DEBUG] Preparing query with context", level=logging.DEBUG)
618
-
619
  enhanced_query = _prepare_query_with_context(request.query, session_state)
620
-
621
- logger.log_message(f"[DEBUG] Enhanced query length: {len(enhanced_query)}", level=logging.DEBUG)
622
-
623
 
624
-
625
- # Initialize agent - handle standard, template, and custom agents
626
-
627
  if "," in agent_name:
628
-
629
- logger.log_message(f"[DEBUG] Processing multiple agents: {agent_name}", level=logging.DEBUG)
630
-
631
- # Multiple agents case
632
-
633
- agent_list = [agent.strip() for agent in agent_name.split(",")]
634
-
635
-
636
-
637
- # Categorize agents
638
-
639
- standard_agents = [agent for agent in agent_list if _is_standard_agent(agent)]
640
-
641
- template_agents = [agent for agent in agent_list if _is_template_agent(agent)]
642
-
643
- custom_agents = [agent for agent in agent_list if not _is_standard_agent(agent) and not _is_template_agent(agent)]
644
-
645
-
646
-
647
- logger.log_message(f"[DEBUG] Agent categorization - standard: {standard_agents}, template: {template_agents}, custom: {custom_agents}", level=logging.DEBUG)
648
-
649
-
650
-
651
- if custom_agents:
652
-
653
- # If any custom agents, use session AI system for all
654
-
655
- ai_system = session_state["ai_system"]
656
-
657
- session_lm = get_session_lm(session_state)
658
-
659
- logger.log_message(f"[DEBUG] Using custom agent execution path", level=logging.DEBUG)
660
-
661
- with dspy.context(lm=session_lm):
662
-
663
- response = await asyncio.wait_for(
664
-
665
- _execute_custom_agents(ai_system, agent_list, enhanced_query),
666
-
667
- timeout=REQUEST_TIMEOUT_SECONDS
668
-
669
- )
670
-
671
- logger.log_message(f"[DEBUG] Custom agents response type: {type(response)}, keys: {list(response.keys()) if isinstance(response, dict) else 'not a dict'}", level=logging.DEBUG)
672
-
673
- else:
674
-
675
- # All standard/template agents - use auto_analyst_ind which loads from DB
676
-
677
- user_id = session_state.get("user_id")
678
-
679
- logger.log_message(f"[DEBUG] Using auto_analyst_ind for multiple standard/template agents with user_id: {user_id}", level=logging.DEBUG)
680
-
681
-
682
-
683
- # Create database session for agent loading
684
-
685
- from src.db.init_db import session_factory
686
-
687
- db_session = session_factory()
688
-
689
- try:
690
-
691
- # auto_analyst_ind will load all agents from database
692
-
693
- logger.log_message(f"[DEBUG] Creating auto_analyst_ind instance", level=logging.DEBUG)
694
-
695
- agent = auto_analyst_ind(agents=[], retrievers=session_state["retrievers"], user_id=user_id, db_session=db_session)
696
-
697
- session_lm = get_session_lm(session_state)
698
-
699
- logger.log_message(f"[DEBUG] About to call agent.forward with query and agent list", level=logging.DEBUG)
700
-
701
- with dspy.context(lm=session_lm):
702
-
703
- response = await asyncio.wait_for(
704
-
705
- agent(enhanced_query, ",".join(agent_list)),
706
-
707
- timeout=REQUEST_TIMEOUT_SECONDS
708
-
709
- )
710
-
711
- logger.log_message(f"[DEBUG] auto_analyst_ind response type: {type(response)}, content: {str(response)[:200]}...", level=logging.DEBUG)
712
-
713
- finally:
714
-
715
- db_session.close()
716
-
717
  else:
718
-
719
- logger.log_message(f"[DEBUG] Processing single agent: {agent_name}", level=logging.DEBUG)
720
-
721
- # Single agent case
722
-
723
- if _is_standard_agent(agent_name) or _is_template_agent(agent_name):
724
-
725
- # Standard or template agent - use auto_analyst_ind which loads from DB
726
-
727
- user_id = session_state.get("user_id")
728
-
729
- logger.log_message(f"[DEBUG] Using auto_analyst_ind for single standard/template agent '{agent_name}' with user_id: {user_id}", level=logging.DEBUG)
730
-
731
-
732
-
733
- # Create database session for agent loading
734
-
735
- from src.db.init_db import session_factory
736
-
737
- db_session = session_factory()
738
-
739
- try:
740
-
741
- # auto_analyst_ind will load all agents from database
742
-
743
- logger.log_message(f"[DEBUG] Creating auto_analyst_ind instance for single agent", level=logging.DEBUG)
744
-
745
- agent = auto_analyst_ind(agents=[], retrievers=session_state["retrievers"], user_id=user_id, db_session=db_session)
746
-
747
- session_lm = get_session_lm(session_state)
748
-
749
- logger.log_message(f"[DEBUG] About to call agent.forward for single agent '{agent_name}'", level=logging.DEBUG)
750
-
751
- with dspy.context(lm=session_lm):
752
-
753
- response = await asyncio.wait_for(
754
-
755
- agent(enhanced_query, agent_name),
756
-
757
- timeout=REQUEST_TIMEOUT_SECONDS
758
-
759
- )
760
-
761
- logger.log_message(f"[DEBUG] Single agent response type: {type(response)}, content: {str(response)[:200]}...", level=logging.DEBUG)
762
-
763
- finally:
764
-
765
- db_session.close()
766
-
767
- else:
768
-
769
- # Custom agent - use session AI system
770
-
771
- ai_system = session_state["ai_system"]
772
-
773
- session_lm = get_session_lm(session_state)
774
-
775
- logger.log_message(f"[DEBUG] Using custom agent execution for '{agent_name}'", level=logging.DEBUG)
776
-
777
- with dspy.context(lm=session_lm):
778
-
779
- response = await asyncio.wait_for(
780
-
781
- _execute_custom_agents(ai_system, [agent_name], enhanced_query),
782
-
783
- timeout=REQUEST_TIMEOUT_SECONDS
784
-
785
- )
786
-
787
- logger.log_message(f"[DEBUG] Custom single agent response type: {type(response)}, content: {str(response)[:200]}...", level=logging.DEBUG)
788
-
789
 
790
-
791
- logger.log_message(f"[DEBUG] About to format response to markdown. Response type: {type(response)}", level=logging.DEBUG)
792
-
793
- formatted_response = format_response_to_markdown(response, agent_name, session_state["datasets"])
794
- logger.log_message(f"[DEBUG] Formatted response type: {type(formatted_response)}, length: {len(str(formatted_response))}", level=logging.DEBUG)
795
-
 
 
 
 
 
 
 
 
 
 
 
 
 
796
 
797
-
798
  if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
799
-
800
- logger.log_message(f"[DEBUG] Response was invalid query error", level=logging.DEBUG)
801
-
802
  return {
803
-
804
  "agent_name": agent_name,
805
-
806
  "query": request.query,
807
-
808
  "response": formatted_response,
809
-
810
  "session_id": session_id
811
-
812
  }
813
-
814
 
815
-
816
  # Track usage statistics
817
-
818
  if session_state.get("user_id"):
819
-
820
- logger.log_message(f"[DEBUG] Tracking model usage", level=logging.DEBUG)
821
-
822
  _track_model_usage(
823
-
824
  session_state=session_state,
825
-
826
  enhanced_query=enhanced_query,
827
-
828
  response=response,
829
-
830
  processing_time_ms=int((time.time() - start_time) * 1000)
831
-
832
  )
833
-
834
 
835
-
836
- logger.log_message(f"[DEBUG] chat_with_agent completed successfully", level=logging.DEBUG)
837
-
838
  return {
839
-
840
  "agent_name": agent_name,
841
-
842
  "query": request.query, # Return original query without context
843
-
844
  "response": formatted_response,
845
-
846
  "session_id": session_id
847
-
848
  }
849
-
850
  except HTTPException:
851
-
852
  # Re-raise HTTP exceptions to preserve status codes
853
-
854
- logger.log_message(f"[DEBUG] HTTPException caught and re-raised", level=logging.DEBUG)
855
-
856
  raise
857
-
858
- except asyncio.TimeoutError:
859
-
860
- logger.log_message(f"[ERROR] Timeout error in chat_with_agent", level=logging.ERROR)
861
-
862
- raise HTTPException(status_code=504, detail="Request timed out. Please try a simpler query.")
863
-
864
  except Exception as e:
865
-
866
- logger.log_message(f"[ERROR] Unexpected error in chat_with_agent: {str(e)}", level=logging.ERROR)
867
-
868
- logger.log_message(f"[ERROR] Exception type: {type(e)}, traceback: {str(e)}", level=logging.ERROR)
869
-
870
- import traceback
871
-
872
- logger.log_message(f"[ERROR] Full traceback: {traceback.format_exc()}", level=logging.ERROR)
873
-
874
  raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
875
-
876
-
877
-
878
-
879
-
880
  @app.post("/chat", response_model=dict)
881
-
882
  async def chat_with_all(
883
-
884
  request: QueryRequest,
885
-
886
  request_obj: Request,
887
-
888
  session_id: str = Depends(get_session_id_dependency)
889
-
890
  ):
891
-
892
  session_state = app.state.get_session_state(session_id)
893
 
894
-
895
-
896
  try:
897
-
898
  # Extract and validate query parameters
899
-
900
  _update_session_from_query_params(request_obj, session_state)
901
-
902
 
903
-
904
  # Validate dataset
905
-
906
- if session_state["datasets"] is None:
907
  raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
908
-
909
 
910
-
911
  if session_state["ai_system"] is None:
912
-
913
  raise HTTPException(status_code=500, detail="AI system not properly initialized.")
914
 
915
-
916
-
917
  # Get session-specific model
918
-
919
  session_lm = get_session_lm(session_state)
920
 
921
-
922
-
923
  # Create streaming response
924
-
925
  return StreamingResponse(
926
-
927
  _generate_streaming_responses(session_state, request.query, session_lm),
928
-
929
  media_type='text/event-stream',
930
-
931
  headers={
932
-
933
  'Cache-Control': 'no-cache',
934
-
935
  'Connection': 'keep-alive',
936
-
937
  'Content-Type': 'text/event-stream',
938
-
939
  'Access-Control-Allow-Origin': '*',
940
-
941
  'X-Accel-Buffering': 'no'
942
-
943
  }
944
-
945
  )
946
-
947
  except HTTPException:
948
-
949
  # Re-raise HTTP exceptions to preserve status codes
950
-
951
  raise
952
-
953
  except Exception as e:
954
-
955
  raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
956
 
957
 
958
-
959
-
960
-
961
  # Helper functions to reduce duplication and improve modularity
962
-
963
  def _update_session_from_query_params(request_obj: Request, session_state: dict):
964
-
965
  """Extract and validate chat_id and user_id from query parameters"""
966
-
967
  # Check for chat_id in query parameters
968
-
969
  if "chat_id" in request_obj.query_params:
970
-
971
  try:
972
-
973
  chat_id_param = int(request_obj.query_params.get("chat_id"))
974
-
975
  # Update session state with this chat ID
976
-
977
  session_state["chat_id"] = chat_id_param
978
-
979
  except (ValueError, TypeError):
980
-
981
  logger.log_message("Invalid chat_id parameter", level=logging.WARNING)
982
-
983
  # Continue without updating chat_id
984
 
985
-
986
-
987
  # Check for user_id in query parameters
988
-
989
  if "user_id" in request_obj.query_params:
990
-
991
  try:
992
-
993
  user_id = int(request_obj.query_params["user_id"])
994
-
995
  session_state["user_id"] = user_id
996
-
997
  except (ValueError, TypeError):
998
-
999
  raise HTTPException(
1000
-
1001
  status_code=400,
1002
-
1003
  detail="Invalid user_id in query params. Please provide a valid integer."
1004
-
1005
  )
1006
 
1007
 
1008
-
1009
-
1010
-
1011
- def _validate_agent_name(agent_name: str, session_state: dict = None):
1012
-
1013
- """Validate that the agent name(s) are available"""
1014
-
1015
- logger.log_message(f"[DEBUG] Validating agent name: '{agent_name}'", level=logging.DEBUG)
1016
-
1017
-
1018
-
1019
  if "," in agent_name:
1020
-
1021
- # Multiple agents
1022
-
1023
  agent_list = [agent.strip() for agent in agent_name.split(",")]
1024
-
1025
- logger.log_message(f"[DEBUG] Multiple agents detected: {agent_list}", level=logging.DEBUG)
1026
-
1027
  for agent in agent_list:
1028
-
1029
- is_available = _is_agent_available(agent, session_state)
1030
-
1031
- logger.log_message(f"[DEBUG] Agent '{agent}' availability: {is_available}", level=logging.DEBUG)
1032
-
1033
- if not is_available:
1034
-
1035
- available_agents = _get_available_agents_list(session_state)
1036
-
1037
- logger.log_message(f"[DEBUG] Agent '{agent}' not found. Available: {available_agents}", level=logging.DEBUG)
1038
-
1039
  raise HTTPException(
1040
-
1041
- status_code=400,
1042
-
1043
- detail=f"Agent '{agent}' not found. Available agents: {available_agents}"
1044
-
1045
  )
1046
-
1047
- else:
1048
-
1049
- # Single agent
1050
-
1051
- is_available = _is_agent_available(agent_name, session_state)
1052
-
1053
- logger.log_message(f"[DEBUG] Single agent '{agent_name}' availability: {is_available}", level=logging.DEBUG)
1054
-
1055
- if not is_available:
1056
-
1057
- available_agents = _get_available_agents_list(session_state)
1058
-
1059
- logger.log_message(f"[DEBUG] Agent '{agent_name}' not found. Available: {available_agents}", level=logging.DEBUG)
1060
-
1061
- raise HTTPException(
1062
-
1063
- status_code=400,
1064
-
1065
- detail=f"Agent '{agent_name}' not found. Available agents: {available_agents}"
1066
-
1067
- )
1068
-
1069
-
1070
-
1071
- logger.log_message(f"[DEBUG] Agent validation passed for: '{agent_name}'", level=logging.DEBUG)
1072
-
1073
-
1074
-
1075
- def _is_agent_available(agent_name: str, session_state: dict = None) -> bool:
1076
-
1077
- """Check if an agent is available (standard, template, or custom)"""
1078
-
1079
- # Check if it's a standard agent
1080
-
1081
- if _is_standard_agent(agent_name):
1082
-
1083
- return True
1084
-
1085
-
1086
-
1087
- # Check if it's a template agent
1088
-
1089
- if _is_template_agent(agent_name):
1090
-
1091
- return True
1092
-
1093
-
1094
-
1095
- # Check if it's a custom agent in session
1096
-
1097
- if session_state and "ai_system" in session_state:
1098
-
1099
- ai_system = session_state["ai_system"]
1100
-
1101
- if hasattr(ai_system, 'agents') and agent_name in ai_system.agents:
1102
-
1103
- return True
1104
-
1105
-
1106
-
1107
- return False
1108
-
1109
-
1110
-
1111
- def _get_available_agents_list(session_state: dict = None) -> list:
1112
-
1113
- """Get list of all available agents from database"""
1114
-
1115
- from src.db.init_db import session_factory
1116
-
1117
- from src.agents.agents import load_all_available_templates_from_db
1118
-
1119
-
1120
-
1121
- # Core agents (always available)
1122
-
1123
- available = ["preprocessing_agent", "statistical_analytics_agent", "sk_learn_agent", "data_viz_agent"]
1124
-
1125
-
1126
-
1127
- # Add template agents from database
1128
-
1129
- db_session = session_factory()
1130
-
1131
- try:
1132
-
1133
- template_agents_dict = load_all_available_templates_from_db(db_session)
1134
-
1135
- # template_agents_dict is a dict with template_name as keys
1136
-
1137
- template_names = [template_name for template_name in template_agents_dict.keys()
1138
-
1139
- if template_name not in available and template_name != 'basic_qa_agent']
1140
-
1141
- available.extend(template_names)
1142
-
1143
- except Exception as e:
1144
-
1145
- logger.log_message(f"Error loading template agents: {str(e)}", level=logging.ERROR)
1146
-
1147
- finally:
1148
-
1149
- db_session.close()
1150
-
1151
-
1152
-
1153
- return available
1154
-
1155
-
1156
-
1157
- def _is_standard_agent(agent_name: str) -> bool:
1158
-
1159
- """Check if agent is one of the 4 core standard agents"""
1160
-
1161
- standard_agents = ["preprocessing_agent", "statistical_analytics_agent", "sk_learn_agent", "data_viz_agent"]
1162
-
1163
- return agent_name in standard_agents
1164
-
1165
-
1166
-
1167
- def _is_template_agent(agent_name: str) -> bool:
1168
-
1169
- """Check if agent is a template agent"""
1170
-
1171
- try:
1172
-
1173
- from src.db.init_db import session_factory
1174
-
1175
- from src.db.schemas.models import AgentTemplate
1176
-
1177
-
1178
-
1179
- db_session = session_factory()
1180
-
1181
- try:
1182
-
1183
- template = db_session.query(AgentTemplate).filter(
1184
-
1185
- AgentTemplate.template_name == agent_name,
1186
-
1187
- AgentTemplate.is_active == True
1188
-
1189
- ).first()
1190
-
1191
- return template is not None
1192
-
1193
- finally:
1194
-
1195
- db_session.close()
1196
-
1197
- except Exception as e:
1198
-
1199
- logger.log_message(f"Error checking if {agent_name} is template: {str(e)}", level=logging.ERROR)
1200
-
1201
- return False
1202
-
1203
-
1204
-
1205
- async def _execute_custom_agents(ai_system, agent_names: list, query: str):
1206
-
1207
- """Execute custom agents using the session's AI system"""
1208
-
1209
- try:
1210
-
1211
- # For custom agents, we need to use the AI system's execute_agent method
1212
-
1213
-
1214
-
1215
- agent_results = [ai_system]
1216
-
1217
- if len(agent_names) == 1:
1218
-
1219
- # Single custom agent
1220
-
1221
- agent_name = agent_names[0]
1222
-
1223
- # Prepare inputs for the custom agent (similar to standard agents like data_viz_agent)
1224
-
1225
- dict_ = {}
1226
-
1227
- dict_['dataset'] = ai_system.dataset.retrieve(query)[0].text
1228
-
1229
- dict_['styling_index'] = ai_system.styling_index.retrieve(query)[0].text
1230
-
1231
- dict_['goal'] = query
1232
-
1233
- dict_['Agent_desc'] = str(ai_system.agent_desc)
1234
-
1235
-
1236
-
1237
- # Get input fields for this agent
1238
-
1239
- if agent_name in ai_system.agent_inputs:
1240
-
1241
- inputs = {x: dict_[x] for x in ai_system.agent_inputs[agent_name] if x in dict_}
1242
-
1243
-
1244
-
1245
- # Execute the custom agent
1246
-
1247
- agent_name_result, result_dict = await ai_system.agents[agent_name](**inputs)
1248
-
1249
- return {agent_name_result: result_dict}
1250
-
1251
- else:
1252
-
1253
- logger.log_message(f"Agent '{agent_name}' not found in ai_system.agent_inputs", level=logging.ERROR)
1254
-
1255
- return {"error": f"Agent '{agent_name}' input configuration not found"}
1256
-
1257
- else:
1258
-
1259
- # Multiple agents - execute sequentially
1260
-
1261
- results = {}
1262
-
1263
- for agent_name in agent_names:
1264
-
1265
- single_result = await _execute_custom_agents(ai_system, [agent_name], query)
1266
-
1267
- results.update(single_result)
1268
-
1269
- return results
1270
-
1271
-
1272
-
1273
- except Exception as e:
1274
-
1275
- logger.log_message(f"Error in _execute_custom_agents: {str(e)}", level=logging.ERROR)
1276
-
1277
- return {"error": f"Error executing custom agents: {str(e)}"}
1278
-
1279
 
1280
 
1281
  def _prepare_query_with_context(query: str, session_state: dict) -> str:
1282
-
1283
  """Prepare the query with chat context from previous messages"""
1284
-
1285
  chat_id = session_state.get("chat_id")
1286
-
1287
  if not chat_id:
1288
-
1289
  return query
1290
-
1291
 
1292
-
1293
  # Get chat manager from app state
1294
-
1295
  chat_manager = app.state._session_manager.chat_manager
1296
-
1297
  # Get recent messages
1298
-
1299
  recent_messages = chat_manager.get_recent_chat_history(chat_id, limit=MAX_RECENT_MESSAGES)
1300
-
1301
  # Extract response history
1302
-
1303
  chat_context = chat_manager.extract_response_history(recent_messages)
1304
-
1305
 
1306
-
1307
  # Append context to the query if available
1308
-
1309
  if chat_context:
1310
-
1311
  return f"### Current Query:\n{query}\n\n{chat_context}"
1312
-
1313
  return query
1314
 
1315
 
1316
-
1317
-
1318
-
1319
  def _track_model_usage(session_state: dict, enhanced_query: str, response, processing_time_ms: int):
1320
-
1321
  """Track model usage statistics in the database"""
1322
-
1323
  try:
1324
-
1325
  ai_manager = app.state.get_ai_manager()
1326
-
1327
 
1328
-
1329
  # Get model configuration
1330
-
1331
  model_config = session_state.get("model_config", DEFAULT_MODEL_CONFIG)
1332
-
1333
  model_name = model_config.get("model", DEFAULT_MODEL_CONFIG["model"])
1334
-
1335
  provider = ai_manager.get_provider_for_model(model_name)
1336
-
1337
 
1338
-
1339
  # Calculate token usage
1340
-
1341
  try:
1342
-
1343
  # Try exact tokenization
1344
-
1345
  prompt_tokens = len(ai_manager.tokenizer.encode(enhanced_query))
1346
-
1347
  completion_tokens = len(ai_manager.tokenizer.encode(str(response)))
1348
-
1349
  total_tokens = prompt_tokens + completion_tokens
1350
-
1351
  except Exception as token_error:
1352
-
1353
  # Fall back to estimation
1354
-
1355
  logger.log_message(f"Tokenization error: {str(token_error)}", level=logging.WARNING)
1356
-
1357
  prompt_words = len(enhanced_query.split())
1358
-
1359
  completion_words = len(str(response).split())
1360
-
1361
  prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
1362
-
1363
  completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
1364
-
1365
  total_tokens = prompt_tokens + completion_tokens
1366
-
1367
 
1368
-
1369
  # Calculate cost
1370
-
1371
  cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
1372
-
1373
 
1374
-
1375
  # Save usage to database
1376
-
1377
  ai_manager.save_usage_to_db(
1378
-
1379
  user_id=session_state.get("user_id"),
1380
-
1381
  chat_id=session_state.get("chat_id"),
1382
-
1383
  model_name=model_name,
1384
-
1385
  provider=provider,
1386
-
1387
  prompt_tokens=int(prompt_tokens),
1388
-
1389
  completion_tokens=int(completion_tokens),
1390
-
1391
  total_tokens=int(total_tokens),
1392
-
1393
  query_size=len(enhanced_query),
1394
-
1395
  response_size=len(str(response)),
1396
-
1397
  cost=round(cost, 7),
1398
-
1399
  request_time_ms=processing_time_ms,
1400
-
1401
  is_streaming=False
1402
-
1403
  )
1404
-
1405
  except Exception as e:
1406
-
1407
  # Log but don't fail the request if usage tracking fails
1408
-
1409
  logger.log_message(f"Failed to track model usage: {str(e)}", level=logging.ERROR)
1410
 
1411
 
1412
-
1413
-
1414
-
1415
  async def _generate_streaming_responses(session_state: dict, query: str, session_lm):
1416
-
1417
  """Generate streaming responses for chat_with_all endpoint"""
1418
-
1419
  overall_start_time = time.time()
1420
-
1421
  total_response = ""
1422
-
1423
  total_inputs = ""
1424
-
1425
  usage_records = []
1426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1427
 
1428
-
1429
- # Add chat context from previous messages
1430
-
1431
- enhanced_query = _prepare_query_with_context(query, session_state)
1432
-
1433
-
1434
-
1435
- # try:
1436
-
1437
- # Get the plan - planner is now async, so we need to await it
1438
-
1439
- plan_response = await session_state["ai_system"].get_plan(enhanced_query)
1440
-
1441
-
1442
-
1443
- plan_description = format_response_to_markdown(
1444
-
1445
- {"analytical_planner": plan_response},
1446
-
1447
- datasets=session_state["datasets"]
1448
- )
1449
-
1450
-
1451
-
1452
- # Check if plan is valid
1453
-
1454
- if plan_description == RESPONSE_ERROR_INVALID_QUERY:
1455
-
1456
- yield json.dumps({
1457
-
1458
- "agent": "Analytical Planner",
1459
-
1460
- "content": plan_description,
1461
-
1462
- "status": "error"
1463
-
1464
- }) + "\n"
1465
-
1466
- return
1467
-
1468
-
1469
-
1470
- yield json.dumps({
1471
-
1472
- "agent": "Analytical Planner",
1473
-
1474
- "content": plan_description,
1475
-
1476
- "status": "success" if plan_description else "error"
1477
-
1478
- }) + "\n"
1479
-
1480
-
1481
-
1482
- # Track planner usage
1483
-
1484
- if session_state.get("user_id"):
1485
-
1486
- planner_tokens = _estimate_tokens(ai_manager=app.state.ai_manager,
1487
-
1488
- input_text=enhanced_query,
1489
-
1490
- output_text=plan_description)
1491
-
1492
-
1493
-
1494
- usage_records.append(_create_usage_record(
1495
-
1496
- session_state=session_state,
1497
-
1498
- model_name=session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"],
1499
-
1500
- prompt_tokens=planner_tokens["prompt"],
1501
-
1502
- completion_tokens=planner_tokens["completion"],
1503
-
1504
- query_size=len(enhanced_query),
1505
-
1506
- response_size=len(plan_description),
1507
-
1508
- processing_time_ms=int((time.time() - overall_start_time) * 1000),
1509
-
1510
- is_streaming=False
1511
-
1512
- ))
1513
-
1514
-
1515
-
1516
- logger.log_message(f"Plan response: {plan_response}", level=logging.INFO)
1517
-
1518
- logger.log_message(f"Plan response type: {type(plan_response)}", level=logging.INFO)
1519
-
1520
-
1521
-
1522
- # Check if plan_response is valid
1523
-
1524
- # if not plan_response or not isinstance(plan_response, dict):
1525
-
1526
- # yield json.dumps({
1527
-
1528
- # "agent": "Analytical Planner",
1529
-
1530
- # "content": "**Error: Invalid plan response**\n\nResponse: " + str(plan_response),
1531
-
1532
- # "status": "error"
1533
-
1534
- # }) + "\n"
1535
-
1536
- # return
1537
-
1538
-
1539
-
1540
- # Execute the plan with well-managed concurrency
1541
-
1542
- with dspy.context(lm = session_lm):
1543
-
1544
- # try:
1545
-
1546
-
1547
-
1548
- async for agent_name, inputs, response in session_state["ai_system"].execute_plan(enhanced_query, plan_response):
1549
-
1550
-
1551
-
1552
- if agent_name == "plan_not_found":
1553
-
1554
- yield json.dumps({
1555
-
1556
- "agent": "Analytical Planner",
1557
-
1558
- "content": "**No plan found**\n\nPlease try again with a different query or try using a different model.",
1559
-
1560
- "status": "error"
1561
-
1562
- }) + "\n"
1563
-
1564
- return
1565
-
1566
-
1567
-
1568
- if agent_name == "plan_not_formated_correctly":
1569
-
1570
- yield json.dumps({
1571
-
1572
- "agent": "Analytical Planner",
1573
-
1574
- "content": "**Something went wrong with formatting, retry the query!**",
1575
-
1576
- "status": "error"
1577
-
1578
- }) + "\n"
1579
-
1580
- return
1581
-
1582
-
1583
-
1584
-
1585
-
1586
- formatted_response = format_response_to_markdown(
1587
-
1588
- {agent_name: response},
1589
-
1590
- datasets=session_state["datasets"]
1591
- )
1592
-
1593
-
1594
-
1595
- yield json.dumps({
1596
-
1597
- "agent": agent_name.split("__")[0] if "__" in agent_name else agent_name,
1598
-
1599
- "content": formatted_response,
1600
-
1601
- "status": "success" if response else "error"
1602
-
1603
- }) + "\n"
1604
-
1605
-
1606
-
1607
- # Handle agent errors
1608
-
1609
- if isinstance(response, dict) and "error" in response:
1610
-
1611
- yield json.dumps({
1612
-
1613
- "agent": agent_name,
1614
-
1615
- "content": f"**Error in {agent_name}**: {response['error']}",
1616
-
1617
- "status": "error"
1618
-
1619
- }) + "\n"
1620
-
1621
- continue # Continue with next agent instead of returning
1622
-
1623
-
1624
-
1625
-
1626
-
1627
-
1628
-
1629
- if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
1630
-
1631
- yield json.dumps({
1632
-
1633
- "agent": agent_name,
1634
-
1635
- "content": formatted_response,
1636
-
1637
- "status": "error"
1638
-
1639
- }) + "\n"
1640
-
1641
- continue # Continue with next agent instead of returning
1642
-
1643
-
1644
-
1645
- # Send response chunk
1646
-
1647
-
1648
-
1649
-
1650
-
1651
- # Track agent usage for future batch DB write
1652
-
1653
- if session_state.get("user_id"):
1654
-
1655
- agent_tokens = _estimate_tokens(
1656
-
1657
- ai_manager=app.state.ai_manager,
1658
-
1659
- input_text=str(inputs),
1660
-
1661
- output_text=str(response)
1662
-
1663
- )
1664
-
1665
-
1666
-
1667
- # Get appropriate model name for code combiner
1668
-
1669
- if "code_combiner_agent" in agent_name and "__" in agent_name:
1670
-
1671
- provider = agent_name.split("__")[1]
1672
-
1673
- model_name = _get_model_name_for_provider(provider)
1674
-
1675
- else:
1676
-
1677
- model_name = session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"]
1678
-
1679
-
1680
-
1681
- usage_records.append(_create_usage_record(
1682
-
1683
- session_state=session_state,
1684
-
1685
- model_name=model_name,
1686
-
1687
- prompt_tokens=agent_tokens["prompt"],
1688
-
1689
- completion_tokens=agent_tokens["completion"],
1690
-
1691
- query_size=len(str(inputs)),
1692
-
1693
- response_size=len(str(response)),
1694
-
1695
- processing_time_ms=int((time.time() - overall_start_time) * 1000),
1696
-
1697
- is_streaming=True
1698
-
1699
- ))
1700
-
1701
-
1702
-
1703
- # except asyncio.TimeoutError:
1704
-
1705
- # yield json.dumps({
1706
-
1707
- # "agent": "planner",
1708
-
1709
- # "content": "The request timed out. Please try a simpler query.",
1710
-
1711
- # "status": "error"
1712
-
1713
- # }) + "\n"
1714
-
1715
- # return
1716
-
1717
-
1718
-
1719
- # except Exception as e:
1720
-
1721
- # logger.log_message(f"Error executing plan: {str(e)}", level=logging.ERROR)
1722
-
1723
- # yield json.dumps({
1724
-
1725
- # "agent": "planner",
1726
-
1727
- # "content": f"An error occurred while executing the plan: {str(e)}",
1728
-
1729
- # "status": "error"
1730
-
1731
- # }) + "\n"
1732
-
1733
- # return
1734
-
1735
-
1736
-
1737
- # except Exception as e:
1738
-
1739
- # logger.log_message(f"Error in streaming response: {str(e)}", level=logging.ERROR)
1740
-
1741
- # yield json.dumps({
1742
-
1743
- # "agent": "planner",
1744
-
1745
- # "content": "An error occurred while generating responses. Please try again!" + str(e) + str({k: v for k, v in session_lm.__dict__['kwargs'].items() if k != 'api_key'}),
1746
-
1747
- # "status": "error"
1748
-
1749
- # }) + "\n"
1750
-
1751
-
1752
-
1753
 
1754
 
1755
  def _estimate_tokens(ai_manager, input_text: str, output_text: str) -> dict:
1756
-
1757
  """Estimate token counts, with fallback for tokenization errors"""
1758
-
1759
  try:
1760
-
1761
  # Try exact tokenization
1762
-
1763
  prompt_tokens = len(ai_manager.tokenizer.encode(input_text))
1764
-
1765
  completion_tokens = len(ai_manager.tokenizer.encode(output_text))
1766
-
1767
  except Exception:
1768
-
1769
  # Fall back to estimation
1770
-
1771
  prompt_words = len(input_text.split())
1772
-
1773
  completion_words = len(output_text.split())
1774
-
1775
  prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
1776
-
1777
  completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
1778
-
1779
 
1780
-
1781
  return {
1782
-
1783
  "prompt": prompt_tokens,
1784
-
1785
  "completion": completion_tokens,
1786
-
1787
  "total": prompt_tokens + completion_tokens
1788
-
1789
  }
1790
 
1791
 
1792
-
1793
-
1794
-
1795
  def _create_usage_record(session_state: dict, model_name: str, prompt_tokens: int,
1796
-
1797
  completion_tokens: int, query_size: int, response_size: int,
1798
-
1799
  processing_time_ms: int, is_streaming: bool) -> dict:
1800
-
1801
  """Create a usage record for the database"""
1802
-
1803
  ai_manager = app.state.get_ai_manager()
1804
-
1805
  provider = ai_manager.get_provider_for_model(model_name)
1806
-
1807
  cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
1808
-
1809
 
1810
-
1811
  return {
1812
-
1813
  "user_id": session_state.get("user_id"),
1814
-
1815
  "chat_id": session_state.get("chat_id"),
1816
-
1817
  "model_name": model_name,
1818
-
1819
  "provider": provider,
1820
-
1821
  "prompt_tokens": int(prompt_tokens),
1822
-
1823
  "completion_tokens": int(completion_tokens),
1824
-
1825
  "total_tokens": int(prompt_tokens + completion_tokens),
1826
-
1827
  "query_size": query_size,
1828
-
1829
  "response_size": response_size,
1830
-
1831
  "cost": round(cost, 7),
1832
-
1833
  "request_time_ms": processing_time_ms,
1834
-
1835
  "is_streaming": is_streaming
1836
-
1837
  }
1838
 
1839
 
1840
-
1841
-
1842
-
1843
  def _get_model_name_for_provider(provider: str) -> str:
1844
-
1845
  """Get the model name for a provider"""
1846
-
1847
  provider_model_map = {
1848
-
1849
- "openai": "gpt-5.4",
1850
- "anthropic": "claude-sonnet-4-6",
1851
  "gemini": "gemini-2.5-pro-preview-03-25"
1852
  }
1853
- return provider_model_map.get(provider, "gpt-5.4")
1854
 
1855
 
1856
-
1857
-
1858
-
1859
-
1860
-
1861
- # Add an endpoint to list available agents
1862
-
1863
- @app.get("/agents", response_model=dict)
1864
-
1865
- async def list_agents(request: Request, session_id: str = Depends(get_session_id_dependency)):
1866
-
1867
- """Get all available agents (standard, template, and custom)"""
1868
-
1869
- session_state = app.state.get_session_state(session_id)
1870
-
1871
-
1872
-
1873
  try:
1874
-
1875
- # Get all available agents from database and session
1876
-
1877
- available_agents_list = _get_available_agents_list(session_state)
1878
-
1879
-
1880
-
1881
- # Categorize agents
1882
-
1883
- standard_agents = ["preprocessing_agent", "statistical_analytics_agent", "sk_learn_agent", "data_viz_agent"]
1884
-
1885
-
1886
-
1887
- # Get template agents from database
1888
-
1889
- from src.db.init_db import session_factory
1890
-
1891
- from src.agents.agents import load_all_available_templates_from_db
1892
-
1893
-
1894
-
1895
- db_session = session_factory()
1896
-
1897
- try:
1898
-
1899
- template_agents_dict = load_all_available_templates_from_db(db_session)
1900
-
1901
- # template_agents_dict is a dict with template_name as keys
1902
-
1903
- template_agents = [template_name for template_name in template_agents_dict.keys()
1904
-
1905
- if template_name not in standard_agents and template_name != 'basic_qa_agent']
1906
-
1907
- except Exception as e:
1908
-
1909
- logger.log_message(f"Error loading template agents in /agents endpoint: {str(e)}", level=logging.ERROR)
1910
-
1911
- template_agents = []
1912
-
1913
- finally:
1914
-
1915
- db_session.close()
1916
-
1917
-
1918
-
1919
- # Get custom agents from session
1920
-
1921
- custom_agents = []
1922
-
1923
- if session_state and "ai_system" in session_state:
1924
-
1925
- ai_system = session_state["ai_system"]
1926
-
1927
- if hasattr(ai_system, 'agents'):
1928
-
1929
- custom_agents = [agent for agent in available_agents_list
1930
-
1931
- if agent not in standard_agents and agent not in template_agents]
1932
-
1933
-
1934
-
1935
- # Ensure template agents are in the available list
1936
-
1937
- for template_agent in template_agents:
1938
-
1939
- if template_agent not in available_agents_list:
1940
-
1941
- available_agents_list.append(template_agent)
1942
-
1943
-
1944
-
1945
- return {
1946
-
1947
- "available_agents": available_agents_list,
1948
-
1949
- "standard_agents": standard_agents,
1950
-
1951
- "template_agents": template_agents,
1952
-
1953
- "custom_agents": custom_agents
1954
-
1955
- }
1956
-
1957
  except Exception as e:
1958
-
1959
- logger.log_message(f"Error getting agents list: {str(e)}", level=logging.ERROR)
1960
-
1961
- raise HTTPException(status_code=500, detail=f"Error getting agents list: {str(e)}")
1962
 
1963
 
 
 
 
 
 
 
 
1964
 
1965
  @app.get("/health", response_model=dict)
1966
-
1967
  async def health():
1968
-
1969
  return {"message": "API is healthy and running"}
1970
 
1971
-
1972
-
1973
  @app.get("/")
1974
-
1975
  async def index():
1976
-
1977
  return {
1978
-
1979
  "title": "Welcome to the AI Analytics API",
1980
-
1981
  "message": "Explore our API for advanced analytics and visualization tools designed to empower your data-driven decisions.",
1982
-
1983
  "description": "Utilize our powerful agents and models to gain insights from your data effortlessly.",
1984
-
1985
  "colors": {
1986
-
1987
  "primary": "#007bff",
1988
-
1989
  "secondary": "#6c757d",
1990
-
1991
  "success": "#28a745",
1992
-
1993
  "danger": "#dc3545",
1994
-
1995
  },
1996
-
1997
  "features": [
1998
-
1999
  "Real-time data processing",
2000
-
2001
  "Customizable visualizations",
2002
-
2003
  "Seamless integration with various data sources",
2004
-
2005
  "User-friendly interface for easy navigation",
2006
-
2007
  "Custom Analytics",
2008
-
2009
  ],
2010
-
2011
  }
2012
 
2013
-
2014
-
2015
  @app.post("/chat_history_name")
2016
-
2017
  async def chat_history_name(request: dict, session_id: str = Depends(get_session_id_dependency)):
2018
-
2019
  query = request.get("query")
2020
-
2021
  name = None
2022
-
2023
 
2024
-
2025
- lm = dspy.LM(model="openai/gpt-5-nano", max_tokens=300, temperature=0.5, api_key=os.getenv("OPENAI_API_KEY"))
2026
-
2027
 
2028
-
2029
  with dspy.context(lm=lm):
2030
-
2031
  name = app.state.get_chat_history_name_agent()(query=str(query))
2032
-
2033
 
2034
-
2035
  return {"name": name.name if name else "New Chat"}
2036
 
2037
-
2038
-
2039
- @app.post("/deep_analysis_streaming")
2040
-
2041
- async def deep_analysis_streaming(
2042
-
2043
- request: DeepAnalysisRequest,
2044
-
2045
- request_obj: Request,
2046
-
2047
- session_id: str = Depends(get_session_id_dependency)
2048
-
2049
- ):
2050
-
2051
- """Perform streaming deep analysis with real-time updates"""
2052
-
2053
- session_state = app.state.get_session_state(session_id)
2054
-
2055
-
2056
-
2057
- try:
2058
-
2059
- # Extract and validate query parameters
2060
-
2061
- _update_session_from_query_params(request_obj, session_state)
2062
-
2063
-
2064
-
2065
- # Validate dataset
2066
-
2067
- if session_state["datasets"] is None:
2068
- raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
2069
-
2070
-
2071
-
2072
- # Get user_id from session state (if available)
2073
-
2074
- user_id = session_state.get("user_id")
2075
-
2076
-
2077
-
2078
- # Generate a UUID for this report
2079
-
2080
- import uuid
2081
-
2082
- report_uuid = str(uuid.uuid4())
2083
-
2084
-
2085
-
2086
- # Create initial pending report in the database
2087
-
2088
- try:
2089
-
2090
- from src.db.init_db import session_factory
2091
-
2092
- from src.db.schemas.models import DeepAnalysisReport
2093
-
2094
-
2095
-
2096
- db_session = session_factory()
2097
-
2098
-
2099
-
2100
- try:
2101
-
2102
- # Create a pending report entry
2103
-
2104
- new_report = DeepAnalysisReport(
2105
-
2106
- report_uuid=report_uuid,
2107
-
2108
- user_id=user_id,
2109
-
2110
- goal=request.goal,
2111
-
2112
- status="pending",
2113
-
2114
- start_time=datetime.now(UTC),
2115
-
2116
- progress_percentage=0
2117
-
2118
- )
2119
-
2120
-
2121
-
2122
- db_session.add(new_report)
2123
-
2124
- db_session.commit()
2125
-
2126
- db_session.refresh(new_report)
2127
-
2128
-
2129
-
2130
- # Store the report ID in session state for later updates
2131
-
2132
- session_state["current_deep_analysis_id"] = new_report.report_id
2133
-
2134
- session_state["current_deep_analysis_uuid"] = report_uuid
2135
-
2136
-
2137
-
2138
- except Exception as e:
2139
-
2140
- logger.log_message(f"Error creating initial deep analysis report: {str(e)}", level=logging.ERROR)
2141
-
2142
- # Continue even if DB storage fails
2143
-
2144
- finally:
2145
-
2146
- db_session.close()
2147
-
2148
-
2149
-
2150
- except Exception as e:
2151
-
2152
- logger.log_message(f"Database operation failed: {str(e)}", level=logging.ERROR)
2153
-
2154
- # Continue even if DB operation fails
2155
-
2156
-
2157
-
2158
- # Get session-specific model
2159
-
2160
- # session_lm = get_session_lm(session_state)
2161
-
2162
- session_lm = dspy.LM(model="anthropic/claude-sonnet-4-6", max_tokens=7000, temperature=0.5)
2163
-
2164
-
2165
-
2166
- return StreamingResponse(
2167
-
2168
- _generate_deep_analysis_stream(session_state, request.goal, session_lm, session_id),
2169
-
2170
- media_type='text/event-stream',
2171
-
2172
- headers={
2173
-
2174
- 'Cache-Control': 'no-cache',
2175
-
2176
- 'Connection': 'keep-alive',
2177
-
2178
- 'Content-Type': 'text/event-stream',
2179
-
2180
- 'Access-Control-Allow-Origin': '*',
2181
-
2182
- 'X-Accel-Buffering': 'no'
2183
-
2184
- }
2185
-
2186
- )
2187
-
2188
-
2189
-
2190
- except HTTPException:
2191
-
2192
- raise
2193
-
2194
- except Exception as e:
2195
-
2196
- logger.log_message(f"Streaming deep analysis failed: {str(e)}", level=logging.ERROR)
2197
-
2198
- raise HTTPException(status_code=500, detail=f"Streaming deep analysis failed: {str(e)}")
2199
-
2200
-
2201
-
2202
- async def _generate_deep_analysis_stream(session_state: dict, goal: str, session_lm, session_id: str):
2203
-
2204
- """Generate streaming responses for deep analysis"""
2205
-
2206
- # Track the start time for duration calculation
2207
-
2208
- start_time = datetime.now(UTC)
2209
-
2210
-
2211
-
2212
- try:
2213
-
2214
- # Get dataset info
2215
- datasets = session_state["datasets"]
2216
- desc = session_state['description']
2217
-
2218
- # Generate dataset info for all datasets
2219
- logger.log_message(f"🔍 DEEP ANALYSIS START - datasets type: {type(datasets)}, keys: {list(datasets.keys()) if datasets else 'None'}", level=logging.DEBUG)
2220
-
2221
- dataset_info = desc
2222
- logger.log_message(f"🔍 DEEP ANALYSIS - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
2223
- logger.log_message(f"🔍 DEEP ANALYSIS - dataset_info content: {dataset_info[:200]}...", level=logging.DEBUG)
2224
-
2225
-
2226
-
2227
- # Get report info from session state
2228
-
2229
- report_id = session_state.get("current_deep_analysis_id")
2230
-
2231
- report_uuid = session_state.get("current_deep_analysis_uuid")
2232
-
2233
- user_id = session_state.get("user_id")
2234
-
2235
-
2236
-
2237
- # Helper function to update report in database
2238
-
2239
- async def update_report_in_db(status, progress, step=None, content=None):
2240
-
2241
- if not report_id:
2242
-
2243
- return
2244
-
2245
-
2246
-
2247
- try:
2248
-
2249
- from src.db.init_db import session_factory
2250
-
2251
- from src.db.schemas.models import DeepAnalysisReport
2252
-
2253
-
2254
-
2255
- db_session = session_factory()
2256
-
2257
-
2258
-
2259
- try:
2260
-
2261
- report = db_session.query(DeepAnalysisReport).filter(DeepAnalysisReport.report_id == report_id).first()
2262
-
2263
-
2264
-
2265
- if report:
2266
-
2267
- report.status = status
2268
-
2269
- report.progress_percentage = progress
2270
-
2271
-
2272
-
2273
- # Update step-specific fields if provided
2274
-
2275
- if step == "questions" and content:
2276
-
2277
- report.deep_questions = content
2278
-
2279
- elif step == "planning" and content:
2280
-
2281
- report.deep_plan = content
2282
-
2283
- elif step == "analysis" and content:
2284
-
2285
- # For analysis step, we get the full object with multiple fields
2286
-
2287
- if isinstance(content, dict):
2288
-
2289
- # Update fields from content if they exist
2290
-
2291
- if "deep_questions" in content and content["deep_questions"]:
2292
-
2293
- report.deep_questions = content["deep_questions"]
2294
-
2295
- if "deep_plan" in content and content["deep_plan"]:
2296
-
2297
- report.deep_plan = content["deep_plan"]
2298
-
2299
- if "code" in content and content["code"]:
2300
-
2301
- report.analysis_code = content["code"]
2302
-
2303
- if "final_conclusion" in content and content["final_conclusion"]:
2304
-
2305
- report.final_conclusion = content["final_conclusion"]
2306
-
2307
- # Also update summary from conclusion
2308
-
2309
- conclusion = content["final_conclusion"]
2310
-
2311
- conclusion = conclusion.replace("**Conclusion**", "")
2312
-
2313
- report.report_summary = conclusion[:200] + "..." if len(conclusion) > 200 else conclusion
2314
-
2315
-
2316
-
2317
- # Handle JSON fields
2318
-
2319
- if "summaries" in content and content["summaries"]:
2320
-
2321
- report.summaries = json.dumps(content["summaries"])
2322
-
2323
- if "plotly_figs" in content and content["plotly_figs"]:
2324
-
2325
- report.plotly_figures = json.dumps(content["plotly_figs"])
2326
-
2327
- if "synthesis" in content and content["synthesis"]:
2328
-
2329
- report.synthesis = json.dumps(content["synthesis"])
2330
-
2331
-
2332
-
2333
- # For the final step, update the HTML report
2334
-
2335
- if step == "completed":
2336
-
2337
- if content:
2338
-
2339
- report.html_report = content
2340
-
2341
- else:
2342
-
2343
- logger.log_message("No HTML content provided for completed step", level=logging.WARNING)
2344
-
2345
-
2346
-
2347
- report.end_time = datetime.now(UTC)
2348
-
2349
- # Ensure start_time is timezone-aware before calculating duration
2350
-
2351
- if report.start_time.tzinfo is None:
2352
-
2353
- start_time_utc = report.start_time.replace(tzinfo=UTC)
2354
-
2355
- else:
2356
-
2357
- start_time_utc = report.start_time
2358
-
2359
- report.duration_seconds = int((report.end_time - start_time_utc).total_seconds())
2360
-
2361
-
2362
-
2363
- report.updated_at = datetime.now(UTC)
2364
-
2365
- db_session.commit()
2366
-
2367
-
2368
-
2369
- except Exception as e:
2370
-
2371
- db_session.rollback()
2372
-
2373
- logger.log_message(f"Error updating deep analysis report: {str(e)}", level=logging.ERROR)
2374
-
2375
- finally:
2376
-
2377
- db_session.close()
2378
-
2379
- except Exception as e:
2380
-
2381
- logger.log_message(f"Database operation failed: {str(e)}", level=logging.ERROR)
2382
-
2383
-
2384
-
2385
- # Use session model for this request
2386
-
2387
- with dspy.context(lm=session_lm):
2388
-
2389
- # Send initial status
2390
-
2391
- yield json.dumps({
2392
-
2393
- "step": "initialization",
2394
-
2395
- "status": "starting",
2396
-
2397
- "message": "Initializing deep analysis...",
2398
-
2399
- "progress": 5
2400
-
2401
- }) + "\n"
2402
-
2403
-
2404
-
2405
- # Update DB status to running
2406
-
2407
- await update_report_in_db("running", 5)
2408
-
2409
-
2410
-
2411
- # Get deep analyzer - use the correct session_id from the session_state
2412
-
2413
- logger.log_message(f"Getting deep analyzer for session_id: {session_id}, user_id: {user_id}", level=logging.INFO)
2414
-
2415
- deep_analyzer = app.state.get_deep_analyzer(session_id)
2416
-
2417
-
2418
-
2419
- # Make all datasets available globally for code execution
2420
-
2421
- for dataset_name, dataset_df in datasets.items():
2422
- globals()[dataset_name] = dataset_df
2423
-
2424
- # Use the new streaming method and forward all progress updates
2425
- final_result = None
2426
-
2427
- logger.log_message(f"🔍 CALLING DEEP ANALYSIS - goal: {goal[:100]}...", level=logging.DEBUG)
2428
- logger.log_message(f"🔍 CALLING DEEP ANALYSIS - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
2429
- logger.log_message(f"🔍 CALLING DEEP ANALYSIS - session_datasets type: {type(datasets)}, keys: {list(datasets.keys()) if datasets else 'None'}", level=logging.DEBUG)
2430
-
2431
- async for update in deep_analyzer.execute_deep_analysis_streaming(
2432
- goal=goal,
2433
- dataset_info=dataset_info,
2434
- session_datasets=datasets # Pass all datasets instead of single df
2435
- ):
2436
-
2437
- # Convert the update to the expected format and yield it
2438
-
2439
- if update.get("step") == "questions" and update.get("status") == "completed":
2440
-
2441
- # Update DB with questions
2442
-
2443
- await update_report_in_db("running", update.get("progress", 0), "questions", update.get("content"))
2444
-
2445
- elif update.get("step") == "planning" and update.get("status") == "completed":
2446
-
2447
- # Update DB with planning
2448
-
2449
- await update_report_in_db("running", update.get("progress", 0), "planning", update.get("content"))
2450
-
2451
- elif update.get("step") == "conclusion" and update.get("status") == "completed":
2452
-
2453
- # Store the final result for later processing
2454
-
2455
- final_result = update.get("final_result")
2456
-
2457
-
2458
-
2459
- # Convert Plotly figures to JSON format for network transmission
2460
-
2461
- if final_result:
2462
-
2463
- import plotly.io
2464
-
2465
- serialized_return_dict = final_result.copy()
2466
-
2467
-
2468
-
2469
- # Convert plotly_figs to JSON format
2470
-
2471
- if 'plotly_figs' in serialized_return_dict and serialized_return_dict['plotly_figs']:
2472
-
2473
- json_figs = []
2474
-
2475
- for fig_list in serialized_return_dict['plotly_figs']:
2476
-
2477
- if isinstance(fig_list, list):
2478
-
2479
- json_fig_list = []
2480
-
2481
- for fig in fig_list:
2482
-
2483
- if hasattr(fig, 'to_json'): # Check if it's a Plotly figure
2484
-
2485
- json_fig_list.append(plotly.io.to_json(fig))
2486
-
2487
- else:
2488
-
2489
- json_fig_list.append(fig) # Already JSON or other format
2490
-
2491
- json_figs.append(json_fig_list)
2492
-
2493
- else:
2494
-
2495
- # Single figure case
2496
-
2497
- if hasattr(fig_list, 'to_json'):
2498
-
2499
- json_figs.append(plotly.io.to_json(fig_list))
2500
-
2501
- else:
2502
-
2503
- json_figs.append(fig_list)
2504
-
2505
- serialized_return_dict['plotly_figs'] = json_figs
2506
-
2507
-
2508
-
2509
- # Update DB with analysis results
2510
-
2511
- await update_report_in_db("running", update.get("progress", 0), "analysis", serialized_return_dict)
2512
-
2513
-
2514
-
2515
- # Generate HTML report using the original final_result with Figure objects
2516
-
2517
- html_report = None
2518
-
2519
- try:
2520
-
2521
- html_report = generate_html_report(final_result)
2522
-
2523
- except Exception as e:
2524
-
2525
- logger.log_message(f"Error generating HTML report: {str(e)}", level=logging.ERROR)
2526
-
2527
- # Continue even if HTML generation fails
2528
-
2529
-
2530
-
2531
- # Send the analysis results
2532
-
2533
- yield json.dumps({
2534
-
2535
- "step": "analysis",
2536
-
2537
- "status": "completed",
2538
-
2539
- "content": serialized_return_dict,
2540
-
2541
- "progress": 90
2542
-
2543
- }) + "\n"
2544
-
2545
-
2546
-
2547
- # Send report generation status
2548
-
2549
- yield json.dumps({
2550
-
2551
- "step": "report",
2552
-
2553
- "status": "processing",
2554
-
2555
- "message": "Generating final report...",
2556
-
2557
- "progress": 95
2558
-
2559
- }) + "\n"
2560
-
2561
-
2562
-
2563
- # Send final completion
2564
-
2565
- yield json.dumps({
2566
-
2567
- "step": "completed",
2568
-
2569
- "status": "success",
2570
-
2571
- "analysis": serialized_return_dict,
2572
-
2573
- "html_report": html_report,
2574
-
2575
- "progress": 100
2576
-
2577
- }) + "\n"
2578
-
2579
-
2580
-
2581
- # Update DB with completed report (with HTML if generated)
2582
-
2583
- if html_report:
2584
-
2585
- logger.log_message(f"Saving HTML report to database, length: {len(html_report)}", level=logging.INFO)
2586
-
2587
- else:
2588
-
2589
- logger.log_message("No HTML report to save to database", level=logging.WARNING)
2590
-
2591
- await update_report_in_db("completed", 100, "completed", html_report)
2592
-
2593
- elif update.get("step") == "error":
2594
-
2595
- # Forward error directly
2596
-
2597
- yield json.dumps(update) + "\n"
2598
-
2599
- await update_report_in_db("failed", 0)
2600
-
2601
- return
2602
-
2603
- else:
2604
-
2605
- # Forward all other progress updates
2606
-
2607
- yield json.dumps(update) + "\n"
2608
-
2609
-
2610
-
2611
- # If we somehow exit the loop without getting a final result, that's an error
2612
-
2613
- if not final_result:
2614
-
2615
- yield json.dumps({
2616
-
2617
- "step": "error",
2618
-
2619
- "status": "failed",
2620
-
2621
- "message": "Deep analysis completed without final result",
2622
-
2623
- "progress": 0
2624
-
2625
- }) + "\n"
2626
-
2627
- await update_report_in_db("failed", 0)
2628
-
2629
-
2630
-
2631
- except Exception as e:
2632
-
2633
- logger.log_message(f"Error in deep analysis stream: {str(e)}", level=logging.ERROR)
2634
-
2635
- yield json.dumps({
2636
-
2637
- "step": "error",
2638
-
2639
- "status": "failed",
2640
-
2641
- "message": f"Deep analysis failed: {str(e)}",
2642
-
2643
- "progress": 0
2644
-
2645
- }) + "\n"
2646
-
2647
-
2648
-
2649
- # Update DB with error status
2650
-
2651
- if 'update_report_in_db' in locals() and session_state.get("current_deep_analysis_id"):
2652
-
2653
- await update_report_in_db("failed", 0)
2654
-
2655
-
2656
-
2657
- @app.post("/deep_analysis/download_report")
2658
-
2659
- async def download_html_report(
2660
-
2661
- request: dict,
2662
-
2663
- session_id: str = Depends(get_session_id_dependency)
2664
-
2665
- ):
2666
-
2667
- """Download HTML report from previous deep analysis"""
2668
-
2669
- try:
2670
-
2671
- analysis_data = request.get("analysis_data")
2672
-
2673
- if not analysis_data:
2674
-
2675
- raise HTTPException(status_code=400, detail="No analysis data provided")
2676
-
2677
-
2678
-
2679
- # Get report UUID from request if available (for saving to DB)
2680
-
2681
- report_uuid = request.get("report_uuid")
2682
-
2683
- session_state = app.state.get_session_state(session_id)
2684
-
2685
-
2686
-
2687
- # If no report_uuid in request, try to get it from session state
2688
-
2689
- if not report_uuid and session_state.get("current_deep_analysis_uuid"):
2690
-
2691
- report_uuid = session_state.get("current_deep_analysis_uuid")
2692
-
2693
-
2694
-
2695
- # Convert JSON-serialized Plotly figures back to Figure objects for HTML generation
2696
-
2697
- processed_data = analysis_data.copy()
2698
-
2699
-
2700
-
2701
- if 'plotly_figs' in processed_data and processed_data['plotly_figs']:
2702
-
2703
- import plotly.io
2704
-
2705
- import plotly.graph_objects as go
2706
-
2707
-
2708
-
2709
- figure_objects = []
2710
-
2711
- for fig_list in processed_data['plotly_figs']:
2712
-
2713
- if isinstance(fig_list, list):
2714
-
2715
- fig_obj_list = []
2716
-
2717
- for fig_json in fig_list:
2718
-
2719
- if isinstance(fig_json, str):
2720
-
2721
- # Convert JSON string back to Figure object
2722
-
2723
- try:
2724
-
2725
- fig_obj = plotly.io.from_json(fig_json)
2726
-
2727
- fig_obj_list.append(fig_obj)
2728
-
2729
- except Exception as e:
2730
-
2731
- logger.log_message(f"Error parsing Plotly JSON: {str(e)}", level=logging.WARNING)
2732
-
2733
- continue
2734
-
2735
- elif hasattr(fig_json, 'to_html'):
2736
-
2737
- # Already a Figure object
2738
-
2739
- fig_obj_list.append(fig_json)
2740
-
2741
- figure_objects.append(fig_obj_list)
2742
-
2743
- else:
2744
-
2745
- # Single figure case
2746
-
2747
- if isinstance(fig_list, str):
2748
-
2749
- try:
2750
-
2751
- fig_obj = plotly.io.from_json(fig_list)
2752
-
2753
- figure_objects.append(fig_obj)
2754
-
2755
- except Exception as e:
2756
-
2757
- logger.log_message(f"Error parsing Plotly JSON: {str(e)}", level=logging.WARNING)
2758
-
2759
- continue
2760
-
2761
- elif hasattr(fig_list, 'to_html'):
2762
-
2763
- figure_objects.append(fig_list)
2764
-
2765
-
2766
-
2767
- processed_data['plotly_figs'] = figure_objects
2768
-
2769
-
2770
-
2771
- # Generate HTML report
2772
-
2773
- html_report = generate_html_report(processed_data)
2774
-
2775
-
2776
-
2777
- # Save report to database if we have a UUID
2778
-
2779
- if report_uuid:
2780
-
2781
- try:
2782
-
2783
- from src.db.init_db import session_factory
2784
-
2785
- from src.db.schemas.models import DeepAnalysisReport
2786
-
2787
-
2788
-
2789
- db_session = session_factory()
2790
-
2791
- try:
2792
-
2793
- # Try to find existing report by UUID
2794
-
2795
- report = db_session.query(DeepAnalysisReport).filter(DeepAnalysisReport.report_uuid == report_uuid).first()
2796
-
2797
-
2798
-
2799
- if report:
2800
-
2801
- # Update existing report with HTML content
2802
-
2803
- report.html_report = html_report
2804
-
2805
- report.updated_at = datetime.now(UTC)
2806
-
2807
- db_session.commit()
2808
-
2809
- except Exception as e:
2810
-
2811
- db_session.rollback()
2812
-
2813
- finally:
2814
-
2815
- db_session.close()
2816
-
2817
- except Exception as e:
2818
-
2819
- logger.log_message(f"Database operation failed when storing HTML report: {str(e)}", level=logging.ERROR)
2820
-
2821
- # Continue even if DB storage fails
2822
-
2823
-
2824
-
2825
- # Create a filename with timestamp
2826
-
2827
- timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
2828
-
2829
- filename = f"deep_analysis_report_{timestamp}.html"
2830
-
2831
-
2832
-
2833
- # Return as downloadable file
2834
-
2835
- return StreamingResponse(
2836
-
2837
- iter([html_report.encode('utf-8')]),
2838
-
2839
- media_type='text/html',
2840
-
2841
- headers={
2842
-
2843
- 'Content-Disposition': f'attachment; filename="{filename}"',
2844
-
2845
- 'Content-Type': 'text/html; charset=utf-8'
2846
-
2847
- }
2848
-
2849
- )
2850
-
2851
-
2852
-
2853
- except Exception as e:
2854
-
2855
- logger.log_message(f"Failed to generate HTML report: {str(e)}", level=logging.ERROR)
2856
-
2857
- raise HTTPException(status_code=500, detail=f"Failed to generate report: {str(e)}")
2858
-
2859
-
2860
-
2861
-
2862
-
2863
- # In the section where routers are included, add the session_router
2864
-
2865
- app.include_router(chat_router)
2866
-
2867
- app.include_router(analytics_router)
2868
-
2869
- app.include_router(code_router)
2870
-
2871
- app.include_router(session_router)
2872
-
2873
- app.include_router(feedback_router)
2874
-
2875
- app.include_router(deep_analysis_router)
2876
-
2877
- app.include_router(templates_router)
2878
-
2879
- app.include_router(blog_router)
2880
-
2881
-
2882
 
2883
  if __name__ == "__main__":
2884
-
2885
- port = int(os.environ.get("PORT", 8000))
2886
-
2887
- uvicorn.run(app, host="0.0.0.0", port=port)
2888
-
 
1
  # Standard library imports
 
2
  import asyncio
 
3
  import json
 
4
  import logging
 
5
  import os
 
6
  import time
 
7
  import uuid
 
8
  from io import StringIO
 
9
  from typing import List, Optional
10
 
 
 
 
 
 
 
 
 
 
 
11
  # Third-party imports
12
+ import groq
13
+ import pandas as pd
14
  import uvicorn
 
15
  from dotenv import load_dotenv
 
16
  from fastapi import (
 
17
  Depends,
 
18
  FastAPI,
 
19
  File,
 
20
  Form,
 
21
  HTTPException,
 
22
  Request,
 
23
  UploadFile
 
24
  )
 
25
  from fastapi.middleware.cors import CORSMiddleware
 
26
  from fastapi.responses import JSONResponse, StreamingResponse
 
27
  from fastapi.security import APIKeyHeader
28
+ from llama_index.core import Document, VectorStoreIndex
29
  from pydantic import BaseModel
30
 
 
 
31
  # Local application imports
 
32
  from scripts.format_response import format_response_to_markdown
 
33
  from src.agents.agents import *
 
34
  from src.agents.retrievers.retrievers import *
 
35
  from src.managers.ai_manager import AI_Manager
 
36
  from src.managers.session_manager import SessionManager
 
 
 
37
  from src.routes.analytics_routes import router as analytics_router
 
 
 
38
  from src.routes.chat_routes import router as chat_router
 
39
  from src.routes.code_routes import router as code_router
 
 
 
40
  from src.routes.session_routes import router as session_router, get_session_id_dependency
41
+ from src.schemas.query_schemas import QueryRequest
 
 
 
 
 
 
42
  from src.utils.logger import Logger
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ logger = Logger("app", see_time=True, console_log=False)
46
  load_dotenv()
47
 
48
+ styling_instructions = [
49
+ """
50
+ Dont ignore any of these instructions.
51
+ For a line chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
52
+ Always give a title and make bold using html tag axis label and try to use multiple colors if more than one line
53
+ Annotate the min and max of the line
54
+ Display numbers in thousand(K) or Million(M) if larger than 1000/100000
55
+ Show percentages in 2 decimal points with '%' sign
56
+ Default size of chart should be height =1200 and width =1000
57
+
58
+ """
59
+
60
+ , """
61
+ Dont ignore any of these instructions.
62
+ For a bar chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
63
+ Always give a title and make bold using html tag axis label
64
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000.
65
+ Annotate the values of the bar chart
66
+ If variable is a percentage show in 2 decimal points with '%' sign.
67
+ Default size of chart should be height =1200 and width =1000
68
+ """
69
+ ,
70
+
71
+ """
72
+ For a histogram chart choose a bin_size of 50
73
+ Do not ignore any of these instructions
74
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
75
+ Always give a title and make bold using html tag axis label
76
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
77
+ If variable is a percentage show in 2 decimal points with '%'
78
+ Default size of chart should be height =1200 and width =1000
79
+ """,
80
+
81
+
82
+ """
83
+ For a pie chart only show top 10 categories, bundle rest as others
84
+ Do not ignore any of these instructions
85
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
86
+ Always give a title and make bold using html tag axis label
87
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
88
+ If variable is a percentage show in 2 decimal points with '%'
89
+ Default size of chart should be height =1200 and width =1000
90
+ """,
91
+
92
+ """
93
+ Do not ignore any of these instructions
94
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
95
+ Always give a title and make bold using html tag axis label
96
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
97
+ Don't add K/M if number already in , or value is not a number
98
+ If variable is a percentage show in 2 decimal points with '%'
99
+ Default size of chart should be height =1200 and width =1000
100
+ """,
101
+ """
102
+ For a heat map
103
+ Use the 'plotly_white' template for a clean, white background.
104
+ Set a chart title
105
+ Style the X-axis with a black line color, 0.2 line width, 1 grid width, format 1000/1000000 as K/M
106
+ Do not format non-numerical numbers
107
+ .style the Y-axis with a black line color, 0.2 line width, 1 grid width format 1000/1000000 as K/M
108
+ Do not format non-numerical numbers
109
+
110
+ . Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
111
+ """,
112
+ """
113
+ For a Histogram, used for returns/distribution plotting
114
+ Use the 'plotly_white' template for a clean, white background.
115
+ Set a chart title
116
+ Style the X-axis 1 grid width, format 1000/1000000 as K/M
117
+ Do not format non-numerical numbers
118
+ .style the Y-axis, 1 grid width format 1000/1000000 as K/M
119
+ Do not format non-numerical numbers
120
+
121
+ Use an opacity of 0.75
122
+
123
+ Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
124
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  ]
126
 
 
 
 
 
 
 
 
 
127
  # Add near the top of the file, after imports
 
128
  DEFAULT_MODEL_CONFIG = {
129
  "provider": os.getenv("MODEL_PROVIDER", "openai"),
130
+ "model": os.getenv("MODEL_NAME", "gpt-4o-mini"),
131
  "api_key": os.getenv("OPENAI_API_KEY"),
132
+ "temperature": float(os.getenv("TEMPERATURE", 1.0)),
133
+ "max_tokens": int(os.getenv("MAX_TOKENS", 6000))
 
134
  }
135
 
 
 
136
  # Create default LM config but don't set it globally
137
+ if DEFAULT_MODEL_CONFIG["provider"].lower() == "groq":
138
+ default_lm = dspy.GROQ(
139
+ model=DEFAULT_MODEL_CONFIG["model"],
140
+ api_key=DEFAULT_MODEL_CONFIG["api_key"],
141
+ temperature=DEFAULT_MODEL_CONFIG["temperature"],
142
+ max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
143
+ )
144
+ elif DEFAULT_MODEL_CONFIG["provider"].lower() == "gemini":
145
+ default_lm = dspy.LM(
146
+ model=f"gemini/{DEFAULT_MODEL_CONFIG['model']}",
147
+ api_key=DEFAULT_MODEL_CONFIG["api_key"],
148
+ temperature=DEFAULT_MODEL_CONFIG["temperature"],
149
+ max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
150
+ )
151
+ else:
152
+ default_lm = dspy.LM(
153
+ model=DEFAULT_MODEL_CONFIG["model"],
154
+ api_key=DEFAULT_MODEL_CONFIG["api_key"],
155
+ temperature=DEFAULT_MODEL_CONFIG["temperature"],
156
+ max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
157
+ )
158
 
159
  # Function to get model config from session or use default
 
160
  def get_session_lm(session_state):
 
161
  """Get the appropriate LM instance for a session, or default if not configured"""
 
162
  # First check if we have a valid session-specific model config
 
163
  if session_state and isinstance(session_state, dict) and "model_config" in session_state:
 
164
  model_config = session_state["model_config"]
 
165
  if model_config and isinstance(model_config, dict) and "model" in model_config:
 
166
  # Found valid session-specific model config, use it
 
167
  provider = model_config.get("provider", "openai").lower()
168
+ if provider == "groq":
169
+ return dspy.GROQ(
170
+ model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
171
+ api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
172
+ temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
173
+ max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
174
+ )
175
+ elif provider == "anthropic":
176
+ return dspy.LM(
177
+ model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
178
+ api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
179
+ temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
180
+ max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
181
+ )
182
+ elif provider == "gemini":
183
+ return dspy.LM(
184
+ model=f"gemini/{model_config.get('model', DEFAULT_MODEL_CONFIG['model'])}",
185
+ api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
186
+ temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
187
+ max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
188
+ )
189
+ else: # OpenAI is the default
190
+ return dspy.LM(
191
+ model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
192
+ api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
193
+ temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
194
+ max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
195
+ )
196
 
 
197
  # If no valid session config, use default
198
+ return default_lm
 
 
 
199
 
200
  # Initialize retrievers with empty data first
201
+ def initialize_retrievers(styling_instructions: List[str], doc: List[str]):
202
+ try:
203
+ style_index = VectorStoreIndex.from_documents([Document(text=x) for x in styling_instructions])
204
+ data_index = VectorStoreIndex.from_documents([Document(text=x) for x in doc])
205
+ return {"style_index": style_index, "dataframe_index": data_index}
206
+ except Exception as e:
207
+ logger.log_message(f"Error initializing retrievers: {str(e)}", level=logging.ERROR)
208
+ raise e
209
 
210
  # clear console
 
211
  def clear_console():
 
212
  os.system('cls' if os.name == 'nt' else 'clear')
213
 
214
 
 
 
 
215
  # Check for Housing.csv
 
216
  housing_csv_path = "Housing.csv"
 
217
  if not os.path.exists(housing_csv_path):
 
218
  logger.log_message(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}", level=logging.ERROR)
 
219
  raise FileNotFoundError(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}")
220
 
221
+ AVAILABLE_AGENTS = {
222
+ "data_viz_agent": data_viz_agent,
223
+ "sk_learn_agent": sk_learn_agent,
224
+ "statistical_analytics_agent": statistical_analytics_agent,
225
+ "preprocessing_agent": preprocessing_agent,
226
+ }
227
 
228
+ PLANNER_AGENTS = {
229
+ "planner_preprocessing_agent": planner_preprocessing_agent,
230
+ "planner_sk_learn_agent": planner_sk_learn_agent,
231
+ "planner_statistical_analytics_agent": planner_statistical_analytics_agent,
232
+ "planner_data_viz_agent": planner_data_viz_agent,
233
+ }
234
 
235
  # Add session header
 
236
  X_SESSION_ID = APIKeyHeader(name="X-Session-ID", auto_error=False)
237
 
 
 
238
  # Update AppState class to use SessionManager
239
+ class AppState:
240
+ def __init__(self):
241
+ self._session_manager = SessionManager(styling_instructions, PLANNER_AGENTS)
242
+ self.model_config = DEFAULT_MODEL_CONFIG.copy()
243
+ # Update the SessionManager with the current model_config
244
+ self._session_manager._app_model_config = self.model_config
245
+ self.ai_manager = AI_Manager()
246
+ self.chat_name_agent = chat_history_name_agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
+ def get_session_state(self, session_id: str):
249
+ """Get or create session-specific state using the SessionManager"""
250
+ return self._session_manager.get_session_state(session_id)
251
 
252
+ def clear_session_state(self, session_id: str):
253
+ """Clear session-specific state using the SessionManager"""
254
+ self._session_manager.clear_session_state(session_id)
255
 
256
+ def update_session_dataset(self, session_id: str, df, name, desc):
257
+ """Update dataset for a specific session using the SessionManager"""
258
+ self._session_manager.update_session_dataset(session_id, df, name, desc)
259
 
260
+ def reset_session_to_default(self, session_id: str):
261
+ """Reset a session to use the default dataset using the SessionManager"""
262
+ self._session_manager.reset_session_to_default(session_id)
263
 
264
+ def set_session_user(self, session_id: str, user_id: int, chat_id: int = None):
265
+ """Associate a user with a session using the SessionManager"""
266
+ return self._session_manager.set_session_user(session_id, user_id, chat_id)
 
 
 
 
267
 
268
+ def get_ai_manager(self):
269
+ """Get the AI Manager instance"""
270
+ return self.ai_manager
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ def get_provider_for_model(self, model_name):
273
+ return self.ai_manager.get_provider_for_model(model_name)
274
+
275
+ def calculate_cost(self, model_name, input_tokens, output_tokens):
276
+ return self.ai_manager.calculate_cost(model_name, input_tokens, output_tokens)
277
+
278
+ def save_usage_to_db(self, user_id, chat_id, model_name, provider, prompt_tokens, completion_tokens, total_tokens, query_size, response_size, cost, request_time_ms, is_streaming=False):
279
+ return self.ai_manager.save_usage_to_db(user_id, chat_id, model_name, provider, prompt_tokens, completion_tokens, total_tokens, query_size, response_size, round(cost, 7), request_time_ms, is_streaming)
280
+
281
+ def get_tokenizer(self):
282
+ return self.ai_manager.tokenizer
283
+
284
+ def get_chat_history_name_agent(self):
285
+ return dspy.Predict(self.chat_name_agent)
286
 
287
+ # Initialize FastAPI app with state
288
+ app = FastAPI(title="AI Analytics API", version="1.0")
289
+ app.state = AppState()
 
 
 
 
290
 
291
+ # Configure middleware
292
  app.add_middleware(
 
293
  CORSMiddleware,
294
+ allow_origins=["*"],
 
 
 
 
295
  allow_credentials=True,
 
296
  allow_methods=["*"],
 
297
  allow_headers=["*"],
298
+ expose_headers=["Content-Type", "Content-Length"]
 
 
 
 
299
  )
300
 
 
 
301
  # Add these constants at the top of the file with other imports/constants
 
302
  RESPONSE_ERROR_INVALID_QUERY = "Please provide a valid query..."
 
303
  RESPONSE_ERROR_NO_DATASET = "No dataset is currently loaded. Please link a dataset before proceeding with your analysis."
 
304
  DEFAULT_TOKEN_RATIO = 1.5
305
+ REQUEST_TIMEOUT_SECONDS = 60 # Timeout for LLM requests
306
+ MAX_RECENT_MESSAGES = 3
 
 
 
307
  DB_BATCH_SIZE = 10 # For future batch DB operations
308
 
309
+ # Replace the existing chat_with_agent function
 
310
  @app.post("/chat/{agent_name}", response_model=dict)
 
311
  async def chat_with_agent(
 
312
  agent_name: str,
 
313
  request: QueryRequest,
 
314
  request_obj: Request,
 
315
  session_id: str = Depends(get_session_id_dependency)
 
316
  ):
 
317
  session_state = app.state.get_session_state(session_id)
 
 
 
318
 
 
319
  try:
 
320
  # Extract and validate query parameters
 
 
 
321
  _update_session_from_query_params(request_obj, session_state)
 
 
 
322
 
 
323
  # Validate dataset and agent name
324
+ if session_state["current_df"] is None:
325
+ raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
326
 
327
+ _validate_agent_name(agent_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
 
329
  # Record start time for timing
 
330
  start_time = time.time()
 
331
 
 
332
  # Get chat context and prepare query
 
 
 
333
  enhanced_query = _prepare_query_with_context(request.query, session_state)
 
 
 
334
 
335
+ # Initialize agent
 
 
336
  if "," in agent_name:
337
+ agent_list = [AVAILABLE_AGENTS[agent.strip()] for agent in agent_name.split(",")]
338
+ agent = auto_analyst_ind(agents=agent_list, retrievers=session_state["retrievers"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  else:
340
+ agent = auto_analyst_ind(agents=[AVAILABLE_AGENTS[agent_name]], retrievers=session_state["retrievers"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
+ # Execute agent with timeout
343
+ try:
344
+ # Get session-specific model
345
+ session_lm = get_session_lm(session_state)
346
+
347
+ # Use session-specific model for this request
348
+ with dspy.context(lm=session_lm):
349
+ response = await asyncio.wait_for(
350
+ asyncio.to_thread(agent, enhanced_query, agent_name),
351
+ timeout=REQUEST_TIMEOUT_SECONDS
352
+ )
353
+ except asyncio.TimeoutError:
354
+ logger.log_message(f"Agent execution timed out for {agent_name}", level=logging.WARNING)
355
+ raise HTTPException(status_code=504, detail="Request timed out. Please try a simpler query.")
356
+ except Exception as agent_error:
357
+ logger.log_message(f"Agent execution failed: {str(agent_error)}", level=logging.ERROR)
358
+ raise HTTPException(status_code=500, detail="Failed to process query. Please try again.")
359
+
360
+ formatted_response = format_response_to_markdown(response, agent_name, session_state["current_df"])
361
 
 
362
  if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
 
 
 
363
  return {
 
364
  "agent_name": agent_name,
 
365
  "query": request.query,
 
366
  "response": formatted_response,
 
367
  "session_id": session_id
 
368
  }
 
369
 
 
370
  # Track usage statistics
 
371
  if session_state.get("user_id"):
 
 
 
372
  _track_model_usage(
 
373
  session_state=session_state,
 
374
  enhanced_query=enhanced_query,
 
375
  response=response,
 
376
  processing_time_ms=int((time.time() - start_time) * 1000)
 
377
  )
 
378
 
 
 
 
379
  return {
 
380
  "agent_name": agent_name,
 
381
  "query": request.query, # Return original query without context
 
382
  "response": formatted_response,
 
383
  "session_id": session_id
 
384
  }
 
385
  except HTTPException:
 
386
  # Re-raise HTTP exceptions to preserve status codes
 
 
 
387
  raise
 
 
 
 
 
 
 
388
  except Exception as e:
389
+ logger.log_message(f"Unexpected error in chat_with_agent: {str(e)}", level=logging.ERROR)
 
 
 
 
 
 
 
 
390
  raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
391
+
392
+
 
 
 
393
  @app.post("/chat", response_model=dict)
 
394
  async def chat_with_all(
 
395
  request: QueryRequest,
 
396
  request_obj: Request,
 
397
  session_id: str = Depends(get_session_id_dependency)
 
398
  ):
 
399
  session_state = app.state.get_session_state(session_id)
400
 
 
 
401
  try:
 
402
  # Extract and validate query parameters
 
403
  _update_session_from_query_params(request_obj, session_state)
 
404
 
 
405
  # Validate dataset
406
+ if session_state["current_df"] is None:
 
407
  raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
 
408
 
 
409
  if session_state["ai_system"] is None:
 
410
  raise HTTPException(status_code=500, detail="AI system not properly initialized.")
411
 
 
 
412
  # Get session-specific model
 
413
  session_lm = get_session_lm(session_state)
414
 
 
 
415
  # Create streaming response
 
416
  return StreamingResponse(
 
417
  _generate_streaming_responses(session_state, request.query, session_lm),
 
418
  media_type='text/event-stream',
 
419
  headers={
 
420
  'Cache-Control': 'no-cache',
 
421
  'Connection': 'keep-alive',
 
422
  'Content-Type': 'text/event-stream',
 
423
  'Access-Control-Allow-Origin': '*',
 
424
  'X-Accel-Buffering': 'no'
 
425
  }
 
426
  )
 
427
  except HTTPException:
 
428
  # Re-raise HTTP exceptions to preserve status codes
 
429
  raise
 
430
  except Exception as e:
431
+ logger.log_message(f"Unexpected error in chat_with_all: {str(e)}", level=logging.ERROR)
432
  raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
433
 
434
 
 
 
 
435
  # Helper functions to reduce duplication and improve modularity
 
436
  def _update_session_from_query_params(request_obj: Request, session_state: dict):
 
437
  """Extract and validate chat_id and user_id from query parameters"""
 
438
  # Check for chat_id in query parameters
 
439
  if "chat_id" in request_obj.query_params:
 
440
  try:
 
441
  chat_id_param = int(request_obj.query_params.get("chat_id"))
 
442
  # Update session state with this chat ID
 
443
  session_state["chat_id"] = chat_id_param
 
444
  except (ValueError, TypeError):
 
445
  logger.log_message("Invalid chat_id parameter", level=logging.WARNING)
 
446
  # Continue without updating chat_id
447
 
 
 
448
  # Check for user_id in query parameters
 
449
  if "user_id" in request_obj.query_params:
 
450
  try:
 
451
  user_id = int(request_obj.query_params["user_id"])
 
452
  session_state["user_id"] = user_id
 
453
  except (ValueError, TypeError):
 
454
  raise HTTPException(
 
455
  status_code=400,
 
456
  detail="Invalid user_id in query params. Please provide a valid integer."
 
457
  )
458
 
459
 
460
+ def _validate_agent_name(agent_name: str):
461
+ """Validate that the requested agent(s) exist"""
 
 
 
 
 
 
 
 
 
462
  if "," in agent_name:
 
 
 
463
  agent_list = [agent.strip() for agent in agent_name.split(",")]
 
 
 
464
  for agent in agent_list:
465
+ if agent not in AVAILABLE_AGENTS:
466
+ available = list(AVAILABLE_AGENTS.keys())
 
 
 
 
 
 
 
 
 
467
  raise HTTPException(
468
+ status_code=404,
469
+ detail=f"Agent '{agent}' not found. Available agents: {available}"
 
 
 
470
  )
471
+ elif agent_name not in AVAILABLE_AGENTS:
472
+ available = list(AVAILABLE_AGENTS.keys())
473
+ raise HTTPException(
474
+ status_code=404,
475
+ detail=f"Agent '{agent_name}' not found. Available agents: {available}"
476
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
 
479
  def _prepare_query_with_context(query: str, session_state: dict) -> str:
 
480
  """Prepare the query with chat context from previous messages"""
 
481
  chat_id = session_state.get("chat_id")
 
482
  if not chat_id:
 
483
  return query
 
484
 
 
485
  # Get chat manager from app state
 
486
  chat_manager = app.state._session_manager.chat_manager
 
487
  # Get recent messages
 
488
  recent_messages = chat_manager.get_recent_chat_history(chat_id, limit=MAX_RECENT_MESSAGES)
 
489
  # Extract response history
 
490
  chat_context = chat_manager.extract_response_history(recent_messages)
 
491
 
 
492
  # Append context to the query if available
 
493
  if chat_context:
 
494
  return f"### Current Query:\n{query}\n\n{chat_context}"
 
495
  return query
496
 
497
 
 
 
 
498
  def _track_model_usage(session_state: dict, enhanced_query: str, response, processing_time_ms: int):
 
499
  """Track model usage statistics in the database"""
 
500
  try:
 
501
  ai_manager = app.state.get_ai_manager()
 
502
 
 
503
  # Get model configuration
 
504
  model_config = session_state.get("model_config", DEFAULT_MODEL_CONFIG)
 
505
  model_name = model_config.get("model", DEFAULT_MODEL_CONFIG["model"])
 
506
  provider = ai_manager.get_provider_for_model(model_name)
 
507
 
 
508
  # Calculate token usage
 
509
  try:
 
510
  # Try exact tokenization
 
511
  prompt_tokens = len(ai_manager.tokenizer.encode(enhanced_query))
 
512
  completion_tokens = len(ai_manager.tokenizer.encode(str(response)))
 
513
  total_tokens = prompt_tokens + completion_tokens
 
514
  except Exception as token_error:
 
515
  # Fall back to estimation
 
516
  logger.log_message(f"Tokenization error: {str(token_error)}", level=logging.WARNING)
 
517
  prompt_words = len(enhanced_query.split())
 
518
  completion_words = len(str(response).split())
 
519
  prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
 
520
  completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
 
521
  total_tokens = prompt_tokens + completion_tokens
 
522
 
 
523
  # Calculate cost
 
524
  cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
 
525
 
 
526
  # Save usage to database
 
527
  ai_manager.save_usage_to_db(
 
528
  user_id=session_state.get("user_id"),
 
529
  chat_id=session_state.get("chat_id"),
 
530
  model_name=model_name,
 
531
  provider=provider,
 
532
  prompt_tokens=int(prompt_tokens),
 
533
  completion_tokens=int(completion_tokens),
 
534
  total_tokens=int(total_tokens),
 
535
  query_size=len(enhanced_query),
 
536
  response_size=len(str(response)),
 
537
  cost=round(cost, 7),
 
538
  request_time_ms=processing_time_ms,
 
539
  is_streaming=False
 
540
  )
 
541
  except Exception as e:
 
542
  # Log but don't fail the request if usage tracking fails
 
543
  logger.log_message(f"Failed to track model usage: {str(e)}", level=logging.ERROR)
544
 
545
 
 
 
 
546
  async def _generate_streaming_responses(session_state: dict, query: str, session_lm):
 
547
  """Generate streaming responses for chat_with_all endpoint"""
 
548
  overall_start_time = time.time()
 
549
  total_response = ""
 
550
  total_inputs = ""
 
551
  usage_records = []
552
 
553
+ try:
554
+ # Add chat context from previous messages
555
+ enhanced_query = _prepare_query_with_context(query, session_state)
556
+
557
+ # Use the session model for this specific request
558
+ with dspy.context(lm=session_lm):
559
+ try:
560
+ # Get the plan
561
+ plan_response = await asyncio.wait_for(
562
+ asyncio.to_thread(session_state["ai_system"].get_plan, enhanced_query),
563
+ timeout=REQUEST_TIMEOUT_SECONDS
564
+ )
565
+
566
+ plan_description = format_response_to_markdown(
567
+ {"analytical_planner": plan_response},
568
+ dataframe=session_state["current_df"]
569
+ )
570
+
571
+ # Check if plan is valid
572
+ if plan_description == RESPONSE_ERROR_INVALID_QUERY:
573
+ yield json.dumps({
574
+ "agent": "Analytical Planner",
575
+ "content": plan_description,
576
+ "status": "error"
577
+ }) + "\n"
578
+ return
579
+
580
+ yield json.dumps({
581
+ "agent": "Analytical Planner",
582
+ "content": plan_description,
583
+ "status": "success" if plan_description else "error"
584
+ }) + "\n"
585
+
586
+ # Track planner usage
587
+ if session_state.get("user_id"):
588
+ planner_tokens = _estimate_tokens(ai_manager=app.state.ai_manager,
589
+ input_text=enhanced_query,
590
+ output_text=plan_description)
591
+
592
+ usage_records.append(_create_usage_record(
593
+ session_state=session_state,
594
+ model_name=session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"],
595
+ prompt_tokens=planner_tokens["prompt"],
596
+ completion_tokens=planner_tokens["completion"],
597
+ query_size=len(enhanced_query),
598
+ response_size=len(plan_description),
599
+ processing_time_ms=int((time.time() - overall_start_time) * 1000),
600
+ is_streaming=False
601
+ ))
602
+
603
+ # Execute the plan with well-managed concurrency
604
+ async for agent_name, inputs, response in _execute_plan_with_timeout(
605
+ session_state["ai_system"], enhanced_query, plan_response):
606
+
607
+ if agent_name == "plan_not_found":
608
+ yield json.dumps({
609
+ "agent": "Analytical Planner",
610
+ "content": "**No plan found**\n\nPlease try again with a different query or try using a different model.",
611
+ "status": "error"
612
+ }) + "\n"
613
+ return
614
+
615
+ formatted_response = format_response_to_markdown(
616
+ {agent_name: response},
617
+ dataframe=session_state["current_df"]
618
+ ) or "No response generated"
619
 
620
+ if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
621
+ yield json.dumps({
622
+ "agent": agent_name,
623
+ "content": formatted_response,
624
+ "status": "error"
625
+ }) + "\n"
626
+ return
627
+
628
+ # if "code_combiner_agent" in agent_name:
629
+ # # logger.log_message(f"[>] Code combiner response: {response}", level=logging.INFO)
630
+ # total_response += str(response) if response else ""
631
+ # total_inputs += str(inputs) if inputs else ""
632
+
633
+ # Send response chunk
634
+ yield json.dumps({
635
+ "agent": agent_name.split("__")[0] if "__" in agent_name else agent_name,
636
+ "content": formatted_response,
637
+ "status": "success" if response else "error"
638
+ }) + "\n"
639
+
640
+ # Track agent usage for future batch DB write
641
+ if session_state.get("user_id"):
642
+ agent_tokens = _estimate_tokens(
643
+ ai_manager=app.state.ai_manager,
644
+ input_text=str(inputs),
645
+ output_text=str(response)
646
+ )
647
+
648
+ # Get appropriate model name for code combiner
649
+ if "code_combiner_agent" in agent_name and "__" in agent_name:
650
+ provider = agent_name.split("__")[1]
651
+ model_name = _get_model_name_for_provider(provider)
652
+ else:
653
+ model_name = session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"]
654
+
655
+ usage_records.append(_create_usage_record(
656
+ session_state=session_state,
657
+ model_name=model_name,
658
+ prompt_tokens=agent_tokens["prompt"],
659
+ completion_tokens=agent_tokens["completion"],
660
+ query_size=len(str(inputs)),
661
+ response_size=len(str(response)),
662
+ processing_time_ms=int((time.time() - overall_start_time) * 1000),
663
+ is_streaming=True
664
+ ))
665
+
666
+ except asyncio.TimeoutError:
667
+ yield json.dumps({
668
+ "agent": "planner",
669
+ "content": "The request timed out. Please try a simpler query.",
670
+ "status": "error"
671
+ }) + "\n"
672
+ return
673
+ except Exception as e:
674
+ logger.log_message(f"Error in streaming response: {str(e)}", level=logging.ERROR)
675
+ yield json.dumps({
676
+ "agent": "planner",
677
+ "content": "An error occurred while generating responses. Please try again!",
678
+ "status": "error"
679
+ }) + "\n"
680
+ return
681
+
682
+ # Batch write usage records to DB
683
+ if usage_records and session_state.get("user_id"):
684
+ try:
685
+ # In a real implementation, you would batch these writes
686
+ # For now, we're writing them one by one but could be optimized
687
+ ai_manager = app.state.get_ai_manager()
688
+ for record in usage_records:
689
+ ai_manager.save_usage_to_db(**record)
690
+ except Exception as db_error:
691
+ logger.log_message(f"Failed to save usage records: {str(db_error)}", level=logging.ERROR)
692
+
693
+ except Exception as e:
694
+ logger.log_message(f"Streaming response generation failed: {str(e)}", level=logging.ERROR)
695
+ yield json.dumps({
696
+ "agent": "planner",
697
+ "content": "An error occurred while generating responses. Please try again!",
698
+ "status": "error"
699
+ }) + "\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
 
702
  def _estimate_tokens(ai_manager, input_text: str, output_text: str) -> dict:
 
703
  """Estimate token counts, with fallback for tokenization errors"""
 
704
  try:
 
705
  # Try exact tokenization
 
706
  prompt_tokens = len(ai_manager.tokenizer.encode(input_text))
 
707
  completion_tokens = len(ai_manager.tokenizer.encode(output_text))
 
708
  except Exception:
 
709
  # Fall back to estimation
 
710
  prompt_words = len(input_text.split())
 
711
  completion_words = len(output_text.split())
 
712
  prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
 
713
  completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
 
714
 
 
715
  return {
 
716
  "prompt": prompt_tokens,
 
717
  "completion": completion_tokens,
 
718
  "total": prompt_tokens + completion_tokens
 
719
  }
720
 
721
 
 
 
 
722
  def _create_usage_record(session_state: dict, model_name: str, prompt_tokens: int,
 
723
  completion_tokens: int, query_size: int, response_size: int,
 
724
  processing_time_ms: int, is_streaming: bool) -> dict:
 
725
  """Create a usage record for the database"""
 
726
  ai_manager = app.state.get_ai_manager()
 
727
  provider = ai_manager.get_provider_for_model(model_name)
 
728
  cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
 
729
 
 
730
  return {
 
731
  "user_id": session_state.get("user_id"),
 
732
  "chat_id": session_state.get("chat_id"),
 
733
  "model_name": model_name,
 
734
  "provider": provider,
 
735
  "prompt_tokens": int(prompt_tokens),
 
736
  "completion_tokens": int(completion_tokens),
 
737
  "total_tokens": int(prompt_tokens + completion_tokens),
 
738
  "query_size": query_size,
 
739
  "response_size": response_size,
 
740
  "cost": round(cost, 7),
 
741
  "request_time_ms": processing_time_ms,
 
742
  "is_streaming": is_streaming
 
743
  }
744
 
745
 
 
 
 
746
  def _get_model_name_for_provider(provider: str) -> str:
 
747
  """Get the model name for a provider"""
 
748
  provider_model_map = {
749
+ "openai": "o3-mini",
750
+ "anthropic": "claude-3-7-sonnet-latest",
 
751
  "gemini": "gemini-2.5-pro-preview-03-25"
752
  }
753
+ return provider_model_map.get(provider, "o3-mini")
754
 
755
 
756
+ async def _execute_plan_with_timeout(ai_system, enhanced_query, plan_response):
757
+ """Execute the plan with timeout handling for each step"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  try:
759
+ # Use asyncio.create_task to run the execute_plan coroutine
760
+ async for agent_name, inputs, response in ai_system.execute_plan(enhanced_query, plan_response):
761
+ # Yield results as they come
762
+ yield agent_name, inputs, response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  except Exception as e:
764
+ logger.log_message(f"Error executing plan: {str(e)}", level=logging.ERROR)
765
+ yield "error", None, {"error": "An error occurred during plan execution"}
 
 
766
 
767
 
768
+ # Add an endpoint to list available agents
769
+ @app.get("/agents", response_model=dict)
770
+ async def list_agents():
771
+ return {
772
+ "available_agents": list(AVAILABLE_AGENTS.keys()),
773
+ "description": "List of available specialized agents that can be called using @agent_name"
774
+ }
775
 
776
  @app.get("/health", response_model=dict)
 
777
  async def health():
 
778
  return {"message": "API is healthy and running"}
779
 
 
 
780
  @app.get("/")
 
781
  async def index():
 
782
  return {
 
783
  "title": "Welcome to the AI Analytics API",
 
784
  "message": "Explore our API for advanced analytics and visualization tools designed to empower your data-driven decisions.",
 
785
  "description": "Utilize our powerful agents and models to gain insights from your data effortlessly.",
 
786
  "colors": {
 
787
  "primary": "#007bff",
 
788
  "secondary": "#6c757d",
 
789
  "success": "#28a745",
 
790
  "danger": "#dc3545",
 
791
  },
 
792
  "features": [
 
793
  "Real-time data processing",
 
794
  "Customizable visualizations",
 
795
  "Seamless integration with various data sources",
 
796
  "User-friendly interface for easy navigation",
 
797
  "Custom Analytics",
 
798
  ],
 
799
  }
800
 
 
 
801
  @app.post("/chat_history_name")
 
802
  async def chat_history_name(request: dict, session_id: str = Depends(get_session_id_dependency)):
 
803
  query = request.get("query")
 
804
  name = None
 
805
 
806
+ lm = dspy.LM(model="gpt-4o-mini", max_tokens=300, temperature=0.5)
 
 
807
 
 
808
  with dspy.context(lm=lm):
 
809
  name = app.state.get_chat_history_name_agent()(query=str(query))
 
810
 
 
811
  return {"name": name.name if name else "New Chat"}
812
 
813
+ # In the section where routers are included, add the session_router
814
+ app.include_router(chat_router)
815
+ app.include_router(analytics_router)
816
+ app.include_router(code_router)
817
+ app.include_router(session_router)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
 
819
  if __name__ == "__main__":
820
+ uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
 
 
chat_database.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60428289d8368524e19a3e344fcd0ff6d0bbf91db313d491ee72ce388cf3648a
3
+ size 356352
docs/README.md DELETED
@@ -1,251 +0,0 @@
1
- # Auto-Analyst Backend Documentation
2
-
3
- This directory contains comprehensive documentation for the Auto-Analyst backend - a sophisticated multi-agent AI platform for data analysis built with FastAPI, DSPy, and modern Python technologies.
4
-
5
- ## 📁 Documentation Structure
6
-
7
- ### **🏗️ Architecture** (`/architecture/`)
8
- - **[System Architecture](./architecture/architecture.md)** - Comprehensive overview of backend system design, components, and data flow patterns
9
-
10
- ### **🚀 Development** (`/development/`)
11
- - **[Development Workflow](./development/development_workflow.md)** - Complete development guide with patterns, best practices, and code organization principles
12
-
13
- ### **🔧 System** (`/system/`)
14
- - **[Database Schema](./system/database-schema.md)** - Complete database schema with all tables, relationships, and performance optimization
15
- - **[Shared DataFrame System](./system/shared_dataframe.md)** - Inter-agent data sharing and session management
16
-
17
- ### **🌐 API** (`/api/`)
18
- - **[API Endpoints Overview](./api/README.md)** - Main API reference hub
19
- - **[Route Documentation](./api/routes/)** - Detailed endpoint documentation:
20
- - **[Core Routes](./api/routes/session.md)** - File uploads, sessions, authentication
21
- - **[Chat Routes](./api/routes/chats.md)** - Chat and messaging endpoints
22
- - **[Code Routes](./api/routes/code.md)** - Code execution and processing
23
- - **[Analytics Routes](./api/routes/analytics.md)** - Usage analytics and monitoring
24
- - **[Deep Analysis Routes](./api/routes/deep_analysis.md)** - Multi-agent analysis system
25
- - **[Template Routes](./api/routes/templates.md)** - Agent template management
26
- - **[Feedback Routes](./api/routes/feedback.md)** - User feedback and rating system
27
-
28
- ### **🐛 Troubleshooting** (`/troubleshooting/`)
29
- - **[Troubleshooting Guide](./troubleshooting/troubleshooting.md)** - Common issues, debugging tools, and solutions
30
-
31
- ## 🎯 Backend Overview
32
-
33
- ### **Tech Stack**
34
- - **FastAPI** - Modern async Python web framework
35
- - **DSPy** - AI agent orchestration and LLM integration
36
- - **SQLAlchemy** - Database ORM with PostgreSQL/SQLite support
37
- - **Plotly** - Interactive data visualizations
38
- - **Pandas/NumPy** - Data manipulation and analysis
39
- - **Scikit-learn** - Machine learning models
40
- - **Statsmodels** - Statistical analysis
41
-
42
- ### **Core Features**
43
- - **Multi-Agent System** - 4+ specialized AI agents for different analysis tasks
44
- - **Template System** - User-customizable agent configurations
45
- - **Deep Analysis** - Multi-step analytical workflows with streaming progress
46
- - **Session Management** - Stateful user sessions with shared data context
47
- - **Code Execution** - Safe Python code execution environment
48
- - **Real-time Streaming** - WebSocket support for live analysis updates
49
-
50
- ### **Agent Types**
51
- 1. **Data Preprocessing Agent** - Data cleaning and preparation
52
- 2. **Statistical Analytics Agent** - Statistical analysis using statsmodels
53
- 3. **Machine Learning Agent** - ML modeling with scikit-learn
54
- 4. **Data Visualization Agent** - Interactive charts with Plotly
55
- 5. **Feature Engineering Agent** (Premium) - Advanced feature creation
56
- 6. **Polars Agent** (Premium) - High-performance data processing
57
-
58
- ## 🚀 Quick Start Guide
59
-
60
- ### **1. Environment Setup**
61
-
62
- ```bash
63
- # Navigate to backend directory
64
- cd Auto-Analyst-CS/auto-analyst-backend
65
-
66
- # Create virtual environment
67
- python -m venv venv
68
- source venv/bin/activate # Linux/Mac
69
- venv\Scripts\activate # Windows
70
-
71
- # Install dependencies
72
- pip install -r requirements.txt
73
- ```
74
-
75
- ### **2. Environment Configuration**
76
-
77
- Create `.env` file with required variables:
78
-
79
- ```env
80
- # Database Configuration
81
- DATABASE_URL=sqlite:///./chat_database.db
82
-
83
- # AI Model Configuration
84
- OPENAI_API_KEY=your-openai-api-key
85
- MODEL_PROVIDER=openai # openai, anthropic, groq, gemini
86
- MODEL_NAME=gpt-4o-mini
87
- TEMPERATURE=0.7
88
- MAX_TOKENS=6000
89
-
90
- # Optional: Additional AI Providers
91
- ANTHROPIC_API_KEY=your-anthropic-key
92
- GROQ_API_KEY=your-groq-key
93
- GEMINI_API_KEY=your-gemini-key
94
-
95
- # Security
96
- ADMIN_API_KEY=your-admin-key
97
-
98
- # Application Settings
99
- ENVIRONMENT=development
100
- FRONTEND_URL=http://localhost:3000/
101
- ```
102
-
103
- ### **3. Database Initialization**
104
-
105
- ```bash
106
- # Initialize database and default agents
107
- python -c "
108
- from src.db.init_db import init_db
109
- init_db()
110
- print('✅ Database and agents initialized successfully')
111
- "
112
- ```
113
-
114
- ### **4. Start Development Server**
115
-
116
- ```bash
117
- # Start the FastAPI server
118
- python -m app
119
-
120
- # Or with uvicorn for more control
121
- uvicorn app:app --reload --host 0.0.0.0 --port 8000
122
- ```
123
-
124
- ### **5. Verify Installation**
125
-
126
- - **API Documentation**: `http://localhost:8000/docs`
127
- - **Health Check**: `http://localhost:8000/health`
128
-
129
- ## 🔧 Development Workflow
130
-
131
- ### **Adding New Agents**
132
-
133
- 1. **Define Agent Signature** in `src/agents/agents.py`
134
- 2. **Add Configuration** to `agents_config.json`
135
- 3. **Register Agent** in loading system
136
- 4. **Test Integration** with multi-agent pipeline
137
-
138
- ### **Adding New API Endpoints**
139
-
140
- 1. **Create Route File** in `src/routes/`
141
- 2. **Define Pydantic Models** for request/response
142
- 3. **Implement Endpoints** with proper error handling
143
- 4. **Register Router** in `app.py`
144
- 5. **Update Documentation**
145
-
146
- ### **Database Changes**
147
-
148
- 1. **Modify Models** in `src/db/schemas/models.py`
149
- 2. **Create Migration**: `alembic revision --autogenerate -m "description"`
150
- 3. **Apply Migration**: `alembic upgrade head`
151
- 4. **Update Documentation**
152
-
153
- ## 📊 System Architecture
154
-
155
- ### **Request Processing Flow**
156
- ```
157
- HTTP Request → FastAPI Router → Route Handler → Business Logic →
158
- Database/Agent System → AI Model → Response Processing → JSON Response
159
- ```
160
-
161
- ### **Agent Execution Flow**
162
- ```
163
- User Query → Session Manager → Agent Selection → Context Preparation →
164
- DSPy Chain → AI Model → Code Generation → Execution → Response Formatting
165
- ```
166
-
167
- ### **Deep Analysis Workflow**
168
- ```
169
- Goal Input → Question Generation → Planning → Multi-Agent Execution →
170
- Code Synthesis → Result Compilation → HTML Report Generation
171
- ```
172
-
173
- ## 🧪 Testing & Validation
174
-
175
- ### **API Testing**
176
- ```bash
177
- # Interactive documentation
178
- open http://localhost:8000/docs
179
-
180
- # cURL examples
181
- curl -X GET "http://localhost:8000/health"
182
- curl -X POST "http://localhost:8000/chat/preprocessing_agent" \
183
- -H "Content-Type: application/json" \
184
- -d '{"query": "Clean this dataset", "session_id": "test"}'
185
- ```
186
-
187
- ### **Agent Testing**
188
- ```python
189
- # Test individual agents
190
- from src.agents.agents import preprocessing_agent
191
- import dspy
192
-
193
- # Configure DSPy
194
- lm = dspy.LM('openai/gpt-4o-mini', api_key='your-key')
195
- dspy.configure(lm=lm)
196
-
197
- # Test agent
198
- agent = dspy.ChainOfThought(preprocessing_agent)
199
- result = agent(goal='clean data', dataset='test dataset')
200
- print(result)
201
- ```
202
-
203
- ## 🔒 Security & Production
204
-
205
- ### **Security Features**
206
- - **Session-based authentication** with secure session management
207
- - **API key protection** for admin endpoints
208
- - **Input validation** using Pydantic models
209
- - **Error handling** with proper HTTP status codes
210
- - **CORS configuration** for frontend integration
211
-
212
- ### **Production Considerations**
213
- - **PostgreSQL database** for production deployment
214
- - **Environment variable management** for secrets
215
- - **Logging configuration** for monitoring
216
- - **Rate limiting** for API protection
217
- - **Performance optimization** for large datasets
218
-
219
- ## 📈 Monitoring & Analytics
220
-
221
- The backend includes comprehensive analytics for:
222
- - **Usage tracking** - API endpoint usage and performance
223
- - **Model usage** - AI model consumption and costs
224
- - **User analytics** - User behavior and engagement
225
- - **Error monitoring** - System health and error tracking
226
- - **Performance metrics** - Response times and throughput
227
-
228
- ## 🤝 Contributing
229
-
230
- 1. **Follow coding standards** defined in development workflow
231
- 2. **Add comprehensive tests** for new features
232
- 3. **Update documentation** for all changes
233
- 4. **Use proper error handling** patterns
234
- 5. **Submit detailed pull requests** with clear descriptions
235
-
236
- ---
237
-
238
- ## 📖 Detailed Documentation
239
-
240
- For specific implementation details, refer to the organized documentation in each subdirectory:
241
-
242
- - **[Getting Started Guide](./getting_started.md)** - Complete setup walkthrough
243
- - **[Architecture Documentation](./architecture/)** - System design and components
244
- - **[Development Guides](./development/)** - Workflow and best practices
245
- - **[API Reference](./api/)** - Complete endpoint documentation
246
- - **[System Documentation](./system/)** - Database and core systems
247
- - **[Troubleshooting](./troubleshooting/)** - Debugging and solutions
248
-
249
- ---
250
-
251
- **Need help?** Check the troubleshooting guide or refer to the comprehensive documentation in each section.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/analytics.md DELETED
@@ -1,562 +0,0 @@
1
- # Analytics Routes Documentation
2
-
3
- These routes provide comprehensive analytics functionality for the Auto-Analyst backend, including dashboard summaries, user analytics, model performance metrics, cost analysis, and system monitoring.
4
-
5
- ## Authentication
6
-
7
- All analytics endpoints require admin authentication via API key:
8
-
9
- ```python
10
- ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "default-admin-key-change-me")
11
- ```
12
-
13
- The API key can be provided via:
14
- - **Header:** `X-Admin-API-Key`
15
- - **Query parameter:** `admin_api_key`
16
-
17
- ---
18
-
19
- ## Dashboard Endpoints
20
-
21
- ### **GET /analytics/dashboard**
22
- Returns comprehensive dashboard data combining usage statistics, model performance, and user activity.
23
-
24
- **Query Parameters:**
25
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
26
-
27
- **Response:**
28
- ```json
29
- {
30
- "total_tokens": 123456,
31
- "total_cost": 25.50,
32
- "total_requests": 1000,
33
- "total_users": 50,
34
- "daily_usage": [
35
- {
36
- "date": "2023-05-01",
37
- "tokens": 5000,
38
- "cost": 1.25,
39
- "requests": 100
40
- }
41
- ],
42
- "model_usage": [
43
- {
44
- "model_name": "claude-3-sonnet-20241022",
45
- "tokens": 10000,
46
- "cost": 10.00,
47
- "requests": 200
48
- }
49
- ],
50
- "top_users": [
51
- {
52
- "user_id": "123",
53
- "tokens": 5000,
54
- "cost": 5.00,
55
- "requests": 50
56
- }
57
- ],
58
- "start_date": "2023-04-01",
59
- "end_date": "2023-05-01"
60
- }
61
- ```
62
-
63
- ### **WebSocket /analytics/dashboard/realtime**
64
- WebSocket endpoint for real-time dashboard updates. Accepts connections and maintains them for broadcasting live data updates.
65
-
66
- ---
67
-
68
- ## User Analytics Endpoints
69
-
70
- ### **GET /analytics/users**
71
- Returns user list with usage statistics from the past 7 days.
72
-
73
- **Query Parameters:**
74
- - `limit` (optional): Maximum users to return (default: `100`)
75
- - `offset` (optional): Pagination offset (default: `0`)
76
-
77
- **Response:**
78
- ```json
79
- {
80
- "users": [
81
- {
82
- "user_id": "123",
83
- "tokens": 5000,
84
- "cost": 5.00,
85
- "requests": 50,
86
- "first_seen": "2023-04-01T12:00:00Z",
87
- "last_seen": "2023-05-01T12:00:00Z"
88
- }
89
- ],
90
- "total": 200,
91
- "limit": 100,
92
- "offset": 0
93
- }
94
- ```
95
-
96
- ### **GET /analytics/users/activity**
97
- Returns daily user activity metrics with new user tracking.
98
-
99
- **Query Parameters:**
100
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
101
-
102
- **Response:**
103
- ```json
104
- {
105
- "user_activity": [
106
- {
107
- "date": "2023-05-01",
108
- "activeUsers": 20,
109
- "newUsers": 5,
110
- "sessions": 30
111
- }
112
- ]
113
- }
114
- ```
115
-
116
- ### **GET /analytics/users/sessions/stats**
117
- Returns session statistics including total users, active users today, average queries per session, and average session time.
118
-
119
- **Response:**
120
- ```json
121
- {
122
- "totalUsers": 500,
123
- "activeToday": 25,
124
- "avgQueriesPerSession": 3.2,
125
- "avgSessionTime": 300
126
- }
127
- ```
128
-
129
- ### **WebSocket /analytics/realtime**
130
- WebSocket endpoint for real-time user analytics updates.
131
-
132
- ---
133
-
134
- ## Model Analytics Endpoints
135
-
136
- ### **GET /analytics/usage/models**
137
- Returns model usage breakdown with performance metrics.
138
-
139
- **Query Parameters:**
140
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
141
-
142
- **Response:**
143
- ```json
144
- {
145
- "model_usage": [
146
- {
147
- "model_name": "claude-3-sonnet-20241022",
148
- "tokens": 10000,
149
- "cost": 10.00,
150
- "requests": 200,
151
- "avg_response_time": 1.5
152
- }
153
- ]
154
- }
155
- ```
156
-
157
- ### **GET /analytics/models/history**
158
- Returns daily model usage history with trend data.
159
-
160
- **Query Parameters:**
161
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
162
-
163
- **Response:**
164
- ```json
165
- {
166
- "model_history": [
167
- {
168
- "date": "2023-05-01",
169
- "models": [
170
- {
171
- "name": "claude-3-sonnet-20241022",
172
- "tokens": 5000,
173
- "requests": 100
174
- }
175
- ]
176
- }
177
- ]
178
- }
179
- ```
180
-
181
- ### **GET /analytics/models/metrics**
182
- Returns model performance metrics including success rates and response times.
183
-
184
- **Response:**
185
- ```json
186
- {
187
- "model_metrics": [
188
- {
189
- "name": "claude-3-sonnet-20241022",
190
- "avg_tokens": 250.5,
191
- "avg_response_time": 1.2,
192
- "success_rate": 0.95
193
- }
194
- ]
195
- }
196
- ```
197
-
198
- ---
199
-
200
- ## Cost Analytics Endpoints
201
-
202
- ### **GET /analytics/costs/summary**
203
- Returns cost summary with averages and totals.
204
-
205
- **Query Parameters:**
206
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
207
-
208
- **Response:**
209
- ```json
210
- {
211
- "totalCost": 25.50,
212
- "totalTokens": 100000,
213
- "totalRequests": 1000,
214
- "avgDailyCost": 0.85,
215
- "costPerThousandTokens": 0.255,
216
- "daysInPeriod": 30,
217
- "startDate": "2023-04-01",
218
- "endDate": "2023-05-01"
219
- }
220
- ```
221
-
222
- ### **GET /analytics/costs/daily**
223
- Returns daily cost breakdown with filled gaps for missing dates.
224
-
225
- **Query Parameters:**
226
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
227
-
228
- **Response:**
229
- ```json
230
- {
231
- "daily_costs": [
232
- {
233
- "date": "2023-05-01",
234
- "cost": 1.25,
235
- "tokens": 5000
236
- }
237
- ]
238
- }
239
- ```
240
-
241
- ### **GET /analytics/costs/models**
242
- Returns cost breakdown by model.
243
-
244
- **Query Parameters:**
245
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
246
-
247
- **Response:**
248
- ```json
249
- {
250
- "model_costs": [
251
- {
252
- "model_name": "claude-3-sonnet-20241022",
253
- "cost": 15.50,
254
- "tokens": 50000,
255
- "requests": 500
256
- }
257
- ]
258
- }
259
- ```
260
-
261
- ### **GET /analytics/costs/projections**
262
- Returns cost projections based on last 30 days usage.
263
-
264
- **Response:**
265
- ```json
266
- {
267
- "nextMonth": 75.00,
268
- "next3Months": 225.00,
269
- "nextYear": 900.00,
270
- "tokensNextMonth": 300000,
271
- "dailyCost": 2.50,
272
- "dailyTokens": 10000,
273
- "baselineDays": 30
274
- }
275
- ```
276
-
277
- ### **GET /analytics/costs/today**
278
- Returns today's cost data.
279
-
280
- **Response:**
281
- ```json
282
- {
283
- "date": "2023-05-01",
284
- "cost": 2.50,
285
- "tokens": 10000,
286
- "requests": 100
287
- }
288
- ```
289
-
290
- ---
291
-
292
- ## Tier Analytics Endpoints
293
-
294
- ### **GET /analytics/tiers/usage**
295
- Returns usage data categorized by model tiers with aggregated statistics.
296
-
297
- **Query Parameters:**
298
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
299
-
300
- **Response:**
301
- ```json
302
- {
303
- "tier_data": {
304
- "tier_1": {
305
- "name": "Basic",
306
- "credits": 1,
307
- "total_tokens": 50000,
308
- "total_requests": 500,
309
- "total_cost": 5.00,
310
- "avg_tokens_per_query": 100,
311
- "cost_per_1k_tokens": 0.10,
312
- "total_credit_cost": 500,
313
- "cost_per_credit": 0.01,
314
- "models": [...]
315
- }
316
- },
317
- "period": "30d",
318
- "start_date": "2023-04-01",
319
- "end_date": "2023-05-01"
320
- }
321
- ```
322
-
323
- ### **GET /analytics/tiers/projections**
324
- Returns tier-based cost and usage projections.
325
-
326
- **Response:**
327
- ```json
328
- {
329
- "daily_usage": {...},
330
- "projections": {
331
- "monthly": {...},
332
- "quarterly": {...},
333
- "yearly": {...}
334
- },
335
- "tier_definitions": {...}
336
- }
337
- ```
338
-
339
- ### **GET /analytics/tiers/efficiency**
340
- Returns efficiency metrics by tier including cost per credit and tokens per credit.
341
-
342
- **Query Parameters:**
343
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
344
-
345
- **Response:**
346
- ```json
347
- {
348
- "efficiency_data": {...},
349
- "most_efficient_tier": "tier_2",
350
- "best_value_tier": "tier_1",
351
- "period": "30d",
352
- "start_date": "2023-04-01",
353
- "end_date": "2023-05-01"
354
- }
355
- ```
356
-
357
- ---
358
-
359
- ## Code Execution Analytics Endpoints
360
-
361
- ### **GET /analytics/code-executions/summary**
362
- Returns code execution statistics including success rates and model performance.
363
-
364
- **Query Parameters:**
365
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
366
-
367
- **Response:**
368
- ```json
369
- {
370
- "period": "30d",
371
- "start_date": "2023-04-01",
372
- "end_date": "2023-05-01",
373
- "overall_stats": {
374
- "total_executions": 1000,
375
- "successful_executions": 950,
376
- "failed_executions": 50,
377
- "success_rate": 0.95,
378
- "total_users": 100,
379
- "total_chats": 200
380
- },
381
- "model_performance": [...],
382
- "failed_agents": [...]
383
- }
384
- ```
385
-
386
- ### **GET /analytics/code-executions/detailed**
387
- Returns detailed code execution records with filtering options.
388
-
389
- **Query Parameters:**
390
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
391
- - `success_filter` (optional): Filter by success status (boolean)
392
- - `user_id` (optional): Filter by user ID
393
- - `model_name` (optional): Filter by model name
394
- - `limit` (optional): Maximum results (default: `100`)
395
-
396
- **Response:**
397
- ```json
398
- {
399
- "period": "30d",
400
- "start_date": "2023-04-01",
401
- "end_date": "2023-05-01",
402
- "count": 50,
403
- "executions": [...]
404
- }
405
- ```
406
-
407
- ### **GET /analytics/code-executions/users**
408
- Returns code execution statistics grouped by user.
409
-
410
- **Query Parameters:**
411
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
412
- - `limit` (optional): Maximum users (default: `50`)
413
-
414
- **Response:**
415
- ```json
416
- {
417
- "period": "30d",
418
- "start_date": "2023-04-01",
419
- "end_date": "2023-05-01",
420
- "users": [...]
421
- }
422
- ```
423
-
424
- ### **GET /analytics/code-executions/error-analysis**
425
- Returns error analysis with categorized error types and agent failure patterns.
426
-
427
- **Query Parameters:**
428
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
429
-
430
- **Response:**
431
- ```json
432
- {
433
- "period": "30d",
434
- "start_date": "2023-04-01",
435
- "end_date": "2023-05-01",
436
- "total_failed_executions": 50,
437
- "error_types": [...],
438
- "error_by_agent": [...]
439
- }
440
- ```
441
-
442
- ---
443
-
444
- ## Feedback Analytics Endpoints
445
-
446
- ### **GET /analytics/feedback/summary**
447
- Returns feedback summary statistics including rating distributions and trends.
448
-
449
- **Query Parameters:**
450
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
451
-
452
- **Response:**
453
- ```json
454
- {
455
- "period": "30d",
456
- "start_date": "2023-04-01",
457
- "end_date": "2023-05-01",
458
- "total_feedback": 500,
459
- "avg_rating": 4.2,
460
- "chats_with_feedback": 200,
461
- "ratings_distribution": [
462
- {"rating": 1, "count": 10},
463
- {"rating": 2, "count": 20},
464
- {"rating": 3, "count": 50},
465
- {"rating": 4, "count": 200},
466
- {"rating": 5, "count": 220}
467
- ],
468
- "models_data": [...],
469
- "feedback_trend": [...]
470
- }
471
- ```
472
-
473
- ### **GET /analytics/feedback/detailed**
474
- Returns detailed feedback records with filtering and pagination.
475
-
476
- **Query Parameters:**
477
- - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
478
- - `min_rating` (optional): Minimum rating filter
479
- - `max_rating` (optional): Maximum rating filter
480
- - `model_name` (optional): Filter by model name
481
- - `limit` (optional): Maximum results (default: `100`)
482
- - `offset` (optional): Pagination offset (default: `0`)
483
-
484
- **Response:**
485
- ```json
486
- {
487
- "period": "30d",
488
- "start_date": "2023-04-01",
489
- "end_date": "2023-05-01",
490
- "total": 500,
491
- "count": 100,
492
- "offset": 0,
493
- "limit": 100,
494
- "feedback": [...]
495
- }
496
- ```
497
-
498
- ---
499
-
500
- ## Public Endpoints
501
-
502
- ### **GET /analytics/public/ticker**
503
- Returns public ticker data for landing page statistics. **No authentication required.**
504
-
505
- **Response:**
506
- ```json
507
- {
508
- "total_signups": 1000,
509
- "total_tokens": 5000000,
510
- "total_requests": 50000,
511
- "last_updated": "2023-05-01T12:00:00Z"
512
- }
513
- ```
514
-
515
- ---
516
-
517
- ## Utility Endpoints
518
-
519
- ### **GET /analytics/usage/summary**
520
- Returns overall usage summary (legacy endpoint, calls dashboard with 30d period).
521
-
522
- ### **GET /analytics/debug/model_usage**
523
- Debug endpoint for testing admin API key validation.
524
-
525
- **Response:**
526
- ```json
527
- {
528
- "status": "success",
529
- "message": "Admin API key validated successfully"
530
- }
531
- ```
532
-
533
- ---
534
-
535
- ## Error Categorization
536
-
537
- The system automatically categorizes code execution errors into the following types:
538
-
539
- - **NameError**: Variable or function name not found
540
- - **SyntaxError**: Invalid Python syntax
541
- - **TypeError**: Type-related errors
542
- - **AttributeError**: Attribute access errors
543
- - **IndexError/KeyError**: Index or key access errors
544
- - **ImportError**: Module import errors
545
- - **ValueError**: Invalid values passed to functions
546
- - **OperationError**: Unsupported operations
547
- - **IndentationError**: Python indentation errors
548
- - **PermissionError**: File/system permission errors
549
- - **FileNotFoundError**: File access errors
550
- - **MemoryError**: Memory allocation errors
551
- - **TimeoutError**: Operation timeout errors
552
- - **OtherError**: Uncategorized errors
553
-
554
- ## Real-time Updates
555
-
556
- The analytics system supports real-time updates through WebSocket connections:
557
-
558
- - **Dashboard updates**: Broadcasted when new model usage is recorded
559
- - **User activity updates**: Broadcasted for user activity changes
560
- - **Model performance updates**: Broadcasted for model-specific metrics
561
-
562
- All real-time updates are sent as JSON messages with `type` field indicating the update category and `metrics` containing the delta or new values.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/code.md DELETED
@@ -1,182 +0,0 @@
1
- # Code Routes Documentation
2
-
3
- This document describes the API endpoints available for code execution, editing, fixing, and cleaning operations in the Auto-Analyst backend.
4
-
5
- ## Base URL
6
-
7
- All code-related endpoints are prefixed with `/code`.
8
-
9
- ## Endpoints
10
-
11
- ### Execute Code
12
- Executes Python code against the current session's dataframe.
13
-
14
- **Endpoint:** `POST /code/execute`
15
-
16
- **Request Body:**
17
- ```json
18
- {
19
- "code": "string", // Python code to execute
20
- "session_id": "string", // Optional session ID
21
- "message_id": 123 // Optional message ID for tracking
22
- }
23
- ```
24
-
25
- **Response:**
26
- ```json
27
- {
28
- "output": "string", // Execution output
29
- "plotly_outputs": [ // Optional array of plotly outputs
30
- "string"
31
- ]
32
- }
33
- ```
34
-
35
- **Error Responses:**
36
- - `400 Bad Request`: No dataset loaded or no code provided
37
- - `500 Internal Server Error`: Execution error
38
-
39
- ### Edit Code
40
- Uses AI to edit code based on user instructions.
41
-
42
- **Endpoint:** `POST /code/edit`
43
-
44
- **Request Body:**
45
- ```json
46
- {
47
- "original_code": "string", // Code to be edited
48
- "user_prompt": "string" // Instructions for editing
49
- }
50
- ```
51
-
52
- **Response:**
53
- ```json
54
- {
55
- "edited_code": "string" // The edited code
56
- }
57
- ```
58
-
59
- **Error Responses:**
60
- - `400 Bad Request`: Missing original code or editing instructions
61
- - `500 Internal Server Error`: Editing error
62
-
63
- ### Fix Code
64
- Uses AI to fix code with errors, employing a block-by-block approach with DSPy refinement.
65
-
66
- **Endpoint:** `POST /code/fix`
67
-
68
- **Request Body:**
69
- ```json
70
- {
71
- "code": "string", // Code containing errors
72
- "error": "string" // Error message to fix
73
- }
74
- ```
75
-
76
- **Response:**
77
- ```json
78
- {
79
- "fixed_code": "string" // The fixed code
80
- }
81
- ```
82
-
83
- **Error Responses:**
84
- - `400 Bad Request`: Missing code or error message
85
- - `500 Internal Server Error`: Fixing error
86
-
87
- ### Clean Code
88
- Cleans and formats code by organizing imports and ensuring proper code block formatting.
89
-
90
- **Endpoint:** `POST /code/clean-code`
91
-
92
- **Request Body:**
93
- ```json
94
- {
95
- "code": "string" // Code to clean
96
- }
97
- ```
98
-
99
- **Response:**
100
- ```json
101
- {
102
- "cleaned_code": "string" // The cleaned code
103
- }
104
- ```
105
-
106
- **Error Responses:**
107
- - `400 Bad Request`: No code provided
108
- - `500 Internal Server Error`: Cleaning error
109
-
110
- ### Get Latest Code
111
- Retrieves the latest code from a specific message.
112
-
113
- **Endpoint:** `POST /code/get-latest-code`
114
-
115
- **Request Body:**
116
- ```json
117
- {
118
- "message_id": 123 // Message ID to retrieve code from
119
- }
120
- ```
121
-
122
- **Response:**
123
- ```json
124
- {
125
- "code": "string" // The retrieved code
126
- }
127
- ```
128
-
129
- **Error Responses:**
130
- - `400 Bad Request`: Missing message ID
131
- - `404 Not Found`: Message not found
132
- - `500 Internal Server Error`: Retrieval error
133
-
134
- ## Code Processing Features
135
-
136
- ### Import Organization
137
- The code processing system automatically:
138
- - Moves all import statements to the top of the file
139
- - Deduplicates imports
140
- - Sorts imports alphabetically
141
-
142
- ### Code Block Management
143
- The system supports code blocks marked with special comments:
144
- - Start marker: `# agent_name code start`
145
- - End marker: `# agent_name code end`
146
-
147
- ### Error Handling with DSPy Refinement
148
- When fixing code, the system uses DSPy's refinement mechanism:
149
- - Identifies specific code blocks with errors
150
- - Processes error messages to extract relevant information
151
- - Uses a scoring function to validate fixes
152
- - Employs iterative refinement with up to 3 attempts
153
- - Fixes each block individually while maintaining the overall structure
154
- - Preserves code block markers and relationships
155
-
156
- ### Dataset Context
157
- When editing or fixing code, the system provides context about the current dataset including:
158
- - Number of rows and columns
159
- - Column names and data types
160
- - Null value counts
161
- - Sample values for each column
162
-
163
- ### Code Execution Safety
164
- The execution system includes safety measures:
165
- - Removes blocking calls like `plt.show()`
166
- - Handles `__main__` block extraction
167
- - Cleans up print statements with unwanted newlines
168
- - Executes code in isolated namespaces
169
-
170
- ## Session Management
171
- All endpoints require a valid session ID, which is used to:
172
- - Access the current dataset
173
- - Maintain state between requests
174
- - Track code execution history
175
- - Store execution results for analysis
176
-
177
- ## Error Handling
178
- The system provides detailed error messages while maintaining security by:
179
- - Logging errors for debugging
180
- - Returning user-friendly error messages
181
- - Preserving original code in case of processing failures
182
- - Using code scoring to validate fixes before returning results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/deep_analysis.md DELETED
@@ -1,348 +0,0 @@
1
- # Deep Analysis API Documentation
2
-
3
- ## Overview
4
-
5
- The Deep Analysis system provides advanced multi-agent analytical capabilities that automatically generate comprehensive reports based on user goals. The system uses DSPy (Declarative Self-improving Language Programs) to orchestrate multiple AI agents and create detailed analytical insights.
6
-
7
- ## Key Features
8
-
9
- - **Multi-Agent Analysis**: Orchestrates multiple specialized agents (preprocessing, statistical analysis, machine learning, visualization)
10
- - **Template Integration**: Uses the user's active templates/agents for analysis
11
- - **Streaming Progress**: Real-time progress updates during analysis execution
12
- - **Report Persistence**: Stores complete analysis reports in database with metadata
13
- - **HTML Export**: Generates downloadable HTML reports with visualizations
14
- - **Credit Tracking**: Monitors token usage, costs, and credits consumed
15
-
16
- ## Template Integration
17
-
18
- The deep analysis system integrates with the user's active templates through the agent system:
19
-
20
- 1. **Agent Selection**: Uses agents from the user's active template preferences (configured via `/templates` endpoints)
21
- 2. **Default Agents**: Falls back to system default agents if user hasn't configured preferences:
22
- - `preprocessing` (both individual and planner variants)
23
- - `statistical_analytics` (both individual and planner variants)
24
- - `sk_learn` (both individual and planner variants)
25
- - `data_viz` (both individual and planner variants)
26
- 3. **Template Limits**: Respects the 10-template limit for planner performance optimization
27
- 4. **Dynamic Planning**: The planner automatically selects the most appropriate agents based on the analysis goal and available templates
28
-
29
- ## Analysis Flow
30
-
31
- The deep analysis process follows these steps:
32
-
33
- 1. **Question Generation** (20% progress): Generates 5 targeted analytical questions based on the user's goal
34
- 2. **Planning** (40% progress): Creates an optimized execution plan using available agents
35
- 3. **Agent Execution** (60% progress): Executes analysis using user's active templates
36
- 4. **Code Synthesis** (80% progress): Combines and optimizes code from all agents
37
- 5. **Code Execution** (85% progress): Runs the synthesized analysis code
38
- 6. **Synthesis** (90% progress): Synthesizes results into coherent insights
39
- 7. **Conclusion** (100% progress): Generates final conclusions and recommendations
40
-
41
- ---
42
-
43
- ## API Endpoints
44
-
45
- ### Create Deep Analysis Report
46
-
47
- **POST** `/deep_analysis/reports`
48
-
49
- Creates a new deep analysis report in the database.
50
-
51
- **Request Body:**
52
- ```json
53
- {
54
- "report_uuid": "string",
55
- "user_id": 123,
56
- "goal": "Analyze customer churn patterns",
57
- "status": "completed",
58
- "deep_questions": "1. What factors...\n2. How does...",
59
- "deep_plan": "{\n \"@preprocessing\": {\n \"create\": [...],\n \"use\": [...],\n \"instruction\": \"...\"\n }\n}",
60
- "summaries": ["Agent summary 1", "Agent summary 2"],
61
- "analysis_code": "import pandas as pd\n# Analysis code...",
62
- "plotly_figures": [{"data": [...], "layout": {...}}],
63
- "synthesis": ["Synthesis result 1"],
64
- "final_conclusion": "## Conclusion\nThe analysis reveals...",
65
- "html_report": "<html>...</html>",
66
- "report_summary": "Brief summary of findings",
67
- "progress_percentage": 100,
68
- "duration_seconds": 120,
69
- "credits_consumed": 5,
70
- "error_message": null,
71
- "model_provider": "anthropic",
72
- "model_name": "claude-sonnet-4-20250514",
73
- "total_tokens_used": 15000,
74
- "estimated_cost": 0.25,
75
- "steps_completed": ["questions", "planning", "execution", "synthesis", "conclusion"]
76
- }
77
- ```
78
-
79
- **Response:**
80
- ```json
81
- {
82
- "report_id": 1,
83
- "report_uuid": "uuid-string",
84
- "user_id": 123,
85
- "goal": "Analyze customer churn patterns",
86
- "status": "completed",
87
- "start_time": "2024-01-01T12:00:00Z",
88
- "end_time": "2024-01-01T12:02:00Z",
89
- "duration_seconds": 120,
90
- "report_summary": "Brief summary of findings",
91
- "created_at": "2024-01-01T12:02:00Z",
92
- "updated_at": "2024-01-01T12:02:00Z"
93
- }
94
- ```
95
-
96
- ### Get Deep Analysis Reports
97
-
98
- **GET** `/deep_analysis/reports`
99
-
100
- Retrieves a list of deep analysis reports with optional filtering.
101
-
102
- **Query Parameters:**
103
- - `user_id` (optional): Filter by user ID
104
- - `limit` (optional): Number of reports to return (1-100, default: 10)
105
- - `offset` (optional): Number of reports to skip (default: 0)
106
- - `status` (optional): Filter by status ("pending", "running", "completed", "failed")
107
-
108
- **Response:**
109
- ```json
110
- [
111
- {
112
- "report_id": 1,
113
- "report_uuid": "uuid-string",
114
- "user_id": 123,
115
- "goal": "Analyze customer churn patterns",
116
- "status": "completed",
117
- "start_time": "2024-01-01T12:00:00Z",
118
- "end_time": "2024-01-01T12:02:00Z",
119
- "duration_seconds": 120,
120
- "report_summary": "Brief summary of findings",
121
- "created_at": "2024-01-01T12:02:00Z",
122
- "updated_at": "2024-01-01T12:02:00Z"
123
- }
124
- ]
125
- ```
126
-
127
- ### Get User Historical Reports
128
-
129
- **GET** `/deep_analysis/reports/user_historical`
130
-
131
- Retrieves all historical deep analysis reports for a specific user.
132
-
133
- **Query Parameters:**
134
- - `user_id`: User ID (required)
135
- - `limit` (optional): Number of reports to return (1-100, default: 50)
136
-
137
- ### Get Report by ID
138
-
139
- **GET** `/deep_analysis/reports/{report_id}`
140
-
141
- Retrieves a complete deep analysis report by ID.
142
-
143
- **Query Parameters:**
144
- - `user_id` (optional): Ensures report belongs to specified user
145
-
146
- **Response:**
147
- ```json
148
- {
149
- "report_id": 1,
150
- "report_uuid": "uuid-string",
151
- "user_id": 123,
152
- "goal": "Analyze customer churn patterns",
153
- "status": "completed",
154
- "start_time": "2024-01-01T12:00:00Z",
155
- "end_time": "2024-01-01T12:02:00Z",
156
- "duration_seconds": 120,
157
- "deep_questions": "1. What factors contribute to churn?\n2. How does churn vary by segment?",
158
- "deep_plan": "{\n \"@preprocessing\": {...},\n \"@statistical_analytics\": {...}\n}",
159
- "summaries": ["Agent performed data cleaning...", "Statistical analysis revealed..."],
160
- "analysis_code": "import pandas as pd\n# Complete analysis code",
161
- "plotly_figures": [{"data": [...], "layout": {...}}],
162
- "synthesis": ["The analysis shows clear patterns..."],
163
- "final_conclusion": "## Conclusion\nCustomer churn is primarily driven by...",
164
- "html_report": "<html>...</html>",
165
- "report_summary": "Analysis of customer churn patterns reveals...",
166
- "progress_percentage": 100,
167
- "credits_consumed": 5,
168
- "error_message": null,
169
- "model_provider": "anthropic",
170
- "model_name": "claude-sonnet-4-20250514",
171
- "total_tokens_used": 15000,
172
- "estimated_cost": 0.25,
173
- "steps_completed": ["questions", "planning", "execution", "synthesis", "conclusion"],
174
- "created_at": "2024-01-01T12:02:00Z",
175
- "updated_at": "2024-01-01T12:02:00Z"
176
- }
177
- ```
178
-
179
- ### Get Report by UUID
180
-
181
- **GET** `/deep_analysis/reports/uuid/{report_uuid}`
182
-
183
- Retrieves a complete deep analysis report by UUID. Same response format as get by ID.
184
-
185
- ### Delete Report
186
-
187
- **DELETE** `/deep_analysis/reports/{report_id}`
188
-
189
- Deletes a deep analysis report.
190
-
191
- **Query Parameters:**
192
- - `user_id` (optional): Ensures report belongs to specified user
193
-
194
- **Response:**
195
- ```json
196
- {
197
- "message": "Report 1 deleted successfully"
198
- }
199
- ```
200
-
201
- ### Update Report Status
202
-
203
- **PUT** `/deep_analysis/reports/{report_id}/status`
204
-
205
- Updates the status of a deep analysis report.
206
-
207
- **Request Body:**
208
- ```json
209
- {
210
- "status": "completed"
211
- }
212
- ```
213
-
214
- **Valid Status Values:**
215
- - `pending`: Analysis queued but not started
216
- - `running`: Analysis in progress
217
- - `completed`: Analysis finished successfully
218
- - `failed`: Analysis encountered errors
219
-
220
- ### Get HTML Report
221
-
222
- **GET** `/deep_analysis/reports/uuid/{report_uuid}/html`
223
-
224
- Retrieves only the HTML report content for a specific analysis.
225
-
226
- **Query Parameters:**
227
- - `user_id` (optional): Ensures report belongs to specified user
228
-
229
- **Response:**
230
- ```json
231
- {
232
- "html_report": "<html>...</html>",
233
- "filename": "deep_analysis_report_20240101_120200.html"
234
- }
235
- ```
236
-
237
- ### Download HTML Report
238
-
239
- **POST** `/deep_analysis/download_from_db/{report_uuid}`
240
-
241
- Downloads the HTML report as a file attachment.
242
-
243
- **Query Parameters:**
244
- - `user_id` (optional): Ensures report belongs to specified user
245
-
246
- **Response:**
247
- - Content-Type: `text/html; charset=utf-8`
248
- - Content-Disposition: `attachment; filename="deep_analysis_report_TIMESTAMP.html"`
249
-
250
- ---
251
-
252
- ## Deep Analysis Module Architecture
253
-
254
- ### DSPy Signatures
255
-
256
- The system uses several DSPy signatures for different analysis phases:
257
-
258
- #### 1. `deep_questions`
259
- Generates 5 targeted analytical questions based on the user's goal and dataset structure.
260
-
261
- #### 2. `deep_planner`
262
- Creates an optimized execution plan using the user's active templates/agents. The planner:
263
- - Verifies feasibility using available datasets and agent descriptions
264
- - Batches similar questions per agent call for efficiency
265
- - Reuses outputs across questions to minimize agent calls
266
- - Defines clear variable flow and dependencies between agents
267
-
268
- #### 3. `deep_code_synthesizer`
269
- Combines and optimizes code from multiple agents:
270
- - Fixes errors and inconsistencies between agent outputs
271
- - Ensures proper data flow and type handling
272
- - Converts all visualizations to Plotly format
273
- - Adds comprehensive error handling and validation
274
-
275
- #### 4. `deep_synthesizer`
276
- Synthesizes analysis results into coherent insights and findings.
277
-
278
- #### 5. `final_conclusion`
279
- Generates final conclusions and strategic recommendations based on all analysis results.
280
-
281
- ### Streaming Analysis
282
-
283
- The `execute_deep_analysis_streaming` method provides real-time progress updates:
284
-
285
- ```python
286
- async for update in deep_analysis.execute_deep_analysis_streaming(goal, dataset_info, session_df):
287
- if update["step"] == "questions":
288
- # Handle questions generation progress
289
- elif update["step"] == "planning":
290
- # Handle planning progress
291
- elif update["step"] == "agent_execution":
292
- # Handle agent execution progress
293
- # ... handle other steps
294
- ```
295
-
296
- ### Integration with User Templates
297
-
298
- The deep analysis system integrates with user templates in several ways:
299
-
300
- 1. **Agent Discovery**: Retrieves user's active template preferences from the database
301
- 2. **Dynamic Planning**: The planner uses available agents to create optimal execution plans
302
- 3. **Template Validation**: Ensures all referenced agents exist in the user's active templates
303
- 4. **Fallback Handling**: Uses default agents if user preferences are incomplete
304
- 5. **Performance Optimization**: Respects template limits for efficient execution
305
-
306
- ### Error Handling
307
-
308
- The system includes comprehensive error handling:
309
-
310
- - **Code Execution Errors**: Automatically attempts to fix and retry failed code
311
- - **Template Missing**: Falls back to default agents if user templates are unavailable
312
- - **Timeout Protection**: Includes timeouts for long-running operations
313
- - **Memory Management**: Handles large datasets and visualization efficiently
314
- - **Unicode Handling**: Cleans problematic characters that might cause encoding issues
315
-
316
- ### Visualization Integration
317
-
318
- All visualizations are standardized to Plotly format:
319
- - Consistent styling and color schemes
320
- - Interactive features (zoom, pan, hover)
321
- - Accessibility compliance (colorblind-friendly palettes)
322
- - Export capabilities for reports
323
- - Responsive design for different screen sizes
324
-
325
- ---
326
-
327
- ## Frontend Integration
328
-
329
- The deep analysis system includes React components for:
330
-
331
- - **DeepAnalysisSidebar**: Main interface for starting and managing analyses
332
- - **NewAnalysisForm**: Form for initiating new deep analyses
333
- - **CurrentAnalysisView**: Real-time progress tracking during analysis
334
- - **HistoryView**: Browse and access historical analysis reports
335
- - **AnalysisStep**: Individual step progress visualization
336
-
337
- The frontend integrates with the streaming API to provide real-time feedback and uses the user's active template configuration for personalized analysis capabilities.
338
-
339
- ## Credit and Cost Tracking
340
-
341
- The system tracks detailed usage metrics:
342
- - **Credits Consumed**: Number of credits deducted from user account
343
- - **Token Usage**: Total tokens used across all model calls
344
- - **Estimated Cost**: Dollar cost estimate based on model pricing
345
- - **Model Information**: Provider and model name used for analysis
346
- - **Execution Time**: Duration of analysis for performance monitoring
347
-
348
- This information helps users understand resource consumption and optimize their analysis strategies.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/feedback.md DELETED
@@ -1,153 +0,0 @@
1
- # Feedback Routes Documentation
2
-
3
- This document describes the API endpoints available for managing user feedback on AI-generated messages in the Auto-Analyst backend.
4
-
5
- ## Base URL
6
-
7
- All feedback-related endpoints are prefixed with `/feedback`.
8
-
9
- ## Endpoints
10
-
11
- ### Create or Update Message Feedback
12
- Creates new feedback or updates existing feedback for a specific message.
13
-
14
- **Endpoint:** `POST /feedback/message/{message_id}`
15
-
16
- **Path Parameters:**
17
- - `message_id`: ID of the message to provide feedback for
18
-
19
- **Request Body:**
20
- ```json
21
- {
22
- "rating": 5, // Required: Star rating (1-5)
23
- "model_name": "gpt-4o-mini", // Optional: Model used for the message
24
- "model_provider": "openai", // Optional: Provider of the model
25
- "temperature": 0.7, // Optional: Temperature setting
26
- "max_tokens": 6000 // Optional: Max tokens setting
27
- }
28
- ```
29
-
30
- **Response:**
31
- ```json
32
- {
33
- "feedback_id": 123,
34
- "message_id": 456,
35
- "rating": 5,
36
- "feedback_comment": null,
37
- "model_name": "gpt-4o-mini",
38
- "model_provider": "openai",
39
- "temperature": 0.7,
40
- "max_tokens": 6000,
41
- "created_at": "2023-05-01T12:00:00Z",
42
- "updated_at": "2023-05-01T12:00:00Z"
43
- }
44
- ```
45
-
46
- **Error Responses:**
47
- - `404 Not Found`: Message with specified ID not found
48
- - `500 Internal Server Error`: Failed to create/update feedback
49
-
50
- ### Get Message Feedback
51
- Retrieves feedback for a specific message.
52
-
53
- **Endpoint:** `GET /feedback/message/{message_id}`
54
-
55
- **Path Parameters:**
56
- - `message_id`: ID of the message to get feedback for
57
-
58
- **Response:**
59
- ```json
60
- {
61
- "feedback_id": 123,
62
- "message_id": 456,
63
- "rating": 5,
64
- "feedback_comment": null,
65
- "model_name": "gpt-4o-mini",
66
- "model_provider": "openai",
67
- "temperature": 0.7,
68
- "max_tokens": 6000,
69
- "created_at": "2023-05-01T12:00:00Z",
70
- "updated_at": "2023-05-01T12:00:00Z"
71
- }
72
- ```
73
-
74
- **Error Responses:**
75
- - `404 Not Found`: No feedback found for the specified message
76
- - `500 Internal Server Error`: Failed to retrieve feedback
77
-
78
- ### Get Chat Feedback
79
- Retrieves all feedback for messages in a specific chat.
80
-
81
- **Endpoint:** `GET /feedback/chat/{chat_id}`
82
-
83
- **Path Parameters:**
84
- - `chat_id`: ID of the chat to get feedback for
85
-
86
- **Response:**
87
- ```json
88
- [
89
- {
90
- "feedback_id": 123,
91
- "message_id": 456,
92
- "rating": 5,
93
- "feedback_comment": null,
94
- "model_name": "gpt-4o-mini",
95
- "model_provider": "openai",
96
- "temperature": 0.7,
97
- "max_tokens": 6000,
98
- "created_at": "2023-05-01T12:00:00Z",
99
- "updated_at": "2023-05-01T12:00:00Z"
100
- }
101
- ]
102
- ```
103
-
104
- **Note:** Returns an empty array if no feedback exists for the chat.
105
-
106
- **Error Responses:**
107
- - `500 Internal Server Error`: Failed to retrieve chat feedback
108
-
109
- ## Feedback Features
110
-
111
- ### Rating System
112
- - **Scale:** 1-5 star rating system
113
- - **Required:** Rating is the only required field for feedback
114
- - **Purpose:** Allows users to rate the quality of AI responses
115
-
116
- ### Model Context Tracking
117
- The system optionally tracks:
118
- - **Model Name:** The specific AI model used (e.g., "gpt-4o-mini")
119
- - **Model Provider:** The provider of the model (e.g., "openai", "anthropic")
120
- - **Temperature:** The creativity/randomness setting used
121
- - **Max Tokens:** The maximum response length setting
122
-
123
- ### Update Behavior
124
- - **Upsert Operation:** The POST endpoint either creates new feedback or updates existing feedback
125
- - **Partial Updates:** When updating, only provided fields are modified
126
- - **Timestamp Tracking:** Both creation and update timestamps are maintained
127
-
128
- ## Data Management
129
-
130
- ### Database Operations
131
- - **Atomic Operations:** Feedback creation/updates are handled in database transactions
132
- - **Referential Integrity:** Feedback is linked to specific messages via foreign keys
133
- - **Soft Handling:** Missing optional fields are handled gracefully
134
-
135
- ### Error Handling
136
- - **Comprehensive Logging:** All operations are logged for debugging
137
- - **User-Friendly Messages:** Error responses provide clear information
138
- - **Transaction Safety:** Failed operations are rolled back to maintain data consistency
139
-
140
- ## Usage Patterns
141
-
142
- ### Typical Workflow
143
- 1. User receives an AI-generated message
144
- 2. User provides rating (1-5 stars) via the frontend
145
- 3. Frontend calls `POST /feedback/message/{message_id}` with rating and model context
146
- 4. System stores or updates the feedback
147
- 5. Feedback can be retrieved later for analytics or user review
148
-
149
- ### Analytics Integration
150
- Feedback data is used by the analytics system to:
151
- - Track model performance across different configurations
152
- - Identify patterns in user satisfaction
153
- - Generate insights for model optimization
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/session.md DELETED
@@ -1,273 +0,0 @@
1
- # **Auto-Analyst API Documentation**
2
-
3
- The core application routes are designed to manage the data and AI analysis capabilities of the Auto-Analyst application.
4
-
5
- ## **1. Core Application Routes**
6
- ### **Data Management**
7
-
8
- #### **POST /upload_dataframe**
9
- Uploads a CSV dataset for analysis.
10
- **Request:**
11
- - `file`: CSV file
12
- - `name`: Dataset name
13
- - `description`: Dataset description
14
- **Headers:**
15
- - `X-Force-Refresh`: "true" (optional) - Forces session reset before upload
16
- **Response:**
17
- ```json
18
- { "message": "Dataframe uploaded successfully", "session_id": "abc123" }
19
- ```
20
-
21
- #### **POST /upload_excel**
22
- Uploads an Excel file with a specific sheet for analysis.
23
- **Request:**
24
- - `file`: Excel file
25
- - `name`: Dataset name
26
- - `description`: Dataset description
27
- - `sheet_name`: Name of the Excel sheet to use
28
- **Headers:**
29
- - `X-Force-Refresh`: "true" (optional) - Forces session reset before upload
30
- **Response:**
31
- ```json
32
- { "message": "Excel file processed successfully", "session_id": "abc123", "sheet": "Sheet1" }
33
- ```
34
-
35
- #### **POST /api/excel-sheets**
36
- Gets the list of sheet names from an Excel file.
37
- **Request:**
38
- - `file`: Excel file
39
- **Response:**
40
- ```json
41
- { "sheets": ["Sheet1", "Sheet2", "Data"] }
42
- ```
43
-
44
- #### **GET /api/default-dataset**
45
- Gets the default dataset.
46
- **Response:**
47
- ```json
48
- {
49
- "headers": ["column1", "column2", ...],
50
- "rows": [[val1, val2, ...], ...],
51
- "name": "Housing Dataset",
52
- "description": "A comprehensive dataset containing housing information..."
53
- }
54
- ```
55
-
56
- #### **POST /reset-session**
57
- Resets session to default dataset.
58
- **Request Body:**
59
- ```json
60
- {
61
- "name": "optional name",
62
- "description": "optional description",
63
- "preserveModelSettings": false
64
- }
65
- ```
66
- **Response:**
67
- ```json
68
- {
69
- "message": "Session reset to default dataset",
70
- "session_id": "abc123",
71
- "dataset": "Housing.csv"
72
- }
73
- ```
74
-
75
- #### **GET /api/preview-csv** / **POST /api/preview-csv**
76
- Preview the current dataset in the session.
77
- **Response:**
78
- ```json
79
- {
80
- "headers": ["column1", "column2", ...],
81
- "rows": [[val1, val2, ...], ...],
82
- "name": "Dataset Name",
83
- "description": "Dataset description..."
84
- }
85
- ```
86
-
87
- ---
88
-
89
- ### **2. AI Analysis**
90
-
91
- #### **POST /chat/{agent_name}**
92
- Processes a query using a specific AI agent.
93
- **Path Parameters:** `agent_name`
94
- **Request Body:**
95
- ```json
96
- { "query": "Analyze the relationship between price and size" }
97
- ```
98
- **Query Parameters:** `user_id` (optional), `chat_id` (optional)
99
- **Response:**
100
- ```json
101
- {
102
- "agent_name": "data_viz_agent",
103
- "query": "Analyze the relationship between price and size",
104
- "response": "# Analysis\n\nThere appears to be a strong positive correlation...",
105
- "session_id": "abc123"
106
- }
107
- ```
108
-
109
- #### **POST /chat**
110
- Processes a query using multiple AI agents with streaming responses.
111
- **Request Body:**
112
- ```json
113
- { "query": "Analyze the housing data" }
114
- ```
115
- **Query Parameters:** `user_id` (optional), `chat_id` (optional)
116
- **Response:** *Streaming JSON objects:*
117
- ```json
118
- {"agent": "data_viz_agent", "content": "# Visualization\n\n...", "status": "success"}
119
- {"agent": "statistical_analytics_agent", "content": "# Statistical Analysis\n\n...", "status": "success"}
120
- ```
121
-
122
- #### **POST /chat_history_name**
123
- Generates a name for a chat based on the query.
124
- **Request Body:**
125
- ```json
126
- { "query": "Analyze sales data for Q4" }
127
- ```
128
- **Response:**
129
- ```json
130
- { "name": "Chat about sales data analysis" }
131
- ```
132
-
133
- #### **GET /agents**
134
- Lists available AI agents.
135
- **Response:**
136
- ```json
137
- {
138
- "available_agents": ["data_viz_agent", "sk_learn_agent", "statistical_analytics_agent", "preprocessing_agent"],
139
- "standard_agents": ["preprocessing_agent", "statistical_analytics_agent", "sk_learn_agent", "data_viz_agent"],
140
- "template_agents": ["custom_template_1", "custom_template_2"],
141
- "custom_agents": []
142
- }
143
- ```
144
-
145
- ---
146
-
147
- ### **3. Deep Analysis**
148
-
149
- #### **POST /deep_analysis_streaming**
150
- Performs comprehensive deep analysis with real-time streaming updates.
151
- **Request Body:**
152
- ```json
153
- { "goal": "Perform comprehensive analysis of the sales data" }
154
- ```
155
- **Query Parameters:** `user_id` (optional), `chat_id` (optional)
156
- **Response:** *Streaming JSON objects with progress updates*
157
-
158
- #### **POST /deep_analysis/download_report**
159
- Downloads an HTML report from deep analysis results.
160
- **Request Body:**
161
- ```json
162
- {
163
- "analysis_data": { ... },
164
- "report_uuid": "optional-uuid"
165
- }
166
- ```
167
- **Response:** HTML file download
168
-
169
- ---
170
-
171
- ### **4. Model Settings**
172
-
173
- #### **GET /api/model-settings**
174
- Fetches current model settings.
175
- **Response:**
176
- ```json
177
- {
178
- "provider": "openai",
179
- "model": "gpt-4o-mini",
180
- "hasCustomKey": true,
181
- "temperature": 1.0,
182
- "maxTokens": 6000
183
- }
184
- ```
185
-
186
- #### **POST /settings/model**
187
- Updates model settings.
188
- **Request Body:**
189
- ```json
190
- {
191
- "provider": "openai",
192
- "model": "gpt-4",
193
- "api_key": "sk-...",
194
- "temperature": 0.7,
195
- "max_tokens": 8000
196
- }
197
- ```
198
- **Response:**
199
- ```json
200
- { "message": "Model settings updated successfully" }
201
- ```
202
-
203
- ---
204
-
205
- ### **5. Session Management**
206
-
207
- #### **GET /api/session-info**
208
- Gets information about the current session.
209
- **Response:**
210
- ```json
211
- {
212
- "session_id": "abc123",
213
- "dataset_name": "Housing Dataset",
214
- "dataset_description": "...",
215
- "model_config": { ... }
216
- }
217
- ```
218
-
219
- #### **POST /set-message-info**
220
- Associates message tracking information with the session.
221
- **Request Body:**
222
- ```json
223
- {
224
- "chat_id": 123,
225
- "message_id": 456,
226
- "user_id": 789
227
- }
228
- ```
229
-
230
- #### **POST /create-dataset-description**
231
- Creates an AI-generated description for a dataset.
232
- **Request Body:**
233
- ```json
234
- {
235
- "df_preview": "column1,column2\nvalue1,value2\n...",
236
- "name": "Dataset Name"
237
- }
238
- ```
239
-
240
- ---
241
-
242
- ### **6. System Endpoints**
243
-
244
- #### **GET /**
245
- Returns API welcome information and feature list.
246
-
247
- #### **GET /health**
248
- Health check endpoint.
249
- **Response:**
250
- ```json
251
- { "message": "API is healthy and running" }
252
- ```
253
-
254
- ---
255
-
256
- ---
257
-
258
- ### **7. Authentication & Session Management**
259
- - **Session ID Sources:**
260
- - Query parameter: `session_id`
261
- - Header: `X-Session-ID`
262
- - Auto-generated if not provided
263
- - **Session State Includes:**
264
- - Current dataset
265
- - AI system instance
266
- - Model configuration
267
- - User and chat associations
268
-
269
- ### **9. Error Handling**
270
- - Comprehensive error handling with appropriate HTTP status codes
271
- - Detailed error messages for debugging
272
- - Fallback encoding support for CSV files (UTF-8, unicode_escape, ISO-8859-1)
273
- - Session state preservation during errors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api/routes/templates.md DELETED
@@ -1,363 +0,0 @@
1
- # Templates and Agent Loading Documentation
2
-
3
- This document describes how the Auto-Analyst template system works, including agent loading, user preferences, and template management.
4
-
5
- ## Overview
6
-
7
- The Auto-Analyst system uses a flexible template-based approach for managing AI agents. Templates define specialized agents with specific capabilities, and users can customize which agents are available for their analysis workflows.
8
-
9
- ## Template System Architecture
10
-
11
- ### Template Types
12
-
13
- Templates come in different **variant types** that determine how they can be used:
14
-
15
- - **`individual`**: Templates available for single-agent queries (e.g., `@preprocessing_agent`)
16
- - **`planner`**: Templates available for multi-agent planning workflows
17
- - **`both`**: Templates available in both individual and planner contexts
18
-
19
- ### Default Agents
20
-
21
- The system includes four core default agents that are **enabled by default** for all users:
22
-
23
- **For Individual Use:**
24
- - `preprocessing_agent`: Data cleaning and preprocessing
25
- - `statistical_analytics_agent`: Statistical analysis and insights
26
- - `sk_learn_agent`: Machine learning with scikit-learn
27
- - `data_viz_agent`: Data visualization with Plotly
28
-
29
- **For Planner Use:**
30
- - `planner_preprocessing_agent`: Planning version of preprocessing agent
31
- - `planner_statistical_analytics_agent`: Planning version of statistical agent
32
- - `planner_sk_learn_agent`: Planning version of ML agent
33
- - `planner_data_viz_agent`: Planning version of visualization agent
34
-
35
- ## Template Management Endpoints
36
-
37
- ### Get All Templates
38
-
39
- **Endpoint:** `GET /templates/`
40
-
41
- **Query Parameters:**
42
- - `variant_type`: Filter by `"individual"`, `"planner"`, or `"all"` (default: `"all"`)
43
-
44
- **Response:**
45
- ```json
46
- [
47
- {
48
- "template_id": 1,
49
- "template_name": "preprocessing_agent",
50
- "display_name": "Data Preprocessing Agent",
51
- "description": "Handles data cleaning, missing values, and preprocessing tasks",
52
- "prompt_template": "You are a data preprocessing specialist...",
53
- "template_category": "Data Processing",
54
- "icon_url": "/icons/templates/preprocessing_agent.svg",
55
- "is_premium_only": false,
56
- "is_active": true,
57
- "usage_count": 12,
58
- "created_at": "2023-05-01T12:00:00Z",
59
- "updated_at": "2023-05-01T12:00:00Z"
60
- }
61
- ]
62
- ```
63
-
64
- ### Get Templates by Category
65
-
66
- **Endpoint:** `GET /templates/categories`
67
-
68
- **Query Parameters:**
69
- - `variant_type`: Filter by `"individual"`, `"planner"`, or `"all"` (default: `"individual"`)
70
-
71
- **Response:**
72
- ```json
73
- [
74
- {
75
- "category": "Data Processing",
76
- "templates": [
77
- {
78
- "agent_id": 1,
79
- "agent_name": "preprocessing_agent",
80
- "display_name": "Data Preprocessing Agent",
81
- "description": "Handles data cleaning and preprocessing",
82
- "icon_url": "/icons/templates/preprocessing_agent.svg",
83
- "usage_count": 1234
84
- }
85
- ]
86
- }
87
- ]
88
- ```
89
-
90
- ### Get Template by ID
91
-
92
- **Endpoint:** `GET /templates/template/{template_id}`
93
-
94
- **Response:**
95
- ```json
96
- {
97
- "template_id": 1,
98
- "template_name": "preprocessing_agent",
99
- "display_name": "Data Preprocessing Agent",
100
- "description": "Handles data cleaning, missing values, and preprocessing tasks",
101
- "prompt_template": "You are a data preprocessing specialist...",
102
- "template_category": "Data Processing",
103
- "icon_url": "/icons/templates/preprocessing_agent.svg",
104
- "is_premium_only": false,
105
- "is_active": true,
106
- "usage_count": 1234,
107
- "created_at": "2023-05-01T12:00:00Z",
108
- "updated_at": "2023-05-01T12:00:00Z"
109
- }
110
- ```
111
-
112
- ### Get Template Categories List
113
-
114
- **Endpoint:** `GET /templates/categories/list`
115
-
116
- **Response:**
117
- ```json
118
- {
119
- "categories": [
120
- "Data Processing",
121
- "Machine Learning",
122
- "Visualization",
123
- "Statistics"
124
- ]
125
- }
126
- ```
127
-
128
- ### Get Templates by Specific Category
129
-
130
- **Endpoint:** `GET /templates/category/{category}`
131
-
132
- **Path Parameters:**
133
- - `category`: Name of the category to filter by
134
-
135
- **Response:**
136
- ```json
137
- [
138
- {
139
- "template_id": 1,
140
- "template_name": "preprocessing_agent",
141
- "display_name": "Data Preprocessing Agent",
142
- "description": "Handles data cleaning and preprocessing",
143
- "template_category": "Data Processing",
144
- "icon_url": "/icons/templates/preprocessing_agent.svg",
145
- "usage_count": 1234
146
- }
147
- ]
148
- ```
149
-
150
- ## User Template Preferences
151
-
152
- ### How Agent Loading Works for Users
153
-
154
- 1. **Default Behavior**: New users automatically have the 4 core default agents enabled
155
- 2. **Custom Preferences**: Users can enable/disable additional templates through preferences
156
- 3. **Variant-Specific**: Individual and planner variants are managed separately
157
- 4. **Usage Tracking**: System tracks which templates users actually use
158
-
159
- ### Get User Template Preferences
160
-
161
- **Endpoint:** `GET /templates/user/{user_id}`
162
-
163
- **Query Parameters:**
164
- - `variant_type`: Filter by `"individual"`, `"planner"`, or `"all"` (default: `"planner"`)
165
-
166
- **Response:**
167
- ```json
168
- [
169
- {
170
- "template_id": 1,
171
- "template_name": "preprocessing_agent",
172
- "display_name": "Data Preprocessing Agent",
173
- "description": "Handles data cleaning and preprocessing",
174
- "template_category": "Data Processing",
175
- "icon_url": "/icons/templates/preprocessing_agent.svg",
176
- "is_premium_only": false,
177
- "is_active": true,
178
- "is_enabled": true,
179
- "usage_count": 15,
180
- "last_used_at": "2023-05-01T12:00:00Z",
181
- "created_at": "2023-04-01T12:00:00Z",
182
- "updated_at": "2023-05-01T12:00:00Z"
183
- }
184
- ]
185
- ```
186
-
187
- ### Get Only Enabled Templates
188
-
189
- **Endpoint:** `GET /templates/user/{user_id}/enabled`
190
-
191
- Returns only templates that are currently enabled for the user.
192
-
193
- ### Get Enabled Templates for Planner
194
-
195
- **Endpoint:** `GET /templates/user/{user_id}/enabled/planner`
196
-
197
- Returns enabled planner templates with the following restrictions:
198
- - **Maximum 10 templates** for planner use
199
- - **Sorted by usage** (most used first)
200
- - **Only planner variants** (`planner` or `both` types)
201
-
202
- ## Template Preference Management
203
-
204
- ### Toggle Single Template
205
-
206
- **Endpoint:** `POST /templates/user/{user_id}/template/{template_id}/toggle`
207
-
208
- **Request Body:**
209
- ```json
210
- {
211
- "is_enabled": true
212
- }
213
- ```
214
-
215
- **Restrictions:**
216
- - Cannot disable all templates (at least 1 must remain enabled)
217
- - Cannot enable more than 10 templates for planner use
218
-
219
- ### Bulk Toggle Templates
220
-
221
- **Endpoint:** `POST /templates/user/{user_id}/bulk-toggle`
222
-
223
- **Request Body:**
224
- ```json
225
- {
226
- "preferences": [
227
- {
228
- "template_id": 1,
229
- "is_enabled": true
230
- },
231
- {
232
- "template_id": 2,
233
- "is_enabled": false
234
- }
235
- ]
236
- }
237
- ```
238
-
239
- **Response:**
240
- ```json
241
- {
242
- "results": [
243
- {
244
- "template_id": 1,
245
- "success": true,
246
- "message": "Template enabled successfully",
247
- "is_enabled": true
248
- }
249
- ]
250
- }
251
- ```
252
-
253
- ## Template Categories and Icons
254
-
255
- ### Available Categories
256
-
257
- Templates are organized into categories such as:
258
- - **Data Processing**: Preprocessing, cleaning, feature engineering
259
- - **Machine Learning**: Various ML frameworks and algorithms
260
- - **Visualization**: Plotting and chart generation
261
- - **Statistics**: Statistical analysis and modeling
262
- - **Custom**: User or organization-specific templates
263
-
264
- ### Icon System
265
-
266
- Templates include visual icons stored in `/public/icons/templates/`:
267
-
268
- **Core Agent Icons:**
269
- - `preprocessing_agent.svg`: Data preprocessing
270
- - `sk_learn_agent.svg`: Machine learning
271
- - `matplotlib_agent.png`: Plotting with matplotlib
272
- - `polars_agent.svg`: Data manipulation with Polars
273
-
274
- **Library-Specific Icons:**
275
- - `numpy.svg`, `scipy.png`: Scientific computing
276
- - `plotly.svg`, `seaborn.svg`: Advanced visualization
277
- - `lightgbm.png`, `xgboost.png`: Gradient boosting
278
- - `pymc.png`, `statsmodel.svg`: Statistical modeling
279
-
280
- **Special Purpose Icons:**
281
- - `data-cleaning.png`: Data cleaning workflows
282
- - `feature-engineering.png`: Feature engineering tasks
283
-
284
- ## Agent Loading Process
285
-
286
- ### For Individual Queries
287
-
288
- When a user makes a query like `@preprocessing_agent analyze my data`:
289
-
290
- 1. **Check User Preferences**: System looks up user's enabled individual templates
291
- 2. **Apply Defaults**: If no preference exists, default agents are enabled
292
- 3. **Load Agent**: System loads the specific agent template and executes the query
293
- 4. **Track Usage**: Usage count is incremented for analytics
294
-
295
- ### For Planner Workflows
296
-
297
- When a user makes a general query that triggers the planner:
298
-
299
- 1. **Get Enabled Planner Templates**: System queries user's enabled planner variants
300
- 2. **Apply 10-Template Limit**: Maximum 10 templates for performance
301
- 3. **Sort by Usage**: Most-used templates get priority
302
- 4. **Create Plan**: Planner selects appropriate agents for the analysis
303
- 5. **Execute Workflow**: Selected agents execute in sequence
304
- 6. **Update Usage**: Usage statistics updated for selected agents
305
-
306
- ### Default Agent Behavior
307
-
308
- ```python
309
- # Default agents enabled for new users
310
- individual_defaults = [
311
- "preprocessing_agent",
312
- "statistical_analytics_agent",
313
- "sk_learn_agent",
314
- "data_viz_agent"
315
- ]
316
-
317
- planner_defaults = [
318
- "planner_preprocessing_agent",
319
- "planner_statistical_analytics_agent",
320
- "planner_sk_learn_agent",
321
- "planner_data_viz_agent"
322
- ]
323
- ```
324
-
325
- ## Usage Analytics
326
-
327
- ### Global Usage Tracking
328
-
329
- The system tracks global usage statistics across all users:
330
- - **Total usage count** per template
331
- - **User-specific usage** for personalization
332
- - **Last used timestamps** for sorting
333
-
334
- ### Usage-Based Features
335
-
336
- - **Template Recommendations**: Popular templates shown first
337
- - **Personalized Ordering**: User's most-used templates prioritized
338
- - **Analytics Dashboard**: Usage patterns for administrators
339
-
340
- ## Template Restrictions
341
-
342
- ### User Limitations
343
-
344
- - **Minimum 1 Agent**: Cannot disable all templates
345
- - **Maximum 10 for Planner**: Performance optimization
346
- - **Premium Templates**: Some templates require premium access
347
-
348
- ### System Limitations
349
-
350
- - **Active Templates Only**: Inactive templates not available
351
- - **Variant Compatibility**: Individual/planner variants managed separately
352
- - **Category Organization**: Templates must belong to valid categories
353
-
354
- ## Integration with Deep Analysis
355
-
356
- The deep analysis system uses the template preference system:
357
-
358
- 1. **Load User Preferences**: Gets enabled planner templates for user
359
- 2. **Create Agent Pool**: Instantiates agents from enabled templates
360
- 3. **Execute Analysis**: Uses available agents for comprehensive analysis
361
- 4. **Fallback Behavior**: Uses default agents if no preferences found
362
-
363
- This ensures users get personalized deep analysis based on their template preferences while maintaining system performance through the 10-template limit.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/architecture/architecture.md DELETED
@@ -1,427 +0,0 @@
1
- # Auto-Analyst Backend System Architecture
2
-
3
- ## Overview
4
-
5
- Auto-Analyst is a sophisticated multi-agent AI platform designed for comprehensive data analysis. The backend system orchestrates specialized AI agents, manages user sessions, and provides a robust API for data processing and analysis workflows.
6
-
7
- ## 🏗️ High-Level Architecture
8
-
9
- ```
10
- ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
11
- │ Frontend │ │ Backend │ │ Database │
12
- │ (Next.js) │◄──►│ (FastAPI) │◄──►│ (PostgreSQL/ │
13
- │ │ │ │ │ SQLite) │
14
- └─────────────────┘ └──────────────────┘ └─────────────────┘
15
-
16
-
17
- ┌──────────────────┐
18
- │ AI Models │
19
- │ (DSPy/LLMs) │
20
- └──────────────────┘
21
-
22
-
23
- ┌──────────────────┐
24
- │ Agent System │
25
- │ [Processing] │
26
- │ [Analytics] │
27
- │ [Visualization] │
28
- └──────────────────┘
29
- ```
30
-
31
- ## 🎯 Core Components
32
-
33
- ### 1. Application Layer (`app.py`)
34
-
35
- **FastAPI Application Server**
36
- - **Role**: Main HTTP server and request router
37
- - **Responsibilities**:
38
- - Request/response handling
39
- - Session-based authentication
40
- - Route registration and middleware
41
- - Error handling and logging
42
- - Static file serving
43
- - CORS configuration
44
-
45
- **Key Features**:
46
- - Async/await support for high concurrency
47
- - Automatic API documentation generation
48
- - Request validation with Pydantic
49
- - Session management for user tracking
50
-
51
- ### 2. Agent System (`src/agents/`)
52
-
53
- **Multi-Agent Orchestra**
54
- - **Core Agents**: Specialized AI agents for different analysis tasks
55
- - **Deep Analysis**: Advanced multi-agent coordination system
56
- - **Template System**: User-customizable agent configurations
57
-
58
- #### Agent Types
59
-
60
- 1. **Individual Agents** (`agents.py`):
61
- ```python
62
- - preprocessing_agent # Data cleaning and preparation
63
- - statistical_analytics_agent # Statistical analysis
64
- - sk_learn_agent # Machine learning with scikit-learn
65
- - data_viz_agent # Data visualization
66
- - basic_qa_agent # General Q&A
67
- ```
68
-
69
- 2. **Planner Agents** (Multi-agent coordination):
70
- ```python
71
- - planner_preprocessing_agent
72
- - planner_statistical_analytics_agent
73
- - planner_sk_learn_agent
74
- - planner_data_viz_agent
75
- ```
76
-
77
- 3. **Deep Analysis System** (`deep_agents.py`):
78
- ```python
79
- - deep_questions # Question generation
80
- - deep_planner # Execution planning
81
- - deep_code_synthesizer # Code combination
82
- - deep_synthesizer # Result synthesis
83
- - final_conclusion # Report generation
84
- ```
85
-
86
- #### Agent Architecture Pattern
87
-
88
- ```python
89
- class AgentSignature(dspy.Signature):
90
- """Agent description and purpose"""
91
- goal = dspy.InputField(desc="Analysis objective")
92
- dataset = dspy.InputField(desc="Dataset information")
93
- plan_instructions = dspy.InputField(desc="Execution plan")
94
-
95
- summary = dspy.OutputField(desc="Analysis summary")
96
- code = dspy.OutputField(desc="Generated code")
97
- ```
98
-
99
- ### 3. Database Layer (`src/db/`)
100
-
101
- **Data Persistence and Management**
102
-
103
- #### Database Models (`schemas/models.py`):
104
-
105
- ```python
106
- # Core Models
107
- User # User accounts and authentication
108
- Chat # Conversation sessions
109
- Message # Individual messages in chats
110
- ModelUsage # AI model usage tracking
111
-
112
- # Template System
113
- AgentTemplate # Agent definitions and configurations
114
- UserTemplatePreference # User's enabled/disabled agents
115
-
116
- # Deep Analysis
117
- DeepAnalysisReport # Analysis reports and results
118
-
119
- # Analytics
120
- CodeExecution # Code execution tracking
121
- UserAnalytics # User behavior analytics
122
- ```
123
-
124
- #### Database Architecture:
125
-
126
- ```
127
- Users (1) ──────── (Many) Chats
128
- │ │
129
- │ ▼
130
- └─── (Many) ModelUsage ──┘
131
-
132
- └─── (Many) UserTemplatePreference
133
-
134
-
135
- AgentTemplate
136
- ```
137
-
138
- ### 4. Route Handlers (`src/routes/`)
139
-
140
- **RESTful API Endpoints**
141
-
142
- | Module | Purpose | Key Endpoints |
143
- |--------|---------|---------------|
144
- | `session_routes.py` | Core functionality | `/upload_excel`, `/session_info` |
145
- | `chat_routes.py` | Chat management | `/chats`, `/messages`, `/delete_chat` |
146
- | `code_routes.py` | Code operations | `/execute_code`, `/get_latest_code` |
147
- | `templates_routes.py` | Agent templates | `/templates`, `/user/{id}/enabled` |
148
- | `deep_analysis_routes.py` | Deep analysis | `/reports`, `/download_from_db` |
149
- | `analytics_routes.py` | System analytics | `/usage`, `/feedback`, `/costs` |
150
- | `feedback_routes.py` | User feedback | `/feedback`, `/message/{id}/feedback` |
151
-
152
- NOTE: Make sure to add a router prefix when calling these endpoints, such as to get dashboard, you'll use `http://localhost:8000/templates/dashboard`
153
-
154
- ### 5. Business Logic Layer (`src/managers/`)
155
-
156
- **Service Layer for Complex Operations**
157
-
158
- #### Manager Components:
159
-
160
- 1. **`chat_manager.py`**:
161
- ```python
162
- - Session management
163
- - Message handling
164
- - Context preservation
165
- - Agent orchestration
166
- ```
167
-
168
- 2. **`ai_manager.py`**:
169
- ```python
170
- - Model selection and routing
171
- - Token tracking and cost calculation
172
- - Error handling and retries
173
- - Response formatting
174
- ```
175
-
176
- 3. **`session_manager.py`**:
177
- ```python
178
- - Session lifecycle management
179
- - Data sharing between agents
180
- - Memory management
181
- - Cleanup operations
182
- ```
183
-
184
- ### 6. Utility Layer (`src/utils/`)
185
-
186
- **Shared Services and Helpers**
187
-
188
- - **`logger.py`**: Centralized logging system
189
- - **`generate_report.py`**: HTML report generation
190
- - **`model_registry.py`**: AI model configuration
191
-
192
- ## 🔄 Data Flow Architecture
193
-
194
- ### 1. Request Processing Flow
195
-
196
- ```
197
- HTTP Request → FastAPI Router → Route Handler → Manager/Business Logic →
198
- Database/Agent System → AI Model → Response Processing → JSON Response
199
- ```
200
-
201
- ### 2. Agent Execution Flow
202
-
203
- ```
204
- User Query → Session Creation → Template Selection → Agent Loading →
205
- Code Generation → Code Execution → Result Processing → Response Formatting
206
- ```
207
-
208
- ### 3. Deep Analysis Flow
209
-
210
- ```
211
- Analysis Goal → Question Generation → Planning Phase → Agent Coordination →
212
- Code Synthesis → Execution → Result Synthesis → Final Report Generation
213
- ```
214
-
215
- ### 4. Template System Flow
216
-
217
- ```
218
- User Preferences → Template Loading → Agent Registration →
219
- Capability Mapping → Execution Routing → Usage Tracking
220
- ```
221
-
222
- ## 🎨 Design Patterns
223
-
224
- ### 1. **Module Pattern**
225
- - Clear separation of concerns
226
- - Each module has specific responsibilities
227
- - Minimal dependencies between modules
228
-
229
- ### 2. **Repository Pattern**
230
- - Database access abstracted through SQLAlchemy
231
- - Session management centralized
232
- - Clean separation of data and business logic
233
-
234
- ### 3. **Strategy Pattern**
235
- - Multiple AI models supported through unified interface
236
- - Agent selection based on user preferences
237
- - Dynamic template loading
238
-
239
- ### 4. **Observer Pattern**
240
- - Usage tracking and analytics
241
- - Event-driven model updates
242
- - Real-time progress notifications
243
-
244
- ## 🔧 Configuration Management
245
-
246
- ### Environment Configuration
247
-
248
- ```python
249
- # Database
250
- DATABASE_URL: str # Database connection string
251
- POSTGRES_PASSWORD: str # PostgreSQL password (optional)
252
-
253
- # AI Models
254
- ANTHROPIC_API_KEY: str # Claude API key
255
- OPENAI_API_KEY: str # OpenAI API key
256
-
257
- # Authentication
258
- ADMIN_API_KEY: str # Admin operations key (optional)
259
-
260
- # Deployment
261
- PORT: int = 8000 # Server port
262
- DEBUG: bool = False # Debug mode
263
- ```
264
-
265
- ### Agent Configuration (`agents_config.json`)
266
-
267
- ```json
268
- {
269
- "default_agents": [
270
- {
271
- "template_name": "preprocessing_agent",
272
- "description": "Data cleaning and preparation",
273
- "variant_type": "both",
274
- "is_premium": false,
275
- "usage_count": 0,
276
- "icon_url": "preprocessing.svg"
277
- }
278
- ],
279
- "premium_templates": [...],
280
- "remove": [...]
281
- }
282
- ```
283
-
284
- ## 🔒 Security Architecture
285
-
286
- ### Authentication & Authorization
287
-
288
- 1. **Session-based Authentication**:
289
- - Session IDs for user identification
290
- - Optional API key authentication for admin endpoints
291
-
292
- 2. **Input Validation**:
293
- - Pydantic models for request validation
294
- - SQL injection prevention through SQLAlchemy
295
- - File upload restrictions and validation
296
-
297
- 3. **Resource Protection**:
298
- - User-specific data isolation
299
- - Usage tracking and monitoring
300
- - Rate limiting considerations
301
-
302
- ### Data Security
303
-
304
- 1. **Database Security**:
305
- - Encrypted connections for PostgreSQL
306
- - Parameterized queries
307
- - Regular backup procedures
308
-
309
- 2. **Code Execution Security**:
310
- - Sandboxed code execution environment
311
- - Limited library imports
312
- - Timeout protection
313
-
314
- ## 📊 Performance Architecture
315
-
316
- ### Scalability Features
317
-
318
- 1. **Async Architecture**:
319
- - Non-blocking I/O operations
320
- - Concurrent agent execution
321
- - Streaming responses for long operations
322
-
323
- 2. **Database Optimization**:
324
- - Connection pooling
325
- - Query optimization
326
- - Indexed frequently accessed columns
327
-
328
- 3. **Caching Strategy**:
329
- - In-memory caching for templates
330
- - Result caching for expensive operations
331
- - Session data management
332
-
333
- ### Performance Monitoring
334
-
335
- 1. **Usage Analytics**:
336
- - Request/response time tracking
337
- - Token usage monitoring
338
- - Error rate analysis
339
-
340
- 2. **Resource Monitoring**:
341
- - Database query performance
342
- - Memory usage tracking
343
- - Agent execution time analysis
344
-
345
- ## 🚀 Deployment Architecture
346
-
347
- ### Development Environment
348
-
349
- ```
350
- Local Development → SQLite Database → File-based Logging →
351
- Direct Model API Calls → Hot Reloading
352
- ```
353
-
354
- ### Production Environment
355
-
356
- ```
357
- Load Balancer → Multiple FastAPI Instances → PostgreSQL Database →
358
- Centralized Logging → Monitoring & Alerting
359
- ```
360
-
361
- ### Container Architecture
362
-
363
- ```dockerfile
364
- # Multi-stage build for optimization
365
- FROM python:3.11-slim as base
366
- # Dependencies and application setup
367
- # Health checks and graceful shutdown
368
- # Environment-specific configurations
369
- ```
370
-
371
- ## 🔄 Integration Patterns
372
-
373
- ### External Service Integration
374
-
375
- 1. **AI Model Providers**:
376
- - Anthropic (Claude)
377
- - OpenAI (GPT models)
378
- - Unified interface through DSPy
379
-
380
- 2. **Database Systems**:
381
- - PostgreSQL (production)
382
- - SQLite (development)
383
- - Migration support through Alembic
384
-
385
- ### Frontend Integration
386
-
387
- 1. **REST API**:
388
- - Standard HTTP endpoints
389
- - JSON request/response format
390
- - Session-based communication
391
-
392
- 2. **Data Exchange**:
393
- - File upload capabilities
394
- - Real-time analysis results
395
- - Report generation and download
396
-
397
- ### Third-Party Integration
398
-
399
- 1. **Python Data Science Stack (For Agentic Use only)**:
400
- - Pandas for data manipulation
401
- - NumPy for numerical computing
402
- - Scikit-learn for machine learning
403
- - Plotly for visualization
404
- - Statsmodels for statistical analysis
405
-
406
- 2. **Development Tools**:
407
- - Alembic for database migrations
408
- - SQLAlchemy for ORM
409
- - FastAPI for web framework
410
- - Pydantic for data validation
411
-
412
- ## 📝 Documentation Architecture
413
-
414
- ### API Documentation
415
-
416
- 1. **Auto-generated Docs**: Available at `/docs` endpoint
417
- 2. **Schema Definitions**: Pydantic models with descriptions
418
- 3. **Endpoint Documentation**: Detailed parameter and response docs
419
-
420
- ### Code Documentation
421
-
422
- 1. **Inline Documentation**: Comprehensive docstrings
423
- 2. **Architecture Guides**: High-level system design documentation
424
- 3. **Getting Started**: Developer onboarding documentation
425
- 4. **Troubleshooting**: Common issues and solutions
426
-
427
- This architecture provides a robust, scalable foundation for multi-agent AI analysis while maintaining clean separation of concerns and supporting both development and production deployment scenarios.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/db_schema.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Schema Design Explanation
2
+
3
+ The database schema is designed to support the application's functionality by efficiently managing user data, chat sessions, messages, and model usage statistics. Each model is structured to ensure data integrity and facilitate quick access to relevant information, enabling seamless interactions within the Auto-Analyst application.
4
+
5
+ ## Database Schema
6
+
7
+ The application uses the following database models:
8
+
9
+ ### User
10
+ The User table is used to store user information, including their username, email, and creation date. It is defined in the `init_db.py` file at [init_db.py#L17](../src/init_db.py#L17).
11
+
12
+
13
+ ```python
14
+ class User(Base):
15
+ __tablename__ = 'users'
16
+
17
+ user_id = Column(Integer, primary_key=True, autoincrement=True)
18
+ username = Column(String, unique=True, nullable=False)
19
+ email = Column(String, unique=True, nullable=False)
20
+ created_at = Column(DateTime, default=datetime.utcnow)
21
+ ```
22
+
23
+ ### Chat
24
+ The Chat table is used to store chat sessions, including the chat ID, user ID, title, and creation date. It is defined in the `init_db.py` file at [init_db.py#L25](../src/init_db.py#L25).
25
+
26
+ ```python
27
+ class Chat(Base):
28
+ __tablename__ = 'chats'
29
+
30
+ chat_id = Column(Integer, primary_key=True, autoincrement=True)
31
+ user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
32
+ title = Column(String, default='New Chat')
33
+ created_at = Column(DateTime, default=datetime.utcnow)
34
+ ```
35
+
36
+ ### Message
37
+ The Message table is used to store messages, including the message ID, chat ID, sender, content, and timestamp. It is defined in the `init_db.py` file at [init_db.py#L30](../src/init_db.py#L30).
38
+
39
+ ```python
40
+ class Message(Base):
41
+ __tablename__ = 'messages'
42
+
43
+ message_id = Column(Integer, primary_key=True, autoincrement=True)
44
+ chat_id = Column(Integer, ForeignKey('chats.chat_id'), nullable=False)
45
+ sender = Column(String, nullable=False) # 'user' or 'ai'
46
+ content = Column(Text, nullable=False)
47
+ timestamp = Column(DateTime, default=datetime.utcnow)
48
+ ```
49
+
50
+ ### ModelUsage
51
+ The ModelUsage table is used to store model usage statistics, including the usage ID, user ID, chat ID, model name, provider, prompt tokens, completion tokens, total tokens, query size, response size, cost, timestamp, and streaming status. It is defined in the `init_db.py` file at [init_db.py#L40](../src/init_db.py#L40).
52
+
53
+ ```python
54
+ class ModelUsage(Base):
55
+ __tablename__ = 'model_usage'
56
+
57
+ usage_id = Column(Integer, primary_key=True)
58
+ user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
59
+ chat_id = Column(Integer, ForeignKey('chats.chat_id'), nullable=True)
60
+ model_name = Column(String(100), nullable=False)
61
+ provider = Column(String(50), nullable=False)
62
+ prompt_tokens = Column(Integer, default=0)
63
+ completion_tokens = Column(Integer, default=0)
64
+ total_tokens = Column(Integer, default=0)
65
+ query_size = Column(Integer, default=0) # Size in characters
66
+ response_size = Column(Integer, default=0) # Size in characters
67
+ cost = Column(Float, default=0.0) # Cost in USD
68
+ timestamp = Column(DateTime, default=datetime.utcnow)
69
+ is_streaming = Column(Boolean, default=False)
70
+ request_time_ms = Column(Integer, default=0) # Request processing time in milliseconds
71
+ ```
72
+
73
+
74
+ The models defined in this file are utilized for storing and retrieving data in the database. The database schema is managed using SQLAlchemy, which facilitates the creation and manipulation of these models. For the initialization of the database, refer to the [init_db.py](/Auto-Analyst-CS/auto-analyst-backend/src/init_db.py) file.
docs/development/development_workflow.md DELETED
@@ -1,506 +0,0 @@
1
- # Auto-Analyst Backend Development Workflow
2
-
3
- ## 🎯 Development Philosophy
4
-
5
- The Auto-Analyst backend follows modern Python development practices with emphasis on:
6
- - **Modularity**: Clear separation of concerns across components
7
- - **Async-First**: Non-blocking operations for scalability
8
- - **Type Safety**: Comprehensive type hints and validation
9
- - **Documentation**: Self-documenting code and comprehensive docs
10
- - **Testing**: Robust testing at multiple levels
11
- - **Performance**: Optimized for real-world usage patterns
12
-
13
- ## 🏗️ Code Organization Principles
14
-
15
- ### 1. **Directory Structure Standards**
16
-
17
- ```
18
- src/
19
- ├── agents/ # AI agent implementations
20
- │ ├── agents.py # Core agent definitions
21
- │ ├── deep_agents.py # Deep analysis system
22
- │ └── retrievers/ # Information retrieval components
23
- ├── db/ # Database layer
24
- │ ├── init_db.py # Database initialization
25
- │ └── schemas/ # SQLAlchemy models
26
- ├── managers/ # Business logic layer
27
- │ ├── chat_manager.py # Chat operations
28
- │ ├── ai_manager.py # AI model management
29
- │ └── session_manager.py # Session lifecycle
30
- ├── routes/ # FastAPI route handlers
31
- │ ├── session_routes.py # Core functionality
32
- │ ├── chat_routes.py # Chat endpoints
33
- │ └── [feature]_routes.py # Feature-specific routes
34
- ├── utils/ # Shared utilities
35
- │ ├── logger.py # Centralized logging
36
- │ └── helpers.py # Common functions
37
- └── schemas/ # Pydantic models
38
- ├── chat_schemas.py # Chat data models
39
- └── [feature]_schemas.py # Feature schemas
40
- ```
41
-
42
- ### 2. **Import Organization**
43
-
44
- ```python
45
- # Standard library imports
46
- import asyncio
47
- import json
48
- from datetime import datetime
49
- from typing import List, Optional, Dict, Any
50
-
51
- # Third-party imports
52
- import dspy
53
- import pandas as pd
54
- from fastapi import APIRouter, Depends, HTTPException
55
- from pydantic import BaseModel
56
- from sqlalchemy.orm import Session
57
-
58
- # Local imports
59
- from src.db.init_db import session_factory
60
- from src.db.schemas.models import User, Chat
61
- from src.utils.logger import Logger
62
- from src.managers.chat_manager import ChatManager
63
- ```
64
-
65
- ## 🛠️ Development Patterns
66
-
67
- ### 1. **Agent Development Pattern**
68
-
69
- ```python
70
- # 1. Define DSPy Signature
71
- class new_analysis_agent(dspy.Signature):
72
- """
73
- Comprehensive docstring explaining:
74
- - Agent purpose and capabilities
75
- - Input requirements and formats
76
- - Expected output format
77
- - Usage examples
78
- """
79
- goal = dspy.InputField(desc="Clear description of analysis objective")
80
- dataset = dspy.InputField(desc="Dataset structure and content description")
81
- plan_instructions = dspy.InputField(desc="Execution plan from planner")
82
-
83
- summary = dspy.OutputField(desc="Natural language summary of analysis")
84
- code = dspy.OutputField(desc="Executable Python code for analysis")
85
-
86
- # 2. Add to Agent Configuration
87
- # In agents_config.json:
88
- {
89
- "template_name": "new_analysis_agent",
90
- "description": "Performs specialized analysis on datasets",
91
- "variant_type": "both", # individual, planner, or both
92
- "is_premium": false, # Will be active by default
93
- "usage_count": 0,
94
- "icon_url": "analysis.svg"
95
- }
96
-
97
- # 3. Register in Agent System
98
- # In agents.py, add to the appropriate loading functions
99
- ```
100
-
101
- ### 2. **Route Development Pattern**
102
-
103
- ```python
104
- # 1. Create route file: src/routes/feature_routes.py
105
- from fastapi import APIRouter, Depends, HTTPException, Query
106
- from pydantic import BaseModel
107
- from typing import List, Optional
108
- from src.db.init_db import session_factory
109
- from src.db.schemas.models import FeatureModel
110
- from src.utils.logger import Logger
111
-
112
- logger = Logger("feature_routes", see_time=True, console_log=False)
113
- router = APIRouter(prefix="/feature", tags=["feature"])
114
-
115
- # 2. Define Pydantic schemas
116
- class FeatureCreate(BaseModel):
117
- name: str
118
- description: Optional[str] = None
119
-
120
- class FeatureResponse(BaseModel):
121
- id: int
122
- name: str
123
- description: Optional[str]
124
- created_at: datetime
125
-
126
- # 3. Implement endpoints with proper error handling
127
- @router.post("/", response_model=FeatureResponse)
128
- async def create_feature(feature: FeatureCreate):
129
- try:
130
- session = session_factory()
131
- try:
132
- new_feature = FeatureModel(
133
- name=feature.name,
134
- description=feature.description
135
- )
136
- session.add(new_feature)
137
- session.commit()
138
- session.refresh(new_feature)
139
-
140
- return FeatureResponse(
141
- id=new_feature.id,
142
- name=new_feature.name,
143
- description=new_feature.description,
144
- created_at=new_feature.created_at
145
- )
146
-
147
- except Exception as e:
148
- session.rollback()
149
- logger.log_message(f"Error creating feature: {str(e)}", level=logging.ERROR)
150
- raise HTTPException(status_code=500, detail=f"Failed to create feature: {str(e)}")
151
- finally:
152
- session.close()
153
-
154
- except Exception as e:
155
- logger.log_message(f"Error in create_feature: {str(e)}", level=logging.ERROR)
156
- raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
157
-
158
- # 4. Register in app.py
159
- from src.routes.feature_routes import router as feature_router
160
- app.include_router(feature_router)
161
- ```
162
-
163
- ### 3. **Database Model Pattern**
164
-
165
- ```python
166
- # In src/db/schemas/models.py
167
- from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, ForeignKey
168
- from sqlalchemy.orm import relationship
169
- from sqlalchemy.ext.declarative import declarative_base
170
- from datetime import datetime, timezone
171
-
172
- Base = declarative_base()
173
-
174
- class NewModel(Base):
175
- __tablename__ = "new_models"
176
-
177
- # Primary key
178
- id = Column(Integer, primary_key=True, autoincrement=True)
179
-
180
- # Required fields
181
- name = Column(String(255), nullable=False, unique=True)
182
-
183
- # Optional fields
184
- description = Column(Text, nullable=True)
185
- is_active = Column(Boolean, default=True, nullable=False)
186
-
187
- # Timestamps
188
- created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
189
- updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
190
-
191
- # Foreign keys
192
- user_id = Column(Integer, ForeignKey("users.user_id"), nullable=True)
193
-
194
- # Relationships
195
- user = relationship("User", back_populates="new_models")
196
-
197
- def __repr__(self):
198
- return f"<NewModel(id={self.id}, name='{self.name}')>"
199
-
200
- # Update User model to include back reference
201
- class User(Base):
202
- # ... existing fields ...
203
- new_models = relationship("NewModel", back_populates="user")
204
- ```
205
-
206
- ### 4. **Manager Pattern**
207
-
208
- ```python
209
- # In src/managers/feature_manager.py
210
- from typing import List, Optional, Dict, Any
211
- from sqlalchemy.orm import Session
212
- from src.db.schemas.models import FeatureModel
213
- from src.utils.logger import Logger
214
-
215
- logger = Logger("feature_manager", see_time=True, console_log=False)
216
-
217
- class FeatureManager:
218
- """
219
- Manages business logic for feature operations.
220
- Separates complex business logic from route handlers.
221
- """
222
-
223
- def __init__(self, session: Session):
224
- self.session = session
225
-
226
- async def create_feature(self, name: str, description: Optional[str] = None) -> FeatureModel:
227
- """Create a new feature with validation and business logic."""
228
- try:
229
- # Validation
230
- if not name or len(name.strip()) == 0:
231
- raise ValueError("Feature name cannot be empty")
232
-
233
- # Check for duplicates
234
- existing = self.session.query(FeatureModel).filter_by(name=name).first()
235
- if existing:
236
- raise ValueError(f"Feature with name '{name}' already exists")
237
-
238
- # Create feature
239
- feature = FeatureModel(name=name, description=description)
240
- self.session.add(feature)
241
- self.session.commit()
242
- self.session.refresh(feature)
243
-
244
- logger.log_message(f"Created feature: {name}", level=logging.INFO)
245
- return feature
246
-
247
- except Exception as e:
248
- self.session.rollback()
249
- logger.log_message(f"Error creating feature: {str(e)}", level=logging.ERROR)
250
- raise
251
-
252
- async def get_features(self, active_only: bool = True) -> List[FeatureModel]:
253
- """Retrieve features with optional filtering."""
254
- try:
255
- query = self.session.query(FeatureModel)
256
- if active_only:
257
- query = query.filter(FeatureModel.is_active == True)
258
-
259
- features = query.order_by(FeatureModel.created_at.desc()).all()
260
- return features
261
-
262
- except Exception as e:
263
- logger.log_message(f"Error retrieving features: {str(e)}", level=logging.ERROR)
264
- raise
265
- ```
266
-
267
- ## 📋 Code Quality Standards
268
-
269
- ### 1. **Type Hints and Documentation**
270
-
271
- ```python
272
- from typing import List, Optional, Dict, Any, Union
273
- from datetime import datetime
274
-
275
- async def process_analysis_data(
276
- data: pd.DataFrame,
277
- analysis_type: str,
278
- user_id: Optional[int] = None,
279
- options: Dict[str, Any] = None
280
- ) -> Dict[str, Union[str, List[Any], bool]]:
281
- """
282
- Process analysis data with specified parameters.
283
-
284
- Args:
285
- data: Input DataFrame containing the data to analyze
286
- analysis_type: Type of analysis to perform ("statistical", "ml", "viz")
287
- user_id: Optional user ID for tracking and personalization
288
- options: Additional options for analysis configuration
289
-
290
- Returns:
291
- Dictionary containing:
292
- - status: "success" or "error"
293
- - result: Analysis results or error message
294
- - metadata: Additional information about the analysis
295
-
296
- Raises:
297
- ValueError: If analysis_type is not supported
298
- DataError: If data format is invalid
299
-
300
- Example:
301
- >>> data = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
302
- >>> result = await process_analysis_data(data, "statistical")
303
- >>> print(result["status"])
304
- "success"
305
- """
306
- if options is None:
307
- options = {}
308
-
309
- # Implementation...
310
- return {"status": "success", "result": [], "metadata": {}}
311
- ```
312
-
313
- ### 2. **Error Handling Patterns**
314
-
315
- ```python
316
- # Comprehensive error handling with logging and user-friendly messages
317
- async def safe_operation(data: Any) -> Dict[str, Any]:
318
- """
319
- Template for safe operations with comprehensive error handling.
320
- """
321
- try:
322
- # Validation
323
- if not data:
324
- raise ValueError("Data cannot be empty")
325
-
326
- # Main operation
327
- result = await perform_operation(data)
328
-
329
- # Success logging
330
- logger.log_message("Operation completed successfully", level=logging.INFO)
331
- return {"success": True, "data": result}
332
-
333
- except ValueError as e:
334
- # Input validation errors
335
- logger.log_message(f"Validation error: {str(e)}", level=logging.WARNING)
336
- return {"success": False, "error": "Invalid input", "details": str(e)}
337
-
338
- except ConnectionError as e:
339
- # External service errors
340
- logger.log_message(f"Connection error: {str(e)}", level=logging.ERROR)
341
- return {"success": False, "error": "Service unavailable", "details": "Please try again later"}
342
-
343
- except Exception as e:
344
- # Unexpected errors
345
- logger.log_message(f"Unexpected error in safe_operation: {str(e)}", level=logging.ERROR)
346
- return {"success": False, "error": "Internal error", "details": "Please contact support"}
347
- ```
348
-
349
- ### 3. **Async/Await Best Practices**
350
-
351
- ```python
352
- import asyncio
353
- from typing import List, Coroutine
354
-
355
- # Proper async function definition
356
- async def async_agent_execution(agents: List[str], query: str) -> List[Dict[str, Any]]:
357
- """Execute multiple agents concurrently."""
358
-
359
- # Create coroutines
360
- tasks = [
361
- execute_single_agent(agent, query)
362
- for agent in agents
363
- ]
364
-
365
- # Execute concurrently with error handling
366
- results = []
367
- for task in asyncio.as_completed(tasks):
368
- try:
369
- result = await task
370
- results.append(result)
371
- except Exception as e:
372
- logger.log_message(f"Agent execution failed: {e}", level=logging.ERROR)
373
- results.append({"error": str(e)})
374
-
375
- return results
376
-
377
- # Database operations with proper session management
378
- async def async_database_operation(session: Session) -> Any:
379
- """Template for async database operations."""
380
- try:
381
- # Use asyncio.to_thread for CPU-bound database operations
382
- result = await asyncio.to_thread(
383
- lambda: session.query(Model).filter(...).all()
384
- )
385
- return result
386
- except Exception as e:
387
- session.rollback()
388
- raise
389
- finally:
390
- session.close()
391
- ```
392
-
393
- ## 🔧 Development Workflow
394
-
395
- ### 1. **Feature Development Process**
396
-
397
- 1. **Plan the Feature**:
398
- ```bash
399
- # Create feature branch
400
- git checkout -b feature/new-analysis-agent
401
-
402
- # Document requirements
403
- echo "## New Analysis Agent" >> docs/feature_plan.md
404
- ```
405
-
406
- 2. **Implement Core Logic**:
407
- ```bash
408
- # Create agent signature
409
- # Add to agents_config.json
410
- # Implement business logic in managers/
411
- # Create route handlers
412
- ```
413
-
414
- 3. **Add Database Changes**:
415
- ```bash
416
- # Modify models if needed
417
- alembic revision --autogenerate -m "Add new analysis tables"
418
- alembic upgrade head
419
- ```
420
-
421
- ### 3. **Release Process**
422
-
423
- 1. **Pre-release Testing**:
424
- ```bash
425
- # Run full test suite
426
- pytest tests/
427
-
428
- # Test database migrations
429
- alembic upgrade head
430
-
431
- # Test with sample data
432
- python scripts/test_with_sample_data.py
433
- ```
434
-
435
- 2. **Documentation Updates**:
436
- ```bash
437
- # Update API documentation
438
- # Update troubleshooting guide
439
- # Update changelog
440
- ```
441
-
442
- 3. **Deployment Preparation**:
443
- ```bash
444
- # Update requirements.txt
445
- pip freeze > requirements.txt
446
-
447
- # Test container build
448
- docker build -t auto-analyst-backend .
449
-
450
- ```
451
-
452
- ## 📊 Performance Considerations
453
-
454
- ### 1. **Database Optimization**
455
-
456
- ```python
457
- # Use query optimization
458
- from sqlalchemy.orm import joinedload
459
-
460
- # Bad: N+1 query problem
461
- users = session.query(User).all()
462
- for user in users:
463
- print(user.chats) # Separate query for each user
464
-
465
- # Good: Eager loading
466
- users = session.query(User).options(joinedload(User.chats)).all()
467
- for user in users:
468
- print(user.chats) # No additional queries
469
-
470
- # Use pagination for large datasets
471
- def get_paginated_results(session, model, page=1, per_page=20):
472
- offset = (page - 1) * per_page
473
- return session.query(model).offset(offset).limit(per_page).all()
474
- ```
475
-
476
-
477
- ### 2. **Async Optimization**
478
-
479
- ```python
480
- # Use connection pooling
481
- from sqlalchemy.pool import QueuePool
482
-
483
- engine = create_engine(
484
- DATABASE_URL,
485
- poolclass=QueuePool,
486
- pool_size=20,
487
- max_overflow=30
488
- )
489
-
490
- # Batch operations
491
- async def batch_process_agents(agents: List[str], queries: List[str]):
492
- semaphore = asyncio.Semaphore(5) # Limit concurrent operations
493
-
494
- async def process_with_limit(agent, query):
495
- async with semaphore:
496
- return await process_agent(agent, query)
497
-
498
- tasks = [
499
- process_with_limit(agent, query)
500
- for agent, query in zip(agents, queries)
501
- ]
502
-
503
- return await asyncio.gather(*tasks, return_exceptions=True)
504
- ```
505
-
506
- This development workflow guide provides a comprehensive framework for maintaining code quality, consistency, and performance in the Auto-Analyst backend system. Following these patterns ensures that new features integrate seamlessly with the existing architecture while maintaining the high standards of the codebase.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/{api/README.md → endpoints.md} RENAMED
@@ -5,19 +5,6 @@ This document is a guide to the backend API endpoints utilized within the Auto-A
5
  The Auto-Analyst application is designed to facilitate seamless interactions and efficient data management, making it essential for users to understand the available endpoints and their functionalities. Each section of this documentation is crafted to provide clarity and insight into how the API operates, ensuring that developers and users alike can effectively leverage its capabilities.
6
 
7
  For more specific details regarding the various functionalities offered by the API, please refer to the following sections, which delve deeper into their respective areas:
8
-
9
- ## 📚 Core Documentation
10
-
11
- - **[Getting Started Guide](./getting_started.md)**: Quick start guide for new developers and LLMs to understand the system architecture and get up to speed quickly
12
- - **[System Architecture](./architecture.md)**: Comprehensive overview of the backend system design, components, and data flow patterns
13
- - **[Troubleshooting Guide](./troubleshooting.md)**: Common issues, debugging tools, and solutions for development and deployment problems
14
-
15
- ## 🛠️ API Reference
16
-
17
- - **[Core Endpoints](./routes/session.md)**: Review the core endpoints that handle fundamental operations within the application, including data uploads, AI analysis, model settings, and session management.
18
- - **[Analytics Endpoints](./routes/analytics.md)**: Explore the endpoints dedicated to analytics, providing insights into usage statistics, performance metrics, cost analysis, and real-time monitoring.
19
- - **[Chat Endpoints](./routes/chats.md)**: Discover the endpoints that manage chat interactions, enabling users to create, retrieve, and manage chat sessions effectively.
20
- - **[Code Endpoints](./routes/code.md)**: Learn about the endpoints for code execution, editing, fixing, and cleaning operations with advanced AI assistance.
21
- - **[Deep Analysis Endpoints](./routes/deep_analysis.md)**: Comprehensive documentation for the multi-agent deep analysis system, including streaming progress, report management, template integration, and how user's active agents are leveraged for advanced analytical insights.
22
- - **[Feedback Endpoints](./routes/feedback.md)**: Understand the endpoints for managing user feedback on AI-generated messages, including rating systems and model performance tracking.
23
- - **[Templates Endpoints](./routes/templates.md)**: Comprehensive guide to the template system, agent loading, user preferences, and how personalized AI agent configurations work for different users.
 
5
  The Auto-Analyst application is designed to facilitate seamless interactions and efficient data management, making it essential for users to understand the available endpoints and their functionalities. Each section of this documentation is crafted to provide clarity and insight into how the API operates, ensuring that developers and users alike can effectively leverage its capabilities.
6
 
7
  For more specific details regarding the various functionalities offered by the API, please refer to the following sections, which delve deeper into their respective areas:
8
+ - [Analytics Endpoints](./analytics.md): Explore the endpoints dedicated to analytics, providing insights into usage statistics and performance metrics.
9
+ - [Chat Endpoints](./chats.md): Discover the endpoints that manage chat interactions, enabling users to create, retrieve, and manage chat sessions effectively.
10
+ - [Core Endpoints](./core.md): Review the core endpoints that handle fundamental operations within the application, including data uploads and session management.
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/getting_started.md DELETED
@@ -1,273 +0,0 @@
1
- # Auto-Analyst Backend - Getting Started Guide
2
-
3
- ## 🎯 Overview
4
-
5
- This guide will help you set up and understand the Auto-Analyst backend system. Auto-Analyst is a multi-agent AI platform that orchestrates specialized agents for comprehensive data analysis.
6
-
7
- ## 🏗️ Core Concepts
8
-
9
- ### 1. **Multi-Agent System**
10
- The platform uses specialized AI agents:
11
- - **Preprocessing Agent**: Data cleaning and preparation
12
- - **Statistical Analytics Agent**: Statistical analysis and insights
13
- - **Machine Learning Agent**: Scikit-learn based modeling
14
- - **Data Visualization Agent**: Chart and plot generation
15
-
16
- ### 2. **Template System**
17
- - **Individual Agents**: Single-purpose agents for specific tasks
18
- - **Planner Agents**: Multi-agent coordination for complex workflows
19
- - **User Templates**: Customizable agent preferences
20
- - **Default vs Premium**: Core agents available to all users
21
-
22
- ### 3. **Session Management**
23
- - Session-based user tracking
24
- - Shared DataFrame context between agents
25
- - Conversation history and code execution tracking
26
-
27
- ### 4. **Deep Analysis System**
28
- - Multi-step analysis workflow (questions → planning → execution → synthesis)
29
- - Streaming progress updates
30
- - HTML report generation
31
-
32
- ## 🚀 Quick Start
33
-
34
- ### 1. Installation
35
-
36
- ```bash
37
- # Clone and navigate to backend
38
- cd Auto-Analyst-CS/auto-analyst-backend
39
-
40
- # Create virtual environment
41
- python -m venv venv
42
- source venv/bin/activate # Linux/Mac
43
- # or
44
- venv\Scripts\activate # Windows
45
-
46
- # Install dependencies
47
- pip install -r requirements.txt
48
- ```
49
-
50
- ### 2. Environment Variables
51
-
52
- Create `.env` file with:
53
-
54
- ```env
55
- # Database
56
- DATABASE_URL=sqlite:///./auto_analyst.db # For development
57
- # DATABASE_URL=postgresql://user:pass@host:port/db # For production
58
-
59
- # AI Models
60
- ANTHROPIC_API_KEY=your_anthropic_key_here
61
- OPENAI_API_KEY=your_openai_key_here
62
-
63
- # Authentication (optional)
64
- ADMIN_API_KEY=your_admin_key_here
65
- ```
66
-
67
- ### 3. Database Initialization
68
-
69
- ```bash
70
- # Initialize database and default agents
71
- python -c "
72
- from src.db.init_db import init_db
73
- init_db()
74
- print('✅ Database initialized successfully')
75
- "
76
- ```
77
-
78
- ### 4. Start the Server
79
-
80
- ```bash
81
- # Development server
82
- python app.py
83
-
84
- # Or with uvicorn
85
- uvicorn app:app --reload --host 0.0.0.0 --port 8000
86
- ```
87
-
88
- ### 5. Verify Setup
89
-
90
- Visit: `http://localhost:8000/docs` for interactive API documentation
91
-
92
- ## 📚 Key Files to Understand
93
-
94
- ### Core Application Files
95
-
96
- 1. **`app.py`** - Main FastAPI application and core endpoints
97
- 2. **`src/agents/agents.py`** - Agent definitions and orchestration
98
- 3. **`src/agents/deep_agents.py`** - Deep analysis system
99
- 4. **`src/db/schemas/models.py`** - Database models
100
- 5. **`src/managers/chat_manager.py`** - Chat and session management
101
-
102
- ### Route Files (API Endpoints)
103
-
104
- - **`src/routes/session_routes.py`** - File uploads, sessions, authentication
105
- - **`src/routes/chat_routes.py`** - Chat and messaging
106
- - **`src/routes/code_routes.py`** - Code execution and processing
107
- - **`src/routes/templates_routes.py`** - Agent template management
108
- - **`src/routes/deep_analysis_routes.py`** - Deep analysis reports
109
- - **`src/routes/analytics_routes.py`** - Usage analytics and monitoring
110
-
111
- ### Configuration Files
112
-
113
- - **`agents_config.json`** - Agent and template definitions
114
- - **`requirements.txt`** - Python dependencies
115
- - **`alembic.ini`** - Database migration configuration
116
-
117
- ## 🔧 Development Workflow
118
-
119
- ### 1. Adding New Agents
120
-
121
- ```python
122
- # 1. Define agent signature in src/agents/agents.py
123
- class new_agent(dspy.Signature):
124
- """Agent description"""
125
- goal = dspy.InputField(desc="Analysis goal")
126
- dataset = dspy.InputField(desc="Dataset info")
127
- result = dspy.OutputField(desc="Analysis result")
128
-
129
- # 2. Add to agents_config.json
130
- {
131
- "template_name": "new_agent",
132
- "description": "Agent description",
133
- "variant_type": "both",
134
- "is_premium": false,
135
- "usage_count": 0
136
- }
137
-
138
- # 3. Register in agent loading system
139
- ```
140
-
141
- ### 2. Adding New Endpoints
142
-
143
- ```python
144
- # 1. Create route in src/routes/feature_routes.py
145
- from fastapi import APIRouter
146
- router = APIRouter(prefix="/feature", tags=["feature"])
147
-
148
- @router.get("/endpoint")
149
- async def new_endpoint():
150
- return {"message": "Hello"}
151
-
152
- # 2. Register in app.py
153
- from src.routes.feature_routes import router as feature_router
154
- app.include_router(feature_router)
155
- ```
156
-
157
- ### 3. Database Changes
158
-
159
- ```bash
160
- # 1. Modify models in src/db/schemas/models.py
161
- # 2. Create migration
162
- alembic revision --autogenerate -m "description"
163
- # 3. Apply migration
164
- alembic upgrade head
165
- ```
166
-
167
- ## 🧪 Testing Your Changes
168
-
169
- ### 1. Test API Endpoints
170
-
171
- ```bash
172
- # Use the interactive docs
173
- open http://localhost:8000/docs
174
-
175
- # Or use curl
176
- curl -X GET "http://localhost:8000/health"
177
- ```
178
-
179
- ### 2. Test Agent System
180
-
181
- ```python
182
- # Test individual agent
183
- python -c "
184
- from src.agents.agents import preprocessing_agent
185
- import dspy
186
- dspy.LM('anthropic/claude-sonnet-4-20250514')
187
- agent = dspy.ChainOfThought(preprocessing_agent)
188
- result = agent(goal='clean data', dataset='test data')
189
- print(result)
190
- "
191
- ```
192
-
193
- ### 3. Test Database Operations
194
-
195
- ```python
196
- # Test database
197
- python -c "
198
- from src.db.init_db import session_factory
199
- from src.db.schemas.models import AgentTemplate
200
- session = session_factory()
201
- templates = session.query(AgentTemplate).all()
202
- print(f'Found {len(templates)} templates')
203
- session.close()
204
- "
205
- ```
206
-
207
- ## 🔍 Common Development Tasks
208
-
209
- ### Adding a New Feature
210
-
211
- 1. **Plan the Feature**: Define requirements and API design
212
- 2. **Database Changes**: Add new models if needed
213
- 3. **Create Routes**: Add API endpoints in `src/routes/`
214
- 4. **Business Logic**: Add managers in `src/managers/` if complex
215
- 5. **Documentation**: Update relevant `.md` files
216
- 6. **Testing**: Test endpoints and integration
217
-
218
- ### Debugging Issues
219
-
220
- 1. **Check Logs**: Application logs show detailed error information
221
- 2. **Database State**: Verify data with database queries
222
- 3. **API Testing**: Use `/docs` interface for endpoint testing
223
- 4. **Agent Behavior**: Test individual agents separately
224
-
225
- ### Performance Optimization
226
-
227
- 1. **Database Queries**: Use SQLAlchemy query optimization
228
- 2. **Agent Execution**: Implement async patterns for agent orchestration
229
- 3. **Resource Management**: Monitor memory usage for large datasets
230
-
231
- ## 📊 System Architecture Overview
232
-
233
- ```mermaid
234
- graph TD
235
- A[Frontend Request] --> B[FastAPI Router]
236
- B --> C[Route Handler]
237
- C --> D[Manager Layer]
238
- D --> E[Database Layer]
239
- D --> F[Agent System]
240
- F --> G[AI Models]
241
- G --> H[Code Generation]
242
- H --> I[Execution Environment]
243
- I --> J[Results Processing]
244
- J --> K[Response]
245
-
246
- subgraph "Agent Orchestration"
247
- F1[Individual Agents]
248
- F2[Planner Module]
249
- F3[Deep Analysis]
250
- F1 --> F2
251
- F2 --> F3
252
- end
253
-
254
- F --> F1
255
- ```
256
-
257
- ## 📈 Template Integration
258
-
259
- The system uses **active user templates** for agent selection:
260
-
261
- ### Default Agents (Always Available)
262
- - `preprocessing_agent` (individual & planner variants)
263
- - `statistical_analytics_agent` (individual & planner variants)
264
- - `sk_learn_agent` (individual & planner variants)
265
- - `data_viz_agent` (individual & planner variants)
266
-
267
- ### Template Loading Logic
268
- 1. **Individual Agent Execution** (`@agent_name`): Loads ALL available templates
269
- 2. **Planner Execution**: Loads user's enabled templates (max 10 for performance)
270
- 3. **Deep Analysis**: Uses user's active template preferences
271
- 4. **Fallback**: Uses 4 core agents if no user preferences found
272
-
273
- This architecture ensures users can leverage their preferred agents while maintaining system performance and reliability.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/routes/analytics.md ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ## Analytics Routes Overview
3
+
4
+ These routes provide comprehensive analytics functionality for the Auto-Analyst application, including dashboard summaries, user and model analytics, and cost breakdowns.
5
+
6
+ ### Authentication
7
+
8
+ All analytics endpoints require admin authentication via an API key:
9
+
10
+ ```python
11
+ ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "default-admin-key-change-me")
12
+ ```
13
+
14
+ The API key can be provided via:
15
+ - **Header:** `X-Admin-API-Key`
16
+ - **Query parameter:** `admin_api_key`
17
+
18
+ ---
19
+
20
+ ### Dashboard Endpoints
21
+
22
+ #### **GET /analytics/dashboard**
23
+ Returns a comprehensive summary of usage data for the dashboard.
24
+
25
+ **Query Parameters:**
26
+ - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
27
+
28
+ **Response:**
29
+ ```json
30
+ {
31
+ "total_tokens": 123456,
32
+ "total_cost": 25.50,
33
+ "total_requests": 1000,
34
+ "total_users": 50,
35
+ "daily_usage": [
36
+ {
37
+ "date": "2023-05-01",
38
+ "tokens": 5000,
39
+ "cost": 1.25,
40
+ "requests": 100
41
+ }
42
+ ],
43
+ "model_usage": [
44
+ {
45
+ "model_name": "gpt-4",
46
+ "tokens": 10000,
47
+ "cost": 10.00,
48
+ "requests": 200
49
+ }
50
+ ],
51
+ "top_users": [
52
+ {
53
+ "user_id": "123",
54
+ "tokens": 5000,
55
+ "cost": 5.00,
56
+ "requests": 50
57
+ }
58
+ ],
59
+ "start_date": "2023-04-01",
60
+ "end_date": "2023-05-01"
61
+ }
62
+ ```
63
+
64
+ **Process Flow:**
65
+ 1. Parse date range from the `period` parameter.
66
+ 2. Query total statistics (tokens, cost, requests, users).
67
+ 3. Query daily usage broken down by date.
68
+ 4. Query model usage statistics.
69
+ 5. Query top users by token usage.
70
+ 6. Return combined data.
71
+
72
+ ---
73
+
74
+ ### WebSocket **/analytics/dashboard/realtime**
75
+
76
+ WebSocket endpoint for real-time updates to dashboard data.
77
+
78
+ **Data Flow:**
79
+ 1. Client connects to WebSocket.
80
+ 2. Server adds the connection to the active connections set.
81
+ 3. Server broadcasts updates to all connected clients when new data arrives.
82
+ 4. Connection is removed when the client disconnects.
83
+
84
+ ---
85
+
86
+ ## User Analytics Endpoints
87
+
88
+ ### **GET /analytics/users**
89
+ Returns a list of users with their usage statistics.
90
+
91
+ **Query Parameters:**
92
+ - `limit` (optional): Maximum number of users to return (default: `100`)
93
+ - `offset` (optional): Offset for pagination (default: `0`)
94
+
95
+ **Response:**
96
+ ```json
97
+ {
98
+ "users": [
99
+ {
100
+ "user_id": "123",
101
+ "tokens": 5000,
102
+ "cost": 5.00,
103
+ "requests": 50,
104
+ "first_seen": "2023-04-01T12:00:00Z",
105
+ "last_seen": "2023-05-01T12:00:00Z"
106
+ }
107
+ ],
108
+ "total": 200,
109
+ "limit": 100,
110
+ "offset": 0
111
+ }
112
+ ```
113
+
114
+ **Process Flow:**
115
+ 1. Query user data with aggregated metrics.
116
+ 2. Calculate total users for pagination.
117
+ 3. Format and return data.
118
+
119
+ ---
120
+
121
+ ### **GET /analytics/users/activity**
122
+ Returns user activity metrics over time.
123
+
124
+ **Query Parameters:**
125
+ - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
126
+
127
+ **Response:**
128
+ ```json
129
+ {
130
+ "user_activity": [
131
+ {
132
+ "date": "2023-05-01",
133
+ "activeUsers": 20,
134
+ "newUsers": 5,
135
+ "sessions": 30
136
+ }
137
+ ]
138
+ }
139
+ ```
140
+
141
+ **Process Flow:**
142
+ 1. Parse date range from `period` parameter.
143
+ 2. Get first date each user was seen (for new users count).
144
+ 3. Get daily activity metrics.
145
+ 4. Fill in any missing dates with zeros.
146
+ 5. Return formatted data.
147
+
148
+ ---
149
+
150
+ ## Model Analytics Endpoints
151
+
152
+ ### **GET /analytics/usage/models**
153
+ Returns model usage breakdown.
154
+
155
+ **Query Parameters:**
156
+ - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
157
+
158
+ **Response:**
159
+ ```json
160
+ {
161
+ "model_usage": [
162
+ {
163
+ "model_name": "gpt-4",
164
+ "tokens": 10000,
165
+ "cost": 10.00,
166
+ "requests": 200,
167
+ "avg_response_time": 1.5
168
+ }
169
+ ]
170
+ }
171
+ ```
172
+
173
+ **Process Flow:**
174
+ 1. Parse date range from `period` parameter.
175
+ 2. Query model usage with aggregated metrics.
176
+ 3. Format and return data.
177
+
178
+ ---
179
+
180
+ ## Cost Analytics Endpoints
181
+
182
+ ### **GET /analytics/costs/summary**
183
+ Returns a summary of costs.
184
+
185
+ **Query Parameters:**
186
+ - `period` (optional): Time period (`7d`, `30d`, `90d`, default: `30d`)
187
+
188
+ **Response:**
189
+ ```json
190
+ {
191
+ "totalCost": 25.50,
192
+ "totalTokens": 100000,
193
+ "totalRequests": 1000,
194
+ "avgDailyCost": 0.85,
195
+ "costPerThousandTokens": 0.255,
196
+ "daysInPeriod": 30,
197
+ "startDate": "2023-04-01",
198
+ "endDate": "2023-05-01"
199
+ }
200
+ ```
docs/{api/routes → routes}/chats.md RENAMED
@@ -7,23 +7,27 @@ These routes handle chat interactions, message processing, user management, and
7
  ### **Chat Management**
8
 
9
  #### **1. Create a New Chat**
10
- **Endpoint:** `POST /chats/`
11
  **Description:** Creates a new chat session.
12
  **Request Body:**
13
  ```json
14
  {
15
- "user_id": 123
 
16
  }
17
  ```
18
  **Response:**
19
  ```json
20
  {
21
  "chat_id": 456,
22
- "user_id": 123,
23
  "title": "New Chat",
24
- "created_at": "2023-05-01T12:00:00Z"
 
25
  }
26
  ```
 
 
 
27
 
28
  ---
29
 
@@ -50,33 +54,42 @@ These routes handle chat interactions, message processing, user management, and
50
  ]
51
  }
52
  ```
 
 
 
 
 
53
 
54
  ---
55
 
56
  #### **3. List Recent Chats**
57
- **Endpoint:** `GET /chats/`
58
  **Description:** Retrieves a list of recent chats, optionally filtered by user ID.
59
  **Query Parameters:**
60
  - `user_id` (Optional for filtering by user)
61
- - `limit` (Maximum number of chats, default: 10, max: 100)
62
  - `offset` (For pagination, default: 0)
63
  **Response:**
64
  ```json
65
  [
66
  {
67
  "chat_id": 456,
68
- "user_id": 123,
69
  "title": "New Chat",
70
- "created_at": "2023-05-01T12:00:00Z"
 
71
  }
72
  ]
73
  ```
 
 
 
 
74
 
75
  ---
76
 
77
  #### **4. Update a Chat**
78
  **Endpoint:** `PUT /chats/{chat_id}`
79
- **Description:** Updates a chat's title or user ID.
80
  **Path Parameter:** `chat_id` (ID of the chat to update)
81
  **Request Body:**
82
  ```json
@@ -94,25 +107,57 @@ These routes handle chat interactions, message processing, user management, and
94
  "user_id": 123
95
  }
96
  ```
 
 
 
97
 
98
  ---
99
 
100
  #### **5. Delete a Chat**
101
  **Endpoint:** `DELETE /chats/{chat_id}`
102
- **Description:** Deletes a chat and all its messages while preserving model usage records.
103
  **Path Parameter:** `chat_id` (ID of the chat to delete)
104
  **Query Parameter:** `user_id` (Optional for access control)
105
  **Response:**
106
  ```json
107
  {
108
- "message": "Chat 456 deleted successfully",
109
- "preserved_model_usage": true
110
  }
111
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  ---
114
 
115
- #### **6. Cleanup Empty Chats**
116
  **Endpoint:** `POST /chats/cleanup-empty`
117
  **Description:** Deletes empty chats for a user.
118
  **Request Body:**
@@ -128,33 +173,30 @@ These routes handle chat interactions, message processing, user management, and
128
  "message": "Deleted 5 empty chats"
129
  }
130
  ```
 
 
 
 
131
 
132
  ---
133
 
134
  ### **Message Management**
135
 
136
- #### **1. Add Message to Chat**
137
- **Endpoint:** `POST /chats/{chat_id}/messages`
138
- **Description:** Adds a message to an existing chat.
 
 
139
  **Path Parameter:** `chat_id` (ID of the chat)
140
- **Query Parameter:** `user_id` (Optional for access control)
141
  **Request Body:**
142
- ```json
143
- {
144
- "content": "Hello, I need help with data analysis",
145
- "sender": "user"
146
- }
147
- ```
148
- **Response:**
149
- ```json
150
- {
151
- "message_id": 789,
152
- "chat_id": 456,
153
- "content": "Hello, I need help with data analysis",
154
- "sender": "user",
155
- "timestamp": "2023-05-01T12:01:00Z"
156
- }
157
- ```
158
 
159
  ---
160
 
@@ -179,3 +221,37 @@ These routes handle chat interactions, message processing, user management, and
179
  "created_at": "2023-05-01T12:00:00Z"
180
  }
181
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ### **Chat Management**
8
 
9
  #### **1. Create a New Chat**
10
+ **Endpoint:** `POST /chats`
11
  **Description:** Creates a new chat session.
12
  **Request Body:**
13
  ```json
14
  {
15
+ "user_id": 123,
16
+ "is_admin": false
17
  }
18
  ```
19
  **Response:**
20
  ```json
21
  {
22
  "chat_id": 456,
 
23
  "title": "New Chat",
24
+ "created_at": "2023-05-01T12:00:00Z",
25
+ "user_id": 123
26
  }
27
  ```
28
+ **Process Flow:**
29
+ 1. Create a new chat for the given user.
30
+ 2. Return the chat details.
31
 
32
  ---
33
 
 
54
  ]
55
  }
56
  ```
57
+ **Process Flow:**
58
+ 1. Retrieve chat details.
59
+ 2. Validate user access if `user_id` is provided.
60
+ 3. Fetch all messages in the chat.
61
+ 4. Return the chat with its messages.
62
 
63
  ---
64
 
65
  #### **3. List Recent Chats**
66
+ **Endpoint:** `GET /chats`
67
  **Description:** Retrieves a list of recent chats, optionally filtered by user ID.
68
  **Query Parameters:**
69
  - `user_id` (Optional for filtering by user)
70
+ - `limit` (Maximum number of chats, default: 10)
71
  - `offset` (For pagination, default: 0)
72
  **Response:**
73
  ```json
74
  [
75
  {
76
  "chat_id": 456,
 
77
  "title": "New Chat",
78
+ "created_at": "2023-05-01T12:00:00Z",
79
+ "user_id": 123
80
  }
81
  ]
82
  ```
83
+ **Process Flow:**
84
+ 1. Fetch recent chats.
85
+ 2. Apply filters and pagination.
86
+ 3. Return the list of chats.
87
 
88
  ---
89
 
90
  #### **4. Update a Chat**
91
  **Endpoint:** `PUT /chats/{chat_id}`
92
+ **Description:** Updates a chats title or user ID.
93
  **Path Parameter:** `chat_id` (ID of the chat to update)
94
  **Request Body:**
95
  ```json
 
107
  "user_id": 123
108
  }
109
  ```
110
+ **Process Flow:**
111
+ 1. Update the chat’s title or user ID.
112
+ 2. Return the updated details.
113
 
114
  ---
115
 
116
  #### **5. Delete a Chat**
117
  **Endpoint:** `DELETE /chats/{chat_id}`
118
+ **Description:** Deletes a chat and all its messages.
119
  **Path Parameter:** `chat_id` (ID of the chat to delete)
120
  **Query Parameter:** `user_id` (Optional for access control)
121
  **Response:**
122
  ```json
123
  {
124
+ "message": "Chat 456 deleted successfully"
 
125
  }
126
  ```
127
+ **Process Flow:**
128
+ 1. Validate if the chat exists and if the user has access.
129
+ 2. Delete the chat and associated messages.
130
+ 3. Return a success message.
131
+
132
+ ---
133
+
134
+ #### **6. Search Chats**
135
+ **Endpoint:** `GET /chats/search/`
136
+ **Description:** Searches chats based on a query string.
137
+ **Query Parameters:**
138
+ - `query` (Search term)
139
+ - `user_id` (Optional filter)
140
+ - `limit` (Max results)
141
+ **Response:**
142
+ ```json
143
+ [
144
+ {
145
+ "chat_id": 456,
146
+ "title": "Chat about machine learning",
147
+ "created_at": "2023-05-01T12:00:00Z",
148
+ "user_id": 123
149
+ }
150
+ ]
151
+ ```
152
+ **Process Flow:**
153
+ 1. Search chat titles and messages for the query.
154
+ 2. Filter by `user_id` if provided.
155
+ 3. Apply a result limit.
156
+ 4. Return the matching chats.
157
 
158
  ---
159
 
160
+ #### **7. Cleanup Empty Chats**
161
  **Endpoint:** `POST /chats/cleanup-empty`
162
  **Description:** Deletes empty chats for a user.
163
  **Request Body:**
 
173
  "message": "Deleted 5 empty chats"
174
  }
175
  ```
176
+ **Process Flow:**
177
+ 1. Identify chats with no messages.
178
+ 2. Delete those chats.
179
+ 3. Return the count of deleted chats.
180
 
181
  ---
182
 
183
  ### **Message Management**
184
 
185
+
186
+
187
+ #### **1. Send a Message and Get AI Response**
188
+ **Endpoint:** `POST /chats/{chat_id}/message`
189
+ **Description:** Sends a message and receives an AI-generated response.
190
  **Path Parameter:** `chat_id` (ID of the chat)
 
191
  **Request Body:**
192
+ **Process Flow:**
193
+ 1. Verify chat existence and user access.
194
+ 2. Store the user message.
195
+ 3. Generate an AI response via `ai_manager`.
196
+ 4. Track model usage metrics.
197
+ 5. Store the AI response.
198
+ 6. Update the chat title if it's the first or second message.
199
+ 7. Return both messages.
 
 
 
 
 
 
 
 
200
 
201
  ---
202
 
 
221
  "created_at": "2023-05-01T12:00:00Z"
222
  }
223
  ```
224
+ **Process Flow:**
225
+ 1. Check if a user with the email exists.
226
+ 2. Create a new user if not found.
227
+ 3. Return the user details.
228
+
229
+ ---
230
+
231
+ ### **Debugging**
232
+
233
+ #### **1. Test Model Usage Tracking**
234
+ **Endpoint:** `POST /chats/debug/test-model-usage`
235
+ **Query Parameters:**
236
+ - `model_name`: Model to test
237
+ - `user_id`: Optional
238
+ **Response:**
239
+ ```json
240
+ {
241
+ "success": true,
242
+ "message": "Model usage tracking test completed",
243
+ "response": "This is a test response",
244
+ "usage_recorded": {
245
+ "usage_id": 123,
246
+ "model_name": "gpt-4",
247
+ "tokens": 50,
248
+ "cost": 0.0005,
249
+ "timestamp": "2023-05-01T12:00:00Z"
250
+ }
251
+ }
252
+ ```
253
+ **Process Flow:**
254
+ 1. Generate a test prompt.
255
+ 2. Call AI manager.
256
+ 3. Fetch usage data.
257
+ 4. Return test results.
docs/routes/core.md ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # **Auto-Analyst API Documentation**
3
+
4
+ The core application routes are designed to manage the data and AI analysis capabilities of the Auto-Analyst application.
5
+
6
+ ## **1. Core Application Routes**
7
+ ### **Data Management**
8
+ - **POST /upload_dataframe**
9
+ **Uploads a dataset for analysis.**
10
+ **Request:**
11
+ - `file`: CSV file
12
+ - `name`: Dataset name
13
+ - `description`: Dataset description
14
+ - `session_id`: Session ID
15
+ **Response:**
16
+ ```json
17
+ { "message": "Dataframe uploaded successfully", "session_id": "abc123" }
18
+ ```
19
+ **Process Flow:**
20
+ - Read CSV file
21
+ - Create dataset description
22
+ - Update session with dataset
23
+ - Return success message
24
+
25
+ - **GET /api/default-dataset**
26
+ **Gets the default dataset.**
27
+ **Query Parameters:** `session_id`
28
+ **Response:**
29
+ ```json
30
+ {
31
+ "headers": ["column1", "column2", ...],
32
+ "rows": [[val1, val2, ...], ...],
33
+ "name": "Housing Dataset",
34
+ "description": "A comprehensive dataset containing housing information..."
35
+ }
36
+ ```
37
+ **Process Flow:**
38
+ - Reset session to use default dataset
39
+ - Format dataset preview
40
+ - Return formatted data
41
+
42
+ - **POST /reset-session**
43
+ **Resets session to default dataset.**
44
+ **Query Parameters:** `session_id`, `name` (optional), `description` (optional)
45
+ **Response:**
46
+ ```json
47
+ {
48
+ "message": "Session reset to default dataset",
49
+ "session_id": "abc123",
50
+ "dataset": "Housing.csv"
51
+ }
52
+ ```
53
+ **Process Flow:**
54
+ - Reset session
55
+ - Update dataset description (if provided)
56
+ - Return success message
57
+
58
+ ---
59
+
60
+ ### **2. AI Analysis**
61
+ - **POST /chat/{agent_name}**
62
+ **Processes a query using a specific AI agent.**
63
+ **Path Parameters:** `agent_name`
64
+ **Request Body:**
65
+ ```json
66
+ { "query": "Analyze the relationship between price and size" }
67
+ ```
68
+ **Query Parameters:** `session_id`, `user_id` (optional), `chat_id` (optional)
69
+ **Response:**
70
+ ```json
71
+ {
72
+ "agent_name": "data_viz_agent",
73
+ "query": "Analyze the relationship between price and size",
74
+ "response": "# Analysis\n\nThere appears to be a strong positive correlation...",
75
+ "session_id": "abc123"
76
+ }
77
+ ```
78
+ **Process Flow:**
79
+ - Get session state
80
+ - Validate dataset and agent
81
+ - Execute agent query
82
+ - Format and return response
83
+
84
+ - **POST /chat**
85
+ **Processes a query using multiple AI agents.**
86
+ **Request Body:**
87
+ ```json
88
+ { "query": "Analyze the housing data" }
89
+ ```
90
+ **Response:** *Streaming JSON objects:*
91
+ ```json
92
+ {"agent": "data_viz_agent", "content": "# Visualization\n\n...", "status": "success"}
93
+ {"agent": "statistical_analytics_agent", "content": "# Statistical Analysis\n\n...", "status": "success"}
94
+ ```
95
+ **Process Flow:**
96
+ - Get session state
97
+ - Validate dataset
98
+ - Generate AI analysis plan
99
+ - Execute with multiple agents and stream responses
100
+
101
+ - **POST /execute_code**
102
+ **Executes Python code and returns results.**
103
+ **Request Body:**
104
+ ```json
105
+ { "code": "import pandas as pd\nimport matplotlib.pyplot as plt\n..." }
106
+ ```
107
+ **Response:**
108
+ ```json
109
+ {
110
+ "output": "Execution successful",
111
+ "plotly_outputs": ["```plotly\n{\"data\": [...], \"layout\": {...}}\n```"]
112
+ }
113
+ ```
114
+ **Process Flow:**
115
+ - Validate dataset
116
+ - Execute code
117
+ - Capture output and plots
118
+ - Return results
119
+
120
+ ---
121
+
122
+ ### **3. Model Settings**
123
+ - **GET /api/model-settings**
124
+ **Fetches current model settings.**
125
+ **Response:**
126
+ ```json
127
+ {
128
+ "provider": "openai",
129
+ "model": "gpt-4o-mini",
130
+ "hasCustomKey": true,
131
+ "temperature": 1.0,
132
+ "maxTokens": 6000
133
+ }
134
+ ```
135
+ - **POST /settings/model**
136
+ **Updates model settings.**
137
+ **Request Body:**
138
+ ```json
139
+ {
140
+ "provider": "openai",
141
+ "model": "gpt-4",
142
+ "api_key": "sk-...",
143
+ "temperature": 0.7,
144
+ "max_tokens": 8000
145
+ }
146
+ ```
147
+ **Response:**
148
+ ```json
149
+ { "message": "Model settings updated successfully" }
150
+ ```
151
+ **Process Flow:**
152
+ - Update model settings
153
+ - Test configuration
154
+ - Return success or error
155
+
156
+ - **GET /agents**
157
+ **Lists available AI agents.**
158
+ **Response:**
159
+ ```json
160
+ {
161
+ "available_agents": ["data_viz_agent", "sk_learn_agent", "statistical_analytics_agent", "preprocessing_agent"],
162
+ "description": "List of available specialized agents that can be called using @agent_name"
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ### **4. Authentication & Session Management**
169
+ - **Session ID Sources:**
170
+ - Query parameter: `session_id`
171
+ - Header: `X-Session-ID`
172
+ - Auto-generated if not provided
173
+ - **Session State Includes:**
174
+ - Current dataset
175
+ - AI system instance
176
+ - Model configuration
177
+
178
+ #### **Admin Authentication**
179
+ - **API Key Sources:**
180
+ - Header: `X-Admin-API-Key`
181
+ - Query Parameter: `admin_api_key`
182
+ - **Validation:**
183
+ - Checked against `ADMIN_API_KEY` environment variable
184
+
185
+ ---
186
+
187
+ ### **5. AI Agents Integration**
188
+ - **Available Agents:**
189
+ - `data_viz_agent`: Creates data visualizations (Plotly)
190
+ - `sk_learn_agent`: ML analysis (Scikit-learn)
191
+ - `statistical_analytics_agent`: Statistical analysis (StatsModels)
192
+ - `preprocessing_agent`: Data transformation
193
+
194
+ **Integration Flow:**
195
+ - Agents are registered in `AVAILABLE_AGENTS`
196
+ - Queries are dispatched based on content
197
+ - Responses are streamed in Markdown
198
+
199
+ ---
200
+
201
+ ### **6. Real-time Updates via WebSockets**
202
+ - **Endpoints:**
203
+ - `/analytics/dashboard/realtime`
204
+ - `/analytics/realtime`
205
+ - **Connections stored in:**
206
+ - `active_dashboard_connections`
207
+ - `active_user_connections`
208
+ - **Broadcast Function:**
209
+ ```python
210
+ async def broadcast_dashboard_update(update_data: Dict[str, Any]):
211
+ for connection in active_dashboard_connections.copy():
212
+ try:
213
+ await connection.send_text(json.dumps(update_data))
214
+ except Exception:
215
+ active_dashboard_connections.remove(connection)
216
+ ```
217
+
218
+ ---
219
+
220
+ ### **7. Error Handling**
221
+ - **Try-Except Blocks:**
222
+ ```python
223
+ try:
224
+ return result
225
+ except ValueError as e:
226
+ raise HTTPException(status_code=404, detail=str(e))
227
+ except Exception as e:
228
+ logger.error(f"Error: {str(e)}")
229
+ raise HTTPException(status_code=500, detail=f"Failed: {str(e)}")
230
+ ```
231
+ - **HTTP Exception Types:**
232
+ - `400`: Bad Request
233
+ - `401`: Unauthorized
234
+ - `403`: Forbidden
235
+ - `404`: Not Found
236
+ - `500`: Internal Server Error
237
+
238
+ - **Logging Setup:**
239
+ ```python
240
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
241
+ logger = logging.getLogger(__name__)
242
+ ```
docs/schema.md ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Entity-Relationship Diagram
2
+
3
+ ```mermaid
4
+ erDiagram
5
+ User ||--o{ Chat : has
6
+ User ||--o{ ModelUsage : has
7
+ Chat ||--o{ Message : has
8
+ Chat ||--o{ ModelUsage : linked_to
9
+
10
+ User {
11
+ Integer user_id PK
12
+ String username "Unique, Not Null"
13
+ String email "Unique, Not Null"
14
+ DateTime created_at "Default=datetime.utcnow"
15
+ }
16
+
17
+ Chat {
18
+ Integer chat_id PK
19
+ Integer user_id FK
20
+ String title "Default='New Chat'"
21
+ DateTime created_at "Default=datetime.utcnow"
22
+ }
23
+
24
+ Message {
25
+ Integer message_id PK
26
+ Integer chat_id FK
27
+ String sender "Not Null"
28
+ Text content "Not Null"
29
+ DateTime timestamp "Default=datetime.utcnow"
30
+ }
31
+
32
+ ModelUsage {
33
+ Integer usage_id PK
34
+ Integer user_id FK
35
+ Integer chat_id FK
36
+ String model_name "Not Null"
37
+ String provider "Not Null"
38
+ Integer prompt_tokens "Default=0"
39
+ Integer completion_tokens "Default=0"
40
+ Integer total_tokens "Default=0"
41
+ Integer query_size "Default=0"
42
+ Integer response_size "Default=0"
43
+ Float cost "Default=0.0"
44
+ DateTime timestamp "Default=datetime.utcnow"
45
+ Boolean is_streaming "Default=False"
46
+ Integer request_time_ms "Default=0"
47
+ }
48
+ ```
docs/{system/shared_dataframe.md → shared_dataframe.md} RENAMED
File without changes
docs/system/database-schema.md DELETED
@@ -1,289 +0,0 @@
1
- # Auto-Analyst Database Schema Documentation
2
-
3
- ## 📋 Overview
4
-
5
- The Auto-Analyst backend uses a relational database schema designed for scalability and data integrity. The schema supports both **SQLite** (development) and **PostgreSQL** (production) databases through SQLAlchemy ORM.
6
-
7
- ### **Database Features**
8
- - **User Management** - Authentication and user data
9
- - **Chat System** - Conversation sessions and message history
10
- - **AI Model Tracking** - Usage analytics and cost monitoring
11
- - **Code Execution** - Code generation and execution tracking
12
- - **Agent Templates** - Customizable AI agent configurations
13
- - **Deep Analysis** - Multi-step analysis reports and results
14
- - **User Feedback** - Rating and feedback system
15
-
16
- ---
17
-
18
- ## 🗄️ Database Tables
19
-
20
- ### **1. Users Table (`users`)**
21
-
22
- **Purpose**: Core user authentication and profile management
23
-
24
- | Column | Type | Constraints | Description |
25
- |--------|------|-------------|-------------|
26
- | `user_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique user identifier |
27
- | `username` | `STRING` | UNIQUE, NOT NULL | User's display name |
28
- | `email` | `STRING` | UNIQUE, NOT NULL | User's email address |
29
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Account creation timestamp |
30
-
31
- **Relationships:**
32
- - **One-to-Many**: `chats` (User → Chat sessions)
33
- - **One-to-Many**: `usage_records` (User → Model usage tracking)
34
- - **One-to-Many**: `deep_analysis_reports` (User → Analysis reports)
35
- - **One-to-Many**: `template_preferences` (User → Agent preferences)
36
-
37
- ---
38
-
39
- ### **2. Chats Table (`chats`)**
40
-
41
- **Purpose**: Conversation sessions and chat organization
42
-
43
- | Column | Type | Constraints | Description |
44
- |--------|------|-------------|-------------|
45
- | `chat_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique chat session identifier |
46
- | `user_id` | `INTEGER` | FOREIGN KEY → `users.user_id`, CASCADE DELETE | Chat owner (nullable for anonymous) |
47
- | `title` | `STRING` | DEFAULT: 'New Chat' | Human-readable chat title |
48
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Chat creation timestamp |
49
-
50
- **Relationships:**
51
- - **Many-to-One**: `user` (Chat → User)
52
- - **One-to-Many**: `messages` (Chat → Messages)
53
- - **One-to-Many**: `usage_records` (Chat → Model usage)
54
-
55
- ---
56
-
57
- ### **3. Messages Table (`messages`)**
58
-
59
- **Purpose**: Individual messages within chat conversations
60
-
61
- | Column | Type | Constraints | Description |
62
- |--------|------|-------------|-------------|
63
- | `message_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique message identifier |
64
- | `chat_id` | `INTEGER` | FOREIGN KEY → `chats.chat_id`, CASCADE DELETE | Parent chat session |
65
- | `sender` | `STRING` | NOT NULL | Message sender: 'user' or 'ai' |
66
- | `content` | `TEXT` | NOT NULL | Message content (text/markdown) |
67
- | `timestamp` | `DATETIME` | DEFAULT: UTC NOW | Message creation time |
68
-
69
- **Relationships:**
70
- - **Many-to-One**: `chat` (Message → Chat)
71
- - **One-to-One**: `feedback` (Message → Feedback)
72
-
73
- ---
74
-
75
- ### **4. Model Usage Table (`model_usage`)**
76
-
77
- **Purpose**: AI model usage tracking for analytics and billing
78
-
79
- | Column | Type | Constraints | Description |
80
- |--------|------|-------------|-------------|
81
- | `usage_id` | `INTEGER` | PRIMARY KEY | Unique usage record identifier |
82
- | `user_id` | `INTEGER` | FOREIGN KEY → `users.user_id`, SET NULL | User who triggered the usage |
83
- | `chat_id` | `INTEGER` | FOREIGN KEY → `chats.chat_id`, SET NULL | Associated chat session |
84
- | `model_name` | `STRING(100)` | NOT NULL | AI model used (e.g., 'gpt-4o-mini') |
85
- | `provider` | `STRING(50)` | NOT NULL | Model provider ('openai', 'anthropic', etc.) |
86
- | `prompt_tokens` | `INTEGER` | DEFAULT: 0 | Input tokens consumed |
87
- | `completion_tokens` | `INTEGER` | DEFAULT: 0 | Output tokens generated |
88
- | `total_tokens` | `INTEGER` | DEFAULT: 0 | Total tokens (input + output) |
89
- | `query_size` | `INTEGER` | DEFAULT: 0 | Query size in characters |
90
- | `response_size` | `INTEGER` | DEFAULT: 0 | Response size in characters |
91
- | `cost` | `FLOAT` | DEFAULT: 0.0 | Cost in USD for this usage |
92
- | `timestamp` | `DATETIME` | DEFAULT: UTC NOW | Usage timestamp |
93
- | `is_streaming` | `BOOLEAN` | DEFAULT: FALSE | Whether response was streamed |
94
- | `request_time_ms` | `INTEGER` | DEFAULT: 0 | Request processing time (milliseconds) |
95
-
96
- **Relationships:**
97
- - **Many-to-One**: `user` (Usage → User)
98
- - **Many-to-One**: `chat` (Usage → Chat)
99
-
100
- ---
101
-
102
- ### **5. Code Executions Table (`code_executions`)**
103
-
104
- **Purpose**: Track code generation and execution attempts
105
-
106
- | Column | Type | Constraints | Description |
107
- |--------|------|-------------|-------------|
108
- | `execution_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique execution identifier |
109
- | `message_id` | `INTEGER` | FOREIGN KEY → `messages.message_id`, CASCADE DELETE | Associated message |
110
- | `chat_id` | `INTEGER` | FOREIGN KEY → `chats.chat_id`, CASCADE DELETE | Parent chat session |
111
- | `user_id` | `INTEGER` | FOREIGN KEY → `users.user_id`, SET NULL | User who triggered execution |
112
- | `initial_code` | `TEXT` | NULLABLE | First version of generated code |
113
- | `latest_code` | `TEXT` | NULLABLE | Most recent code version |
114
- | `is_successful` | `BOOLEAN` | DEFAULT: FALSE | Whether execution succeeded |
115
- | `output` | `TEXT` | NULLABLE | Execution output (including errors) |
116
- | `model_provider` | `STRING(50)` | NULLABLE | AI model provider used |
117
- | `model_name` | `STRING(100)` | NULLABLE | AI model name used |
118
- | `failed_agents` | `TEXT` | NULLABLE | JSON list of failed agent names |
119
- | `error_messages` | `TEXT` | NULLABLE | JSON map of error messages by agent |
120
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Execution creation time |
121
- | `updated_at` | `DATETIME` | DEFAULT: UTC NOW, ON UPDATE | Last update timestamp |
122
-
123
- ---
124
-
125
- ### **6. Message Feedback Table (`message_feedback`)**
126
-
127
- **Purpose**: User feedback and model settings for messages
128
-
129
- | Column | Type | Constraints | Description |
130
- |--------|------|-------------|-------------|
131
- | `feedback_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique feedback identifier |
132
- | `message_id` | `INTEGER` | FOREIGN KEY → `messages.message_id`, CASCADE DELETE | Associated message |
133
- | `rating` | `INTEGER` | NULLABLE | Star rating (1-5 scale) |
134
- | `model_name` | `STRING(100)` | NULLABLE | Model used for this message |
135
- | `model_provider` | `STRING(50)` | NULLABLE | Model provider used |
136
- | `temperature` | `FLOAT` | NULLABLE | Temperature setting used |
137
- | `max_tokens` | `INTEGER` | NULLABLE | Max tokens setting used |
138
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Feedback creation time |
139
- | `updated_at` | `DATETIME` | DEFAULT: UTC NOW, ON UPDATE | Last update timestamp |
140
-
141
- **Relationships:**
142
- - **One-to-One**: `message` (Feedback ↔ Message)
143
-
144
- ---
145
-
146
- ### **7. Deep Analysis Reports Table (`deep_analysis_reports`)**
147
-
148
- **Purpose**: Store comprehensive multi-agent analysis reports
149
-
150
- | Column | Type | Constraints | Description |
151
- |--------|------|-------------|-------------|
152
- | `report_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique report identifier |
153
- | `report_uuid` | `STRING(100)` | UNIQUE, NOT NULL | Frontend-generated UUID |
154
- | `user_id` | `INTEGER` | FOREIGN KEY → `users.user_id`, CASCADE DELETE | Report owner |
155
- | `goal` | `TEXT` | NOT NULL | Analysis objective/question |
156
- | `status` | `STRING(20)` | NOT NULL, DEFAULT: 'pending' | Status: 'pending', 'running', 'completed', 'failed' |
157
- | `start_time` | `DATETIME` | DEFAULT: UTC NOW | Analysis start time |
158
- | `end_time` | `DATETIME` | NULLABLE | Analysis completion time |
159
- | `duration_seconds` | `INTEGER` | NULLABLE | Total analysis duration |
160
- | `deep_questions` | `TEXT` | NULLABLE | Generated analytical questions |
161
- | `deep_plan` | `TEXT` | NULLABLE | Analysis execution plan |
162
- | `summaries` | `JSON` | NULLABLE | Array of analysis summaries |
163
- | `analysis_code` | `TEXT` | NULLABLE | Generated Python code |
164
- | `plotly_figures` | `JSON` | NULLABLE | Array of Plotly figure data |
165
- | `synthesis` | `JSON` | NULLABLE | Array of synthesis insights |
166
- | `final_conclusion` | `TEXT` | NULLABLE | Final analysis conclusion |
167
- | `html_report` | `TEXT` | NULLABLE | Complete HTML report |
168
- | `progress_percentage` | `INTEGER` | DEFAULT: 0 | Progress percentage (0-100) |
169
- | `total_tokens_used` | `INTEGER` | DEFAULT: 0 | Total tokens consumed |
170
- | `estimated_cost` | `FLOAT` | DEFAULT: 0.0 | Estimated cost in USD |
171
- | `credits_consumed` | `INTEGER` | DEFAULT: 0 | Credits deducted for analysis |
172
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Report creation time |
173
- | `updated_at` | `DATETIME` | DEFAULT: UTC NOW, ON UPDATE | Last update timestamp |
174
-
175
- **Relationships:**
176
- - **Many-to-One**: `user` (Report → User)
177
-
178
- ---
179
-
180
- ### **8. Agent Templates Table (`agent_templates`)**
181
-
182
- **Purpose**: Store predefined AI agent configurations
183
-
184
- | Column | Type | Constraints | Description |
185
- |--------|------|-------------|-------------|
186
- | `template_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique template identifier |
187
- | `template_name` | `STRING(100)` | UNIQUE, NOT NULL | Internal template name |
188
- | `display_name` | `STRING(200)` | NULLABLE | User-friendly display name |
189
- | `description` | `TEXT` | NOT NULL | Template description |
190
- | `prompt_template` | `TEXT` | NOT NULL | Agent behavior instructions |
191
- | `icon_url` | `STRING(500)` | NULLABLE | Template icon URL |
192
- | `category` | `STRING(50)` | NULLABLE | Template category |
193
- | `is_premium_only` | `BOOLEAN` | DEFAULT: FALSE | Requires premium subscription |
194
- | `variant_type` | `STRING(20)` | DEFAULT: 'individual' | 'planner', 'individual', or 'both' |
195
- | `is_active` | `BOOLEAN` | DEFAULT: TRUE | Template is active/available |
196
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Template creation time |
197
-
198
- **Relationships:**
199
- - **One-to-Many**: `user_preferences` (Template → User preferences)
200
-
201
- ---
202
-
203
- ### **9. User Template Preferences Table (`user_template_preferences`)**
204
-
205
- **Purpose**: Track user preferences and usage for agent templates
206
-
207
- | Column | Type | Constraints | Description |
208
- |--------|------|-------------|-------------|
209
- | `preference_id` | `INTEGER` | PRIMARY KEY, AUTO INCREMENT | Unique preference identifier |
210
- | `user_id` | `INTEGER` | FOREIGN KEY → `users.user_id`, CASCADE DELETE | User who owns preference |
211
- | `template_id` | `INTEGER` | FOREIGN KEY → `agent_templates.template_id`, CASCADE DELETE | Associated template |
212
- | `is_enabled` | `BOOLEAN` | DEFAULT: TRUE | Whether user has template enabled |
213
- | `usage_count` | `INTEGER` | DEFAULT: 0 | Number of times user used template |
214
- | `last_used_at` | `DATETIME` | NULLABLE | Last time user used template |
215
- | `created_at` | `DATETIME` | DEFAULT: UTC NOW | Preference creation time |
216
-
217
- **Relationships:**
218
- - **Many-to-One**: `user` (Preference → User)
219
- - **Many-to-One**: `template` (Preference → Template)
220
-
221
- **Constraints:**
222
- - **Unique**: `(user_id, template_id)` - One preference per user per template
223
-
224
- ---
225
-
226
- ## 🔗 Entity Relationship Diagram
227
-
228
- ```
229
- Users (1) ──────────── (Many) Chats
230
- │ │
231
- │ ├── (Many) Messages
232
- │ │ │
233
- │ │ └── (1) MessageFeedback
234
- │ │
235
- │ └── (Many) CodeExecutions
236
-
237
- ├── (Many) ModelUsage
238
-
239
- ├── (Many) DeepAnalysisReports
240
-
241
- └── (Many) UserTemplatePreferences
242
-
243
- └── (Many) AgentTemplates
244
- ```
245
-
246
- ---
247
-
248
- ## 📊 Database Performance
249
-
250
- ### **Optimized Indexes**
251
-
252
- ```sql
253
- -- High-performance queries
254
- CREATE INDEX idx_messages_chat_timestamp ON messages(chat_id, timestamp DESC);
255
- CREATE INDEX idx_model_usage_user_time ON model_usage(user_id, timestamp DESC);
256
- CREATE INDEX idx_model_usage_model_time ON model_usage(model_name, timestamp DESC);
257
- CREATE INDEX idx_reports_user_time ON deep_analysis_reports(user_id, created_at DESC);
258
- ```
259
-
260
- ### **Cascade Deletion Rules**
261
-
262
- | Parent → Child | Rule | Description |
263
- |----------------|------|-------------|
264
- | `users` → `chats` | CASCADE | Delete all user chats when user deleted |
265
- | `chats` → `messages` | CASCADE | Delete all chat messages when chat deleted |
266
- | `messages` → `feedback` | CASCADE | Delete feedback when message deleted |
267
- | `users` → `model_usage` | SET NULL | Keep usage records for analytics |
268
-
269
- ---
270
-
271
- ## 🛡️ Security & Maintenance
272
-
273
- ### **Data Protection**
274
- - User data isolated by `user_id`
275
- - Sensitive fields require encryption in production
276
- - Automatic cleanup of anonymous data after 90 days
277
-
278
- ### **Regular Maintenance**
279
- ```sql
280
- -- Clean old anonymous chats
281
- DELETE FROM chats WHERE user_id IS NULL AND created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
282
-
283
- -- Update statistics for query optimization
284
- ANALYZE users, chats, messages, model_usage;
285
- ```
286
-
287
- ---
288
-
289
- This schema supports the full Auto-Analyst application with optimized performance, data integrity, and scalability for both development and production environments.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/troubleshooting/troubleshooting.md DELETED
@@ -1,537 +0,0 @@
1
- # Auto-Analyst Backend Troubleshooting Guide
2
-
3
- ## 🚨 Common Startup Issues
4
-
5
- ### 1. **Database Connection Problems**
6
-
7
- #### Problem: Database connection failed
8
- ```
9
- ❌ sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
10
- ```
11
-
12
- **Solutions:**
13
- 1. **Initialize Database**:
14
- ```bash
15
- python -c "
16
- from src.db.init_db import init_db
17
- init_db()
18
- print('✅ Database initialized')
19
- "
20
- ```
21
-
22
- 2. **Check Database File Permissions**:
23
- ```bash
24
- # For SQLite
25
- ls -la auto_analyst.db
26
- chmod 666 auto_analyst.db # If needed
27
- ```
28
-
29
- 3. **Verify DATABASE_URL**:
30
- ```bash
31
- # Check .env file
32
- cat .env | grep DATABASE_URL
33
-
34
- # For PostgreSQL (production)
35
- DATABASE_URL=postgresql://user:password@host:port/database
36
-
37
- # For SQLite (development)
38
- DATABASE_URL=sqlite:///./auto_analyst.db
39
- ```
40
-
41
- #### Problem: PostgreSQL connection issues
42
- ```
43
- ❌ psycopg2.OperationalError: FATAL: database "auto_analyst" does not exist
44
- ```
45
-
46
- **Solutions:**
47
- 1. **Create Database**:
48
- ```sql
49
- -- Connect to PostgreSQL
50
- psql -h localhost -U postgres
51
- CREATE DATABASE auto_analyst;
52
- \q
53
- ```
54
-
55
- 2. **Update Connection String**:
56
- ```env
57
- DATABASE_URL=postgresql://username:password@localhost:5432/auto_analyst
58
- ```
59
-
60
- ### 2. **Agent Template Loading Issues**
61
-
62
- #### Problem: No agents found
63
- ```
64
- ❌ RuntimeError: No agents loaded for user. Cannot proceed with analysis.
65
- ```
66
-
67
- **Solutions:**
68
- 1. **Initialize Default Agents**:
69
- ```python
70
- python -m scripts.populate_agent_templates
71
- print('✅ Default agents initialized')
72
- "
73
- ```
74
-
75
- 2. **Check Agent Templates in Database**:
76
- ```python
77
- python -c "
78
- from src.db.init_db import session_factory
79
- from src.db.schemas.models import AgentTemplate
80
- session = session_factory()
81
- templates = session.query(AgentTemplate).all()
82
- print(f'Found {len(templates)} templates:')
83
- for t in templates:
84
- print(f' - {t.template_name}: {t.is_active}')
85
- session.close()
86
- "
87
- ```
88
-
89
- 3. **Populate Templates from Config**:
90
- ```bash
91
- python scripts/populate_agent_templates.py
92
- ```
93
-
94
- ### 3. **API Key Issues**
95
-
96
- #### Problem: Missing API keys
97
- ```
98
- ❌ AuthenticationError: Invalid API key provided
99
- ```
100
-
101
- **Solutions:**
102
- 1. **Check Environment Variables**:
103
- ```bash
104
- # Verify API keys are set
105
- echo $ANTHROPIC_API_KEY
106
- echo $OPENAI_API_KEY
107
-
108
- # Or check .env file
109
- cat .env | grep API_KEY
110
- ```
111
-
112
- 2. **Add Missing Keys**:
113
- ```env
114
- # Add to .env file
115
- ANTHROPIC_API_KEY=sk-ant-api03-...
116
- OPENAI_API_KEY=sk-...
117
- ADMIN_API_KEY=your_admin_key_here
118
- ```
119
-
120
- 3. **Test API Key Validity**:
121
- ```python
122
- python -c "
123
- import os
124
- from anthropic import Anthropic
125
- client = Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
126
- try:
127
- # Test call
128
- response = client.messages.create(
129
- model='claude-3-sonnet-20241022',
130
- max_tokens=10,
131
- messages=[{'role': 'user', 'content': 'Hello'}]
132
- )
133
- print('✅ Anthropic API key valid')
134
- except Exception as e:
135
- print(f'❌ Anthropic API key invalid: {e}')
136
- "
137
- ```
138
-
139
- ## 🤖 Agent System Issues
140
-
141
- ### 1. **Agent Not Found Errors**
142
-
143
- #### Problem: Specific agent not available
144
- ```
145
- ❌ KeyError: 'custom_agent' not found in loaded agents
146
- ```
147
-
148
- **Solutions:**
149
- 1. **Check Available Agents**:
150
- ```python
151
- python -c "
152
- from src.agents.agents import load_user_enabled_templates_from_db
153
- from src.db.init_db import session_factory
154
- session = session_factory()
155
- agents = load_user_enabled_templates_from_db('test_user', session)
156
- print('Available agents:', list(agents.keys()))
157
- session.close()
158
- "
159
- ```
160
-
161
- 2. **Verify Agent Template Exists**:
162
- ```python
163
- python -c "
164
- from src.db.init_db import session_factory
165
- from src.db.schemas.models import AgentTemplate
166
- session = session_factory()
167
- agent = session.query(AgentTemplate).filter_by(template_name='custom_agent').first()
168
- if agent:
169
- print(f'Agent found: {agent.display_name}, Active: {agent.is_active}')
170
- else:
171
- print('Agent not found in database')
172
- session.close()
173
- "
174
- ```
175
-
176
- 3. **Add Missing Agent Template**:
177
- ```python
178
- # Add to agents_config.json or use database insertion
179
- python scripts/populate_agent_templates.py
180
- ```
181
-
182
- ### 2. **Deep Analysis Failures**
183
-
184
- #### Problem: Deep analysis stops unexpectedly
185
- ```
186
- ❌ DeepAnalysisError: Agent execution failed at step 3
187
- ```
188
-
189
- **Solutions:**
190
- 1. **Check Agent Configuration**:
191
- ```python
192
- # Verify user has required agents enabled
193
- python -c "
194
- from src.agents.deep_agents import get_user_enabled_agent_names
195
- from src.db.init_db import session_factory
196
- session = session_factory()
197
- agents = get_user_enabled_agent_names('test_user', session)
198
- required = ['preprocessing_agent', 'statistical_analytics_agent', 'sk_learn_agent', 'data_viz_agent']
199
- print('Required agents:', required)
200
- print('Available agents:', agents)
201
- print('Missing:', [a for a in required if a not in agents])
202
- session.close()
203
- "
204
- ```
205
-
206
- 2. **Increase Timeout Settings**:
207
- ```python
208
- # In deep_agents.py, increase timeout values
209
- timeout = 300 # Increase from default
210
- ```
211
-
212
- 3. **Check Dataset Size**:
213
- ```python
214
- # Reduce dataset size for complex analysis
215
- df_sample = df.sample(n=1000) # Use sample for testing
216
- ```
217
-
218
- ## ⚡ Code Execution Problems
219
-
220
- ### 1. **Code Execution Timeouts**
221
-
222
- #### Problem: Code execution takes too long
223
- ```
224
- ❌ TimeoutError: Code execution exceeded 120 seconds
225
- ```
226
-
227
- **Solutions:**
228
- 1. **Optimize Generated Code**:
229
- - Use data sampling for large datasets
230
- - Simplify analysis requirements
231
- - Use sampling for large datasets
232
-
233
- 2. **Check Resource Usage**:
234
- ```python
235
- import psutil
236
- print(f"Memory usage: {psutil.virtual_memory().percent}%")
237
- print(f"CPU usage: {psutil.cpu_percent()}%")
238
- ```
239
-
240
- 3. **Increase Timeout Settings**:
241
- ```python
242
- # In clean_and_store_code function
243
- future.result(timeout=600) # Increase timeout to 10 minutes
244
- ```
245
-
246
- #### Problem: Import Errors in Generated Code
247
- ```
248
- ❌ ModuleNotFoundError: No module named 'some_library'
249
- ```
250
-
251
- **Solutions:**
252
- 1. **Check Available Libraries**:
253
- ```python
254
- # Available in execution environment:
255
- import pandas as pd
256
- import numpy as np
257
- import plotly.express as px
258
- import plotly.graph_objects as go
259
- import sklearn
260
- import statsmodels.api as sm
261
- ```
262
-
263
- 2. **Add Missing Dependencies**:
264
- ```bash
265
- pip install missing_library
266
- ```
267
-
268
- 3. **Update Execution Environment**:
269
- ```python
270
- # In clean_and_store_code function
271
- exec_globals.update({
272
- 'new_library': __import__('new_library')
273
- })
274
- ```
275
-
276
- ### 4. **Database Issues**
277
-
278
- #### Problem: Migration Errors
279
- ```
280
- ❌ alembic.util.exc.CommandError: Can't locate revision identified by 'xyz'
281
- ```
282
-
283
- **Solutions:**
284
- 1. **Reset Migration History**:
285
- ```bash
286
- # Delete migration files (except __init__.py)
287
- rm migrations/versions/*.py
288
-
289
- # Create new initial migration
290
- alembic revision --autogenerate -m "initial migration"
291
- alembic upgrade head
292
- ```
293
-
294
- 2. **Force Migration**:
295
- ```bash
296
- # Mark current state as up-to-date
297
- alembic stamp head
298
- ```
299
-
300
- 3. **Recreate Database**:
301
- ```bash
302
- # For SQLite (development)
303
- rm auto_analyst.db
304
- python -c "from src.db.init_db import init_db; init_db()"
305
- ```
306
-
307
- #### Problem: Constraint Violations
308
- ```
309
- ❌ IntegrityError: UNIQUE constraint failed
310
- ```
311
-
312
- **Solutions:**
313
- 1. **Check Existing Records**:
314
- ```python
315
- from src.db.init_db import session_factory
316
- from src.db.schemas.models import AgentTemplate
317
-
318
- session = session_factory()
319
- templates = session.query(AgentTemplate).all()
320
- for t in templates:
321
- print(f"{t.template_name}: {t.template_id}")
322
- session.close()
323
- ```
324
-
325
- 2. **Clean Duplicate Data**:
326
- ```bash
327
- python -c "
328
- from src.db.init_db import session_factory
329
- from src.db.schemas.models import AgentTemplate
330
- session = session_factory()
331
- # Remove duplicates based on template_name
332
- seen = set()
333
- for template in session.query(AgentTemplate).all():
334
- if template.template_name in seen:
335
- session.delete(template)
336
- else:
337
- seen.add(template.template_name)
338
- session.commit()
339
- session.close()
340
- "
341
- ```
342
-
343
- ### 5. **Authentication and Authorization Issues**
344
-
345
- #### Problem: Unauthorized Access
346
- ```
347
- ❌ 401 Unauthorized: Invalid session
348
- ```
349
-
350
- **Solutions:**
351
- 1. **Check Session ID**:
352
- ```python
353
- # Ensure session_id is provided in request
354
- headers = {"X-Session-ID": "your_session_id"}
355
- # Or as query parameter: ?session_id=your_session_id
356
- ```
357
-
358
- 2. **Create Valid Session**:
359
- ```bash
360
- curl -X POST "http://localhost:8000/session_info" \
361
- -H "Content-Type: application/json"
362
- ```
363
-
364
- 3. **Verify Admin API Key**:
365
- ```bash
366
- curl -X GET "http://localhost:8000/analytics/usage" \
367
- -H "X-API-Key: your_admin_key"
368
- ```
369
-
370
- ### 6. **Performance Issues**
371
-
372
- #### Problem: Slow Response Times
373
- ```
374
- ⚠️ Request taking longer than expected
375
- ```
376
-
377
- **Solutions:**
378
- 1. **Enable Database Connection Pooling**:
379
- ```python
380
- # In init_db.py
381
- engine = create_engine(
382
- DATABASE_URL,
383
- poolclass=QueuePool,
384
- pool_size=10,
385
- max_overflow=20
386
- )
387
- ```
388
-
389
- 2. **Optimize Database Queries**:
390
- ```python
391
- # Use eager loading for relationships
392
- session.query(User).options(joinedload(User.chats)).all()
393
- ```
394
-
395
- 3. **Add Response Caching**:
396
- ```python
397
- # Use local caching for expensive operations
398
- @lru_cache(maxsize=100)
399
- def expensive_operation(data):
400
- return result
401
- ```
402
-
403
- #### Problem: Memory Usage High
404
- ```
405
- ⚠️ Memory usage above 80%
406
- ```
407
-
408
- **Solutions:**
409
- 1. **Optimize DataFrame Operations**:
410
- ```python
411
- # Use chunking for large datasets
412
- for chunk in pd.read_csv('file.csv', chunksize=1000):
413
- process_chunk(chunk)
414
- ```
415
-
416
- 2. **Clear Unused Variables**:
417
- ```python
418
- # In code execution
419
- del large_dataframe
420
- import gc
421
- gc.collect()
422
- ```
423
-
424
- 3. **Monitor Memory Usage**:
425
- ```python
426
- import psutil
427
- import logging
428
-
429
- memory_percent = psutil.virtual_memory().percent
430
- if memory_percent > 80:
431
- logging.warning(f"High memory usage: {memory_percent}%")
432
- ```
433
-
434
- ## 🔧 Debugging Tools and Commands
435
-
436
- ### Health Check Commands
437
-
438
- ```bash
439
- # Test basic connectivity
440
- curl http://localhost:8000/health
441
-
442
- # Check database status
443
- python -c "
444
- from src.db.init_db import session_factory
445
- try:
446
- session = session_factory()
447
- session.execute('SELECT 1')
448
- print('✅ Database connection OK')
449
- session.close()
450
- except Exception as e:
451
- print(f'❌ Database error: {e}')
452
- "
453
-
454
- # Verify agent templates
455
- python -c "
456
- from src.db.init_db import session_factory
457
- from src.db.schemas.models import AgentTemplate
458
- session = session_factory()
459
- count = session.query(AgentTemplate).count()
460
- print(f'Agent templates in database: {count}')
461
- session.close()
462
- "
463
- ```
464
-
465
- ### Performance Monitoring
466
-
467
- ```python
468
- # Memory and CPU monitoring
469
- import psutil
470
- import time
471
-
472
- def monitor_system():
473
- while True:
474
- cpu = psutil.cpu_percent(interval=1)
475
- memory = psutil.virtual_memory()
476
- print(f"CPU: {cpu}% | Memory: {memory.percent}% | Available: {memory.available // 1024 // 1024}MB")
477
- time.sleep(5)
478
-
479
- # Run monitoring
480
- monitor_system()
481
- ```
482
-
483
- ### Database Inspection
484
-
485
- ```python
486
- # Inspect database tables
487
- from src.db.init_db import session_factory
488
- from src.db.schemas.models import *
489
-
490
- session = session_factory()
491
-
492
- # Count records in each table
493
- tables = [User, Chat, Message, AgentTemplate, UserTemplatePreference, DeepAnalysisReport]
494
- for table in tables:
495
- count = session.query(table).count()
496
- print(f"{table.__name__}: {count} records")
497
-
498
- session.close()
499
- ```
500
-
501
- ### Log Analysis
502
-
503
- ```bash
504
- # View recent logs
505
- tail -f logs/app.log
506
-
507
- # Search for errors
508
- grep "ERROR" logs/app.log | tail -20
509
-
510
- # Search for specific issues
511
- grep -i "agent" logs/app.log | grep -i "error"
512
- ```
513
-
514
- ## 🚀 Performance Optimization Tips
515
-
516
- ### Database Optimization
517
-
518
- 1. **Use Indexes**: Ensure frequently queried columns have indexes
519
- 2. **Query Optimization**: Use `joinedload` for relationships
520
- 3. **Connection Pooling**: Configure appropriate pool sizes
521
- 4. **Batch Operations**: Use bulk operations for multiple records
522
-
523
- ### Agent Performance
524
-
525
- 1. **Async Execution**: Use async patterns for concurrent operations
526
- 2. **Result Caching**: Cache expensive computations
527
- 3. **Memory Management**: Clean up large objects after use
528
- 4. **Code Optimization**: Simplify generated code for better performance
529
-
530
- ### System Monitoring
531
-
532
- 1. **Resource Tracking**: Monitor CPU, memory, and disk usage
533
- 2. **Error Monitoring**: Set up alerting for critical errors
534
- 3. **Performance Metrics**: Track response times and throughput
535
- 4. **Usage Analytics**: Monitor feature usage and optimization opportunities
536
-
537
- This troubleshooting guide covers the most common issues you'll encounter with the Auto-Analyst backend. For additional help, check the system logs and use the debugging tools provided.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
entrypoint_local.sh DELETED
@@ -1,175 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Entrypoint script for Auto-Analyst backend
4
- # This script safely initializes the database and starts the application
5
- # SAFE for PostgreSQL/RDS - only modifies SQLite databases
6
-
7
- set -e # Exit on any error
8
-
9
- echo "🚀 Starting Auto-Analyst Backend..."
10
-
11
- # Function to run safe database initialization
12
- init_production_database() {
13
- echo "🔧 Running SAFE database initialization..."
14
-
15
- # Run the safe initialization script
16
- python scripts/init_production_db.py
17
-
18
- # Don't fail if database initialization has issues - let app try to start
19
- if [ $? -eq 0 ]; then
20
- echo "✅ Database initialization completed successfully"
21
- else
22
- echo "⚠️ Database initialization had issues, but continuing..."
23
- echo "📋 App will start but some features may not work properly"
24
- fi
25
- }
26
-
27
- # Function to verify basic app imports work
28
- verify_app_imports() {
29
- echo "🔍 Verifying application imports..."
30
- python -c "
31
- try:
32
- from app import app
33
- print('✅ Main application imports successful')
34
- except Exception as e:
35
- print(f'❌ Application import failed: {e}')
36
- exit(1)
37
- " || {
38
- echo "❌ Critical application import failure - cannot start"
39
- exit 1
40
- }
41
- }
42
-
43
- # Function to verify database connectivity (non-failing)
44
- verify_database_connectivity() {
45
- echo "🔗 Testing database connectivity..."
46
- python -c "
47
- try:
48
- from src.db.init_db import get_session, is_postgres_db
49
- from src.db.schemas.models import AgentTemplate
50
-
51
- db_type = 'PostgreSQL/RDS' if is_postgres_db() else 'SQLite'
52
- print(f'🗄️ Database type: {db_type}')
53
-
54
- session = get_session()
55
-
56
- # Try to query templates if table exists
57
- try:
58
- template_count = session.query(AgentTemplate).count()
59
- print(f'✅ Database connected. Found {template_count} templates.')
60
- except Exception as table_error:
61
- print(f'⚠️ Database connected but template table issue: {table_error}')
62
- print('📋 Template functionality may not work')
63
- finally:
64
- session.close()
65
-
66
- except Exception as e:
67
- print(f'⚠️ Database connectivity issue: {e}')
68
- print('📋 App will start but database features may not work')
69
- "
70
- # Don't exit on database connectivity issues - let app try to start
71
- }
72
-
73
- # Function to populate agents and templates for development (SQLite only)
74
- # Uses agents_config.json if available, falls back to legacy method
75
- populate_agents_templates() {
76
- echo "🔧 Checking if agents/templates need to be populated..."
77
- python -c "
78
- try:
79
- from src.db.init_db import DATABASE_URL
80
- from src.db.schemas.models import AgentTemplate
81
- from src.db.init_db import session_factory
82
-
83
- # Check database type
84
- if DATABASE_URL.startswith('sqlite'):
85
- print('🔍 SQLite database detected - checking template population')
86
-
87
- session = session_factory()
88
- try:
89
- template_count = session.query(AgentTemplate).count()
90
-
91
- if template_count == 0:
92
- print('📋 No templates found - populating agents and templates...')
93
- session.close()
94
- exit(1) # Signal that population is needed
95
- else:
96
- print(f'✅ Found {template_count} templates - population not needed')
97
- session.close()
98
- exit(0) # Signal that population is not needed
99
- except Exception as e:
100
- print(f'⚠️ Error checking templates: {e}')
101
- print('📋 Will attempt to populate anyway')
102
- session.close()
103
- exit(1) # Signal that population is needed
104
- else:
105
- print('🔍 PostgreSQL/RDS detected - skipping auto-population')
106
- exit(0) # Signal that population is not needed
107
-
108
- except Exception as e:
109
- print(f'❌ Error during template check: {e}')
110
- exit(0) # Don't fail startup, just skip population
111
- "
112
-
113
- # Check if population is needed (exit code 1 means yes)
114
- if [ $? -eq 1 ]; then
115
- echo "🚀 Running agent/template population for SQLite..."
116
-
117
- # Check if agents_config.json exists (try multiple locations)
118
- if [ -f "agents_config.json" ] || [ -f "/app/agents_config.json" ] || [ -f "../agents_config.json" ]; then
119
- echo "📖 Found agents_config.json - validating configuration..."
120
-
121
- # Validate configuration first
122
- python scripts/populate_agent_templates.py validate
123
- validation_result=$?
124
-
125
- if [ $validation_result -eq 0 ]; then
126
- echo "✅ Configuration valid - proceeding with sync"
127
- python scripts/populate_agent_templates.py sync
128
- else
129
- echo "⚠️ Configuration validation failed - attempting sync anyway"
130
- python scripts/populate_agent_templates.py sync
131
- fi
132
- else
133
- echo "⚠️ agents_config.json not found - trying legacy method"
134
- python scripts/populate_agent_templates.py
135
- fi
136
-
137
- if [ $? -eq 0 ]; then
138
- echo "✅ Agent/template population completed successfully"
139
- else
140
- echo "⚠️ Agent/template population had issues, but continuing..."
141
- echo "📋 You may need to populate templates manually"
142
- echo "💡 Tip: Ensure agents_config.json exists in the backend directory"
143
- fi
144
- fi
145
- }
146
-
147
- # Check if we need to find agents_config.json from space root
148
- if [ ! -f "/app/agents_config.json" ]; then
149
- echo "⚠️ agents_config.json not found in container - checking build issues"
150
- echo "📁 Files in /app directory:"
151
- ls -la /app/ | head -10
152
- else
153
- echo "✅ agents_config.json found in container"
154
- fi
155
-
156
- # Main startup sequence
157
- echo "🔧 Initializing production environment..."
158
-
159
- # Verify critical imports first
160
- verify_app_imports
161
-
162
- # Initialize database safely (won't modify RDS)
163
- init_production_database
164
-
165
- # Test database connectivity (non-failing)
166
- verify_database_connectivity
167
-
168
- # Populate agents and templates for development (SQLite only)
169
- populate_agents_templates
170
-
171
- echo "🎯 Starting FastAPI application..."
172
- echo "🌐 Application will be available on port 7860"
173
-
174
- # Start the FastAPI application
175
- exec uvicorn app:app --host 0.0.0.0 --port 7860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
images/Auto-Analyst Banner.png ADDED

Git LFS Details

  • SHA256: 30a322031c1e8eca20f202d2bda534a921c5c4556ee9ae81f0fcabdb156ab2cd
  • Pointer size: 131 Bytes
  • Size of remote file: 184 kB
images/Auto-analysts icon small.png ADDED
images/auto-analyst logo.png ADDED
requirements.txt CHANGED
@@ -1,31 +1,30 @@
1
- aiofiles==24.1.0
2
- beautifulsoup4==4.13.4
3
- chardet==5.2.0
4
- dspy==3.2.1
5
- litellm==1.82.3
6
  email_validator==2.2.0
7
- fastapi==0.135.1
8
- fastapi-cli==0.0.7
9
  FastAPI-SQLAlchemy==0.2.1
10
- fastapi-sso==0.16.0
11
- groq==0.18.0
12
- gunicorn==23.0.0
13
- huggingface-hub==0.30.2
14
  joblib==1.4.2
15
- Markdown==3.7
 
 
 
 
 
 
 
 
16
  matplotlib==3.10.0
17
- matplotlib-inline==0.1.7
18
  numpy==2.2.2
19
- openpyxl==3.1.2
20
- xlrd==2.0.1
21
- openai==2.28.0
22
  pandas==2.2.3
23
- polars==1.31.0
24
  pillow==11.1.0
25
  plotly==5.24.1
26
- psycopg2==2.9.10
27
- python-dateutil==2.9.0.post0
28
  python-dotenv==1.0.1
 
29
  requests==2.32.3
30
  scikit-learn==1.6.1
31
  scipy==1.15.1
@@ -37,16 +36,9 @@ tabulate==0.9.0
37
  threadpoolctl==3.5.0
38
  tiktoken==0.8.0
39
  tokenizers==0.21.0
40
- tqdm==4.67.1
41
- urllib3==2.4.0
42
- uvicorn==0.34.0
43
- websockets>=13.1.0
44
  wheel==0.45.1
45
- xgboost-cpu==3.0.2
46
- bokeh==3.7.3
47
- pymc==5.23.0
48
- lightgbm==4.6.0
49
- arviz==0.21.0
50
- optuna==4.3.0
51
- litellm[proxy]
52
- duckdb==1.3.2
 
1
+ groq==0.18.0
2
+ dspy==2.5.43
3
+ dspy-ai==2.5.43
 
 
4
  email_validator==2.2.0
5
+ fastapi==0.111.1
 
6
  FastAPI-SQLAlchemy==0.2.1
7
+ gunicorn==22.0.0
 
 
 
8
  joblib==1.4.2
9
+ litellm==1.53.7
10
+ llama-index==0.12.14
11
+ llama-index-agent-openai==0.4.2
12
+ llama-index-embeddings-openai==0.3.1
13
+ llama-index-indices-managed-llama-cloud==0.6.4
14
+ llama-index-llms-openai==0.3.14
15
+ llama-index-multi-modal-llms-openai==0.4.2
16
+ llama-index-program-openai==0.3.1
17
+ llama-index-question-gen-openai==0.3.0
18
  matplotlib==3.10.0
19
+ multiprocess==0.70.16
20
  numpy==2.2.2
21
+ openai==1.60.1
 
 
22
  pandas==2.2.3
 
23
  pillow==11.1.0
24
  plotly==5.24.1
25
+ pypdf==5.2.0
 
26
  python-dotenv==1.0.1
27
+ regex==2024.11.6
28
  requests==2.32.3
29
  scikit-learn==1.6.1
30
  scipy==1.15.1
 
36
  threadpoolctl==3.5.0
37
  tiktoken==0.8.0
38
  tokenizers==0.21.0
39
+ uvicorn==0.22.0
40
+ websockets==14.2
41
+ Werkzeug==3.1.3
 
42
  wheel==0.45.1
43
+ gunicorn
44
+ psycopg2
 
 
 
 
 
 
sample_output.txt ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ === ERROR IN PLANNER_PREPROCESSING_AGENT ===
4
+ Error in planner_preprocessing_agent: cannot do positional indexing on RangeIndex with these indexers [33.0] of type float
5
+ Traceback (most recent call last):
6
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\scripts\format_response.py", line 196, in execute_code_from_markdown
7
+ exec(block_code, context) # Execute the block
8
+
9
+ Problem at this location:
10
+ 1:
11
+ 2: >>> print(df.head(33.)) <<<
12
+ 3:
13
+ 4: # planner_preprocessing_agent code end
14
+
15
+ Full error details:
16
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\venv\Lib\site-packages\pandas\core\indexes\base.py", line 4301, in _raise_invalid_indexer
17
+ raise TypeError(msg)
18
+ TypeError: cannot do positional indexing on RangeIndex with these indexers [33.0] of type float
19
+
20
+
21
+ === ERROR IN PREPROCESSING_AGENT ===
22
+ Error in preprocessing_agent: cannot do positional indexing on RangeIndex with these indexers [33.33] of type float
23
+ Traceback (most recent call last):
24
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\scripts\format_response.py", line 196, in execute_code_from_markdown
25
+ exec(block_code, context) # Execute the block
26
+
27
+ Problem at this location:
28
+ 1:
29
+ 2: >>> print(df.head(33.33)) <<<
30
+ 3:
31
+ 4: # preprocessing_agent code end
32
+
33
+ Full error details:
34
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\venv\Lib\site-packages\pandas\core\indexes\base.py", line 4301, in _raise_invalid_indexer
35
+ raise TypeError(msg)
36
+ TypeError: cannot do positional indexing on RangeIndex with these indexers [33.33] of type float
37
+
38
+
39
+ === ERROR IN PLANNER_AGENT ===
40
+ Error in planner_agent: NDFrame.head() takes from 1 to 2 positional arguments but 3 were given
41
+ Traceback (most recent call last):
42
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\scripts\format_response.py", line 196, in execute_code_from_markdown
43
+ exec(block_code, context) # Execute the block
44
+
45
+ Problem at this location:
46
+ 1:
47
+ 2: >>> print(df.head(32,3)) <<<
48
+ 3:
49
+ 4: # planner_agent code end
50
+
51
+ Full error details:
52
+ ~~~~^^^^^^^^^^^^^^^^^^^^^
53
+ File "<string>", line 2, in <module>
54
+ TypeError: NDFrame.head() takes from 1 to 2 positional arguments but 3 were given
55
+
56
+
57
+ === ERROR IN STATS_AGENT ===
58
+ Error in stats_agent: 'tuple' object is not callable
59
+ Traceback (most recent call last):
60
+ File "D:\Firebird2\Auto-Analyst-CS\auto-analyst-backend\scripts\format_response.py", line 196, in execute_code_from_markdown
61
+ exec(block_code, context) # Execute the block
62
+
63
+ Problem at this location:
64
+ 1:
65
+ 2: >>> print(df.shape(3)) <<<
66
+ 3:
67
+ 4: # stats_agent code end
68
+
69
+ Full error details:
70
+ ~~~~^^^^^^^^^^^^^^^^^^^^^
71
+ File "<string>", line 2, in <module>
72
+ TypeError: 'tuple' object is not callable
scripts/create_test_user.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+
4
+ # Add parent directory to path so we can import modules
5
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
+
7
+ from init_db import User, session_factory
8
+
9
+ def create_test_users():
10
+ """Create test users for development"""
11
+ session = session_factory()
12
+
13
+ try:
14
+ # Create a few test users
15
+ test_users = [
16
+ {"username": "testuser1", "email": "user1@example.com"},
17
+ {"username": "testuser2", "email": "user2@example.com"},
18
+ {"username": "admin", "email": "admin@example.com"},
19
+ {"username": "demo", "email": "demo@example.com"},
20
+ {"username": "guest", "email": "guest@example.com"},
21
+ ]
22
+
23
+ for user_data in test_users:
24
+ # Check if user already exists
25
+ existing = session.query(User).filter(User.email == user_data["email"]).first()
26
+ if not existing:
27
+ user = User(**user_data)
28
+ session.add(user)
29
+ print(f"Created user: {user_data['username']}")
30
+ else:
31
+ print(f"User {user_data['username']} already exists")
32
+
33
+ session.commit()
34
+ print("Test users created successfully")
35
+
36
+ except Exception as e:
37
+ session.rollback()
38
+ print(f"Error creating test users: {e}")
39
+
40
+ finally:
41
+ session.close()
42
+
43
+ if __name__ == "__main__":
44
+ create_test_users()
45
+ print("Done!")
scripts/format_response.py CHANGED
@@ -8,7 +8,7 @@ import logging
8
  from src.utils.logger import Logger
9
  import textwrap
10
 
11
- logger = Logger(__name__, level=logging.INFO, see_time=False, console_log=False)
12
 
13
  @contextlib.contextmanager
14
  def stdoutIO(stdout=None):
@@ -19,248 +19,6 @@ def stdoutIO(stdout=None):
19
  yield stdout
20
  sys.stdout = old
21
 
22
- # Precompile regex patterns for better performance
23
- SENSITIVE_MODULES = re.compile(r"(os|sys|subprocess|dotenv|requests|http|socket|smtplib|ftplib|telnetlib|paramiko)")
24
- IMPORT_PATTERN = re.compile(r"^\s*import\s+(" + SENSITIVE_MODULES.pattern + r").*?(\n|$)", re.MULTILINE)
25
- FROM_IMPORT_PATTERN = re.compile(r"^\s*from\s+(" + SENSITIVE_MODULES.pattern + r").*?(\n|$)", re.MULTILINE)
26
- DYNAMIC_IMPORT_PATTERN = re.compile(r"__import__\s*\(\s*['\"](" + SENSITIVE_MODULES.pattern + r")['\"].*?\)")
27
- ENV_ACCESS_PATTERN = re.compile(r"(os\.getenv|os\.environ|load_dotenv|\.__import__\s*\(\s*['\"]os['\"].*?\.environ)")
28
- FILE_ACCESS_PATTERN = re.compile(r"(open\(|read\(|write\(|file\(|with\s+open)")
29
-
30
- # Enhanced API key detection patterns
31
- API_KEY_PATTERNS = [
32
- # Direct key assignments
33
- re.compile(r"(?i)(api_?key|access_?token|secret_?key|auth_?token|password|credential|secret)s?\s*=\s*[\"\'][\w\-\+\/\=]{8,}[\"\']"),
34
- # Function calls with keys
35
- re.compile(r"(?i)\.set_api_key\(\s*[\"\'][\w\-\+\/\=]{8,}[\"\']"),
36
- # Dictionary assignments
37
- re.compile(r"(?i)['\"](?:api_?key|access_?token|secret_?key|auth_?token|password|credential|secret)['\"](?:\s*:\s*)[\"\'][\w\-\+\/\=]{8,}[\"\']"),
38
- # Common key formats (base64-like, hex)
39
- re.compile(r"[\"\'](?:[A-Za-z0-9\+\/\=]{32,}|[0-9a-fA-F]{32,})[\"\']"),
40
- # Bearer token pattern
41
- re.compile(r"[\"\'](Bearer\s+[\w\-\+\/\=]{8,})[\"\']"),
42
- # Inline URL with auth
43
- re.compile(r"https?:\/\/[\w\-\+\/\=]{8,}@")
44
- ]
45
-
46
- # Network request patterns
47
- NETWORK_REQUEST_PATTERNS = re.compile(r"(requests\.|urllib\.|http\.client|httpx\.|socket\.connect\()")
48
-
49
- # DataFrame creation with hardcoded data - block only this specific pattern
50
-
51
- def check_security_concerns(code_str, dataset_names):
52
- """Check code for security concerns and return info about what was found"""
53
- security_concerns = {
54
- "has_concern": False,
55
- "blocked_imports": False,
56
- "blocked_dynamic_imports": False,
57
- "blocked_env_access": False,
58
- "blocked_file_access": False,
59
- "blocked_api_keys": False,
60
- "blocked_network": False,
61
- "blocked_dataframe_invention": False, # Add this new field
62
- "messages": []
63
- }
64
-
65
- dataset_names_pattern = "|".join(re.escape(name) for name in dataset_names)
66
- DATAFRAME_INVENTION_PATTERN = re.compile(
67
- rf"({dataset_names_pattern})\s*=\s*pd\.DataFrame\s*\(\s*\{{\s*[^}}]*\}}",
68
- re.MULTILINE
69
- )
70
- if DATAFRAME_INVENTION_PATTERN.search(code_str):
71
- security_concerns["has_concern"] = True
72
- security_concerns["blocked_dataframe_invention"] = True
73
- security_concerns["messages"].append(f"DataFrame creation blocked for dataset variables: {', '.join(dataset_names)}")
74
-
75
- # Check for sensitive imports
76
- if IMPORT_PATTERN.search(code_str) or FROM_IMPORT_PATTERN.search(code_str):
77
- security_concerns["has_concern"] = True
78
- security_concerns["blocked_imports"] = True
79
- security_concerns["messages"].append("Sensitive module imports blocked")
80
-
81
- # Check for __import__ bypass technique
82
- if DYNAMIC_IMPORT_PATTERN.search(code_str):
83
- security_concerns["has_concern"] = True
84
- security_concerns["blocked_dynamic_imports"] = True
85
- security_concerns["messages"].append("Dynamic import of sensitive modules blocked")
86
-
87
- # Check for environment variables access
88
- if ENV_ACCESS_PATTERN.search(code_str):
89
- security_concerns["has_concern"] = True
90
- security_concerns["blocked_env_access"] = True
91
- security_concerns["messages"].append("Environment variables access blocked")
92
-
93
- # Check for file operations
94
- if FILE_ACCESS_PATTERN.search(code_str):
95
- security_concerns["has_concern"] = True
96
- security_concerns["blocked_file_access"] = True
97
- security_concerns["messages"].append("File operations blocked")
98
-
99
- # Check for API key patterns
100
- for pattern in API_KEY_PATTERNS:
101
- if pattern.search(code_str):
102
- security_concerns["has_concern"] = True
103
- security_concerns["blocked_api_keys"] = True
104
- security_concerns["messages"].append("API key/token usage blocked")
105
- break
106
-
107
- # Check for network requests
108
- if NETWORK_REQUEST_PATTERNS.search(code_str):
109
- security_concerns["has_concern"] = True
110
- security_concerns["blocked_network"] = True
111
- security_concerns["messages"].append("Network requests blocked")
112
-
113
-
114
-
115
- return security_concerns
116
-
117
- def clean_code_for_security(code_str, security_concerns, dataset_names):
118
- """Apply security modifications to the code based on detected concerns"""
119
-
120
- modified_code = code_str
121
- dataset_names_pattern = "|".join(re.escape(name) for name in dataset_names)
122
- DATAFRAME_INVENTION_PATTERN = re.compile(
123
- rf"({dataset_names_pattern})\s*=\s*pd\.DataFrame\s*\(\s*\{{\s*[^}}]*\}}",
124
- re.MULTILINE
125
- )
126
-
127
- # Block sensitive imports if needed
128
- if security_concerns["blocked_imports"]:
129
- modified_code = IMPORT_PATTERN.sub(r'# BLOCKED: import \1\n', modified_code)
130
- modified_code = FROM_IMPORT_PATTERN.sub(r'# BLOCKED: from \1\n', modified_code)
131
-
132
- # Block dynamic imports if needed
133
- if security_concerns["blocked_dynamic_imports"]:
134
- modified_code = DYNAMIC_IMPORT_PATTERN.sub(r'"BLOCKED_DYNAMIC_IMPORT"', modified_code)
135
-
136
- # Block environment access if needed
137
- if security_concerns["blocked_env_access"]:
138
- modified_code = ENV_ACCESS_PATTERN.sub(r'"BLOCKED_ENV_ACCESS"', modified_code)
139
-
140
- # Block file operations if needed
141
- if security_concerns["blocked_file_access"]:
142
- modified_code = FILE_ACCESS_PATTERN.sub(r'"BLOCKED_FILE_ACCESS"', modified_code)
143
-
144
- # Block API keys if needed
145
- if security_concerns["blocked_api_keys"]:
146
- for pattern in API_KEY_PATTERNS:
147
- modified_code = pattern.sub(r'"BLOCKED_API_KEY"', modified_code)
148
-
149
- # Block network requests if needed
150
- if security_concerns["blocked_network"]:
151
- modified_code = NETWORK_REQUEST_PATTERNS.sub(r'"BLOCKED_NETWORK_REQUEST"', modified_code)
152
-
153
- # Block DataFrame creation with hardcoded data if detected
154
- if security_concerns["blocked_dataframe_invention"]:
155
- modified_code = DATAFRAME_INVENTION_PATTERN.sub(
156
- r"# BLOCKED_DATAFRAME_INVENTION: \g<0>",
157
- modified_code
158
- )
159
-
160
- # Add warning banner if needed
161
- if security_concerns["has_concern"]:
162
- security_message = "⚠️ SECURITY WARNING: " + ". ".join(security_concerns["messages"]) + "."
163
- modified_code = f"print('{security_message}')\n\n" + modified_code
164
-
165
- return modified_code
166
-
167
- def format_correlation_output(text):
168
- """Format correlation matrix output for better readability"""
169
- lines = text.split('\n')
170
- formatted_lines = []
171
-
172
- for line in lines:
173
- # Skip empty lines at the beginning
174
- if not line.strip() and not formatted_lines:
175
- continue
176
-
177
- if not line.strip():
178
- formatted_lines.append(line)
179
- continue
180
-
181
- # Check if this line contains correlation values or variable names
182
- stripped_line = line.strip()
183
- parts = stripped_line.split()
184
-
185
- if len(parts) > 1:
186
- # Check if this is a header line with variable names
187
- if all(part.replace('_', '').replace('-', '').isalpha() for part in parts):
188
- # This is a header row with variable names
189
- formatted_header = f"{'':12}" # Empty first column for row labels
190
- for part in parts:
191
- formatted_header += f"{part:>12}"
192
- formatted_lines.append(formatted_header)
193
- elif any(char.isdigit() for char in stripped_line) and ('.' in stripped_line or '-' in stripped_line):
194
- # This looks like a correlation line with numbers
195
- row_name = parts[0] if parts else ""
196
- values = parts[1:] if len(parts) > 1 else []
197
-
198
- formatted_row = f"{row_name:<12}"
199
- for value in values:
200
- try:
201
- val = float(value)
202
- formatted_row += f"{val:>12.3f}"
203
- except ValueError:
204
- formatted_row += f"{value:>12}"
205
-
206
- formatted_lines.append(formatted_row)
207
- else:
208
- # Other lines (like titles)
209
- formatted_lines.append(line)
210
- else:
211
- formatted_lines.append(line)
212
-
213
- return '\n'.join(formatted_lines)
214
-
215
- def format_summary_stats(text):
216
- """Format summary statistics for better readability"""
217
- lines = text.split('\n')
218
- formatted_lines = []
219
-
220
- for line in lines:
221
- if not line.strip():
222
- formatted_lines.append(line)
223
- continue
224
-
225
- # Check if this is a header line with statistical terms only (missing first column)
226
- stripped_line = line.strip()
227
- if any(stat in stripped_line.lower() for stat in ['count', 'mean', 'median', 'std', 'min', 'max', '25%', '50%', '75%']):
228
- parts = stripped_line.split()
229
- # Check if this is a header row (starts with statistical terms)
230
- if parts and parts[0].lower() in ['count', 'mean', 'median', 'std', 'min', 'max', '25%', '50%', '75%']:
231
- # This is a header row - add proper spacing
232
- formatted_header = f"{'':12}" # Empty first column for row labels
233
- for part in parts:
234
- formatted_header += f"{part:>15}"
235
- formatted_lines.append(formatted_header)
236
- else:
237
- # This is a data row - format normally
238
- row_name = parts[0] if parts else ""
239
- values = parts[1:] if len(parts) > 1 else []
240
-
241
- formatted_row = f"{row_name:<12}"
242
- for value in values:
243
- try:
244
- if '.' in value or 'e' in value.lower():
245
- val = float(value)
246
- if abs(val) >= 1000000:
247
- formatted_row += f"{val:>15.2e}"
248
- elif abs(val) >= 1:
249
- formatted_row += f"{val:>15.2f}"
250
- else:
251
- formatted_row += f"{val:>15.6f}"
252
- else:
253
- val = int(value)
254
- formatted_row += f"{val:>15}"
255
- except ValueError:
256
- formatted_row += f"{value:>15}"
257
-
258
- formatted_lines.append(formatted_row)
259
- else:
260
- # Other lines (titles, etc.) - keep as is
261
- formatted_lines.append(line)
262
-
263
- return '\n'.join(formatted_lines)
264
 
265
  def clean_print_statements(code_block):
266
  """
@@ -304,14 +62,6 @@ def format_code_block(code_str):
304
  return f'\n{code_clean}\n'
305
 
306
  def format_code_backticked_block(code_str):
307
- # Add None check at the beginning
308
- if code_str is None:
309
- return "```python\n# No code available\n```" # Return empty code block instead of None
310
-
311
- # Add type check to ensure it's a string
312
- if not isinstance(code_str, str):
313
- return f"```python\n# Invalid code type: {type(code_str)}\n```"
314
-
315
  code_clean = re.sub(r'^```python\n?', '', code_str, flags=re.MULTILINE)
316
  code_clean = re.sub(r'\n```$', '', code_clean)
317
  # Only match assignments at top level (not indented)
@@ -320,7 +70,6 @@ def format_code_backticked_block(code_str):
320
 
321
  # Remove reading the csv file if it's already in the context
322
  modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', code_clean)
323
- modified_code = re.sub(r'^(\s*)(df\s*=.*)$', r'\1# \2', code_clean, flags=re.MULTILINE)
324
 
325
  # Only match assignments at top level (not indented)
326
  # 1. Remove 'df = pd.DataFrame()' if it's at the top level
@@ -344,7 +93,8 @@ def format_code_backticked_block(code_str):
344
  return f'```python\n{code_clean}\n```'
345
 
346
 
347
- def execute_code_from_markdown(code_str, datasets=None):
 
348
  import pandas as pd
349
  import plotly.express as px
350
  import plotly
@@ -355,170 +105,8 @@ def execute_code_from_markdown(code_str, datasets=None):
355
  import re
356
  import traceback
357
  import sys
358
- from io import StringIO, BytesIO
359
- import base64
360
-
361
- context_names = list(datasets.keys())
362
- # Check for security concerns in the code
363
- security_concerns = check_security_concerns(code_str, context_names)
364
-
365
- # Apply security modifications to the code
366
- modified_code = clean_code_for_security(code_str, security_concerns, context_names)
367
-
368
- # Enhanced print function that detects and formats tabular data
369
- captured_outputs = []
370
- original_print = print
371
-
372
- # Set pandas display options for full table display
373
- pd.set_option('display.max_columns', None)
374
- pd.set_option('display.max_rows', 20) # Limit to 20 rows instead of unlimited
375
- pd.set_option('display.width', None)
376
- pd.set_option('display.max_colwidth', 50)
377
- pd.set_option('display.expand_frame_repr', False)
378
-
379
-
380
-
381
- def enhanced_print(*args, **kwargs):
382
- # Convert all args to strings
383
- str_args = [str(arg) for arg in args]
384
- output_text = kwargs.get('sep', ' ').join(str_args)
385
-
386
- # Special case for DataFrames - use pipe delimiter and clean format
387
- if isinstance(args[0], pd.DataFrame) and len(args) == 1:
388
- # Format DataFrame with pipe delimiter using to_csv for reliable column separation
389
- df = args[0]
390
-
391
- # Use StringIO to capture CSV output with pipe delimiter
392
- from io import StringIO
393
- csv_buffer = StringIO()
394
-
395
- # Export to CSV with pipe delimiter, preserving index
396
- df.to_csv(csv_buffer, sep='|', index=True, float_format='%.6g')
397
- csv_output = csv_buffer.getvalue()
398
-
399
- # Clean up the CSV output - remove quotes and extra formatting
400
- lines = csv_output.strip().split('\n')
401
- cleaned_lines = []
402
-
403
- for line in lines:
404
- # Remove any quotes that might have been added by to_csv
405
- clean_line = line.replace('"', '')
406
- # Split by pipe, strip whitespace from each part, then rejoin
407
- parts = [part.strip() for part in clean_line.split('|')]
408
- cleaned_lines.append(' | '.join(parts))
409
-
410
- output_text = '\n'.join(cleaned_lines)
411
- captured_outputs.append(f"<TABLE_START>\n{output_text}\n<TABLE_END>")
412
- original_print(output_text)
413
- return
414
-
415
- # Detect if this looks like tabular data (generic approach)
416
- is_table = False
417
-
418
- # Check for table patterns:
419
- # 1. Multiple lines with consistent spacing
420
- lines = output_text.split('\n')
421
- if len(lines) > 2:
422
- # Count lines that look like they have multiple columns (2+ spaces between words)
423
- multi_column_lines = sum(1 for line in lines if len(line.split()) > 1 and ' ' in line)
424
- if multi_column_lines >= 2: # At least 2 lines with multiple columns
425
- is_table = True
426
-
427
- # Check for pandas DataFrame patterns like index with column names
428
- if any(re.search(r'^\s*\d+\s+', line) for line in lines):
429
- # Look for lines starting with an index number followed by spaces
430
- is_table = True
431
-
432
- # Look for table-like structured output with multiple rows of similar format
433
- if len(lines) >= 3:
434
- # Sample a few lines to check for consistent structure
435
- sample_lines = [lines[i] for i in range(min(len(lines), 5)) if i < len(lines) and lines[i].strip()]
436
-
437
- # Check for consistent whitespace patterns
438
- if len(sample_lines) >= 2:
439
- # Get positions of whitespace groups in first line
440
- whitespace_positions = []
441
- for i, line in enumerate(sample_lines):
442
- if not line.strip():
443
- continue
444
- positions = [m.start() for m in re.finditer(r'\s{2,}', line)]
445
- if i == 0:
446
- whitespace_positions = positions
447
- elif len(positions) == len(whitespace_positions):
448
- # Check if whitespace positions are roughly the same
449
- is_similar = all(abs(pos - whitespace_positions[j]) <= 3
450
- for j, pos in enumerate(positions)
451
- if j < len(whitespace_positions))
452
- if is_similar:
453
- is_table = True
454
-
455
- # 2. Contains common table indicators
456
- if any(indicator in output_text.lower() for indicator in [
457
- 'count', 'mean', 'std', 'min', 'max', '25%', '50%', '75%', # Summary stats
458
- 'correlation', 'corr', # Correlation tables
459
- 'coefficient', 'r-squared', 'p-value', # Regression tables
460
- ]):
461
- is_table = True
462
-
463
- # 3. Has many decimal numbers (likely a data table)
464
- if output_text.count('.') > 5 and len(lines) > 2:
465
- is_table = True
466
-
467
- # If we have detected a table, convert space-delimited to pipe-delimited format
468
- if is_table:
469
- # Convert the table to pipe-delimited format for better parsing in frontend
470
- formatted_lines = []
471
- for line in lines:
472
- if not line.strip():
473
- formatted_lines.append(line) # Keep empty lines
474
- continue
475
-
476
- # Split by multiple spaces and join with pipe delimiter
477
- parts = re.split(r'\s{2,}', line.strip())
478
- if parts:
479
- formatted_lines.append(" | ".join(parts))
480
- else:
481
- formatted_lines.append(line)
482
-
483
- # Use the pipe-delimited format
484
- output_text = "\n".join(formatted_lines)
485
-
486
- # Format and mark the output for table processing in UI
487
- captured_outputs.append(f"<TABLE_START>\n{output_text}\n<TABLE_END>")
488
- else:
489
- captured_outputs.append(output_text)
490
-
491
- # Also use original print for stdout capture
492
- original_print(*args, **kwargs)
493
-
494
- # Custom matplotlib capture function
495
- def capture_matplotlib_chart():
496
- """Capture current matplotlib figure as base64 encoded image"""
497
- try:
498
- fig = plt.gcf() # Get current figure
499
- if fig.get_axes(): # Check if figure has any plots
500
- buffer = BytesIO()
501
- fig.savefig(buffer, format='png', dpi=150, bbox_inches='tight',
502
- facecolor='white', edgecolor='none')
503
- buffer.seek(0)
504
- img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
505
- buffer.close()
506
- plt.close(fig) # Close the figure to free memory
507
- return img_base64
508
- return None
509
- except Exception:
510
- return None
511
 
512
- # Store original plt.show function
513
- original_plt_show = plt.show
514
-
515
- def custom_plt_show(*args, **kwargs):
516
- """Custom plt.show that captures the chart instead of displaying it"""
517
- img_base64 = capture_matplotlib_chart()
518
- if img_base64:
519
- matplotlib_outputs.append(img_base64)
520
- # Don't call original show to prevent display
521
-
522
  context = {
523
  'pd': pd,
524
  'px': px,
@@ -529,31 +117,22 @@ def execute_code_from_markdown(code_str, datasets=None):
529
  '__import__': __import__,
530
  'sns': sns,
531
  'np': np,
532
- 'json_outputs': [], # List to store multiple Plotly JSON outputs
533
- 'matplotlib_outputs': [], # List to store matplotlib chart images as base64
534
- 'print': enhanced_print # Replace print with our enhanced version
535
  }
536
-
537
- # Add matplotlib_outputs to local scope for the custom show function
538
- matplotlib_outputs = context['matplotlib_outputs']
539
-
540
- # Replace plt.show with our custom function
541
- plt.show = custom_plt_show
542
-
543
-
544
 
545
  # Modify code to store multiple JSON outputs
546
  modified_code = re.sub(
547
  r'(\w*_?)fig(\w*)\.show\(\)',
548
  r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))',
549
- modified_code
550
  )
551
 
552
  modified_code = re.sub(
553
  r'(\w*_?)fig(\w*)\.to_html\(.*?\)',
554
  r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))',
555
  modified_code
556
- )
 
557
  # Remove reading the csv file if it's already in the context
558
  modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', modified_code)
559
 
@@ -565,70 +144,27 @@ def execute_code_from_markdown(code_str, datasets=None):
565
  modified_code,
566
  flags=re.MULTILINE
567
  )
568
-
569
-
570
- # Custom display function for DataFrames to show head + tail for large datasets
571
- original_repr = pd.DataFrame.__repr__
572
-
573
- def custom_df_repr(self):
574
- if len(self) > 15:
575
- # For large DataFrames, show first 10 and last 5 rows
576
- head_part = self.head(10)
577
- tail_part = self.tail(5)
578
-
579
- head_str = head_part.__repr__()
580
- tail_str = tail_part.__repr__()
581
-
582
- # Extract just the data rows (skip the header from tail)
583
- tail_lines = tail_str.split('\n')
584
- tail_data = '\n'.join(tail_lines[1:]) # Skip header line
585
-
586
- return f"{head_str}\n...\n{tail_data}"
587
- else:
588
- return original_repr(self)
589
-
590
- # Apply custom representation temporarily
591
- pd.DataFrame.__repr__ = custom_df_repr
592
 
593
  # If a dataframe is provided, add it to the context
594
- for dataset_name, dataset_df in datasets.items():
595
- if dataset_df is not None:
596
- context[dataset_name] = dataset_df
597
- logger.log_message(f"Added dataset '{dataset_name}' to execution context", level=logging.DEBUG)
598
-
599
 
600
  # remove pd.read_csv() if it's already in the context
601
  modified_code = re.sub(r"pd\.read_csv\(\s*[\"\'].*?[\"\']\s*\)", '', modified_code)
602
 
603
  # Remove sample dataframe lines with multiple array values
604
  modified_code = re.sub(r"^# Sample DataFrames?.*?(\n|$)", '', modified_code, flags=re.MULTILINE | re.IGNORECASE)
605
-
606
- # Replace plt.savefig() calls with plt.show() to ensure plots are displayed
607
- modified_code = re.sub(r'plt\.savefig\([^)]*\)', 'plt.show()', modified_code)
608
-
609
- # Instead of removing plt.show(), keep them - they'll be handled by our custom function
610
- # Also handle seaborn plots that might not have explicit plt.show()
611
- # Add plt.show() after seaborn plot functions if not already present
612
- seaborn_plot_functions = [
613
- 'sns.scatterplot', 'sns.lineplot', 'sns.barplot', 'sns.boxplot', 'sns.violinplot',
614
- 'sns.stripplot', 'sns.swarmplot', 'sns.pointplot', 'sns.catplot', 'sns.relplot',
615
- 'sns.displot', 'sns.histplot', 'sns.kdeplot', 'sns.ecdfplot', 'sns.rugplot',
616
- 'sns.distplot', 'sns.jointplot', 'sns.pairplot', 'sns.FacetGrid', 'sns.PairGrid',
617
- 'sns.heatmap', 'sns.clustermap', 'sns.regplot', 'sns.lmplot', 'sns.residplot'
618
- ]
619
-
620
- # Add automatic plt.show() after seaborn plots if not already present
621
- for func in seaborn_plot_functions:
622
- pattern = rf'({re.escape(func)}\([^)]*\)(?:\.[^(]*\([^)]*\))*)'
623
- def add_show(match):
624
- plot_call = match.group(1)
625
- # Check if the next non-empty line already has plt.show()
626
- return f'{plot_call}\nplt.show()'
627
 
628
- modified_code = re.sub(pattern, add_show, modified_code)
 
629
 
630
  # Only add df = pd.read_csv() if no dataframe was provided and the code contains pd.read_csv
631
-
 
 
 
 
 
632
 
633
  # Identify code blocks by comments
634
  code_blocks = []
@@ -656,42 +192,11 @@ def execute_code_from_markdown(code_str, datasets=None):
656
  all_outputs = []
657
  for block_name, block_code in code_blocks:
658
  try:
659
- # Clear captured outputs for each block
660
- captured_outputs.clear()
661
-
662
- # Fix indentation issues before execution
663
- try:
664
- block_code = textwrap.dedent(block_code)
665
- except Exception as dedent_error:
666
- logger.log_message(f"Failed to dedent code block '{block_name}': {str(dedent_error)}", level=logging.WARNING)
667
-
668
  with stdoutIO() as s:
669
  exec(block_code, context) # Execute the block
670
-
671
- # Get both stdout and our enhanced captured outputs
672
- stdout_output = s.getvalue()
673
-
674
- # Combine outputs, preferring our enhanced format when available
675
- if captured_outputs:
676
- combined_output = '\n'.join(captured_outputs)
677
- else:
678
- combined_output = stdout_output
679
-
680
- all_outputs.append((block_name, combined_output, None)) # None means no error
681
  except Exception as e:
682
- # Reset pandas options in case of error
683
- pd.reset_option('display.max_columns')
684
- pd.reset_option('display.max_rows')
685
- pd.reset_option('display.width')
686
- pd.reset_option('display.max_colwidth')
687
- pd.reset_option('display.expand_frame_repr')
688
-
689
- # Restore original DataFrame representation in case of error
690
- pd.DataFrame.__repr__ = original_repr
691
-
692
- # Restore original plt.show
693
- plt.show = original_plt_show
694
-
695
  error_traceback = traceback.format_exc()
696
 
697
  # Extract error message and error type
@@ -838,23 +343,9 @@ def execute_code_from_markdown(code_str, datasets=None):
838
 
839
  all_outputs.append((block_name, None, formatted_error))
840
 
841
- # Reset pandas options after execution
842
- pd.reset_option('display.max_columns')
843
- pd.reset_option('display.max_rows')
844
- pd.reset_option('display.width')
845
- pd.reset_option('display.max_colwidth')
846
- pd.reset_option('display.expand_frame_repr')
847
-
848
- # Restore original DataFrame representation
849
- pd.DataFrame.__repr__ = original_repr
850
-
851
- # Restore original plt.show
852
- plt.show = original_plt_show
853
-
854
  # Compile all outputs and errors
855
  output_text = ""
856
  json_outputs = context.get('json_outputs', [])
857
- matplotlib_outputs = context.get('matplotlib_outputs', [])
858
  error_found = False
859
 
860
  for block_name, output, error in all_outputs:
@@ -865,9 +356,9 @@ def execute_code_from_markdown(code_str, datasets=None):
865
  output_text += f"\n\n=== OUTPUT FROM {block_name.upper()}_AGENT ===\n{output}\n"
866
 
867
  if error_found:
868
- return output_text, [], []
869
  else:
870
- return output_text, json_outputs, matplotlib_outputs
871
 
872
 
873
  def format_plan_instructions(plan_instructions):
@@ -875,127 +366,54 @@ def format_plan_instructions(plan_instructions):
875
  Format any plan instructions (JSON string or dict) into markdown sections per agent.
876
  """
877
  # Parse input into a dict
878
-
879
- if "basic_qa_agent" in str(plan_instructions):
880
- return "**Non-Data Request**: Please ask a data related query, don't waste credits!"
881
-
882
-
883
  try:
884
  if isinstance(plan_instructions, str):
885
- try:
886
- instructions = json.loads(plan_instructions)
887
- except json.JSONDecodeError as e:
888
- # Try to clean the string if it's not valid JSON
889
- cleaned_str = plan_instructions.strip()
890
- if cleaned_str.startswith("'") and cleaned_str.endswith("'"):
891
- cleaned_str = cleaned_str[1:-1]
892
- try:
893
- instructions = json.loads(cleaned_str)
894
- except json.JSONDecodeError:
895
- raise ValueError(f"Invalid JSON format in plan instructions: {str(e)}")
896
  elif isinstance(plan_instructions, dict):
897
  instructions = plan_instructions
898
  else:
899
- raise TypeError(f"Unsupported plan instructions type: {type(plan_instructions)}")
900
- except Exception as e:
901
- raise ValueError(f"Error processing plan instructions: {str(e)} + {dspy.settings.lm} ")
902
  # logger.log_message(f"Plan instructions: {instructions}", level=logging.INFO)
903
 
904
-
905
-
906
  markdown_lines = []
907
  for agent, content in instructions.items():
908
- if agent != 'basic_qa_agent':
909
- agent_title = agent.replace('_', ' ').title()
910
- markdown_lines.append(f"#### {agent_title}")
911
- if isinstance(content, dict):
912
- # Handle 'create' key
913
- create_vals = content.get('create', [])
914
- if create_vals:
915
- markdown_lines.append(f"- **Create**:")
916
- for item in create_vals:
917
- markdown_lines.append(f" - {item}")
918
- else:
919
- markdown_lines.append(f"- **Create**: None")
920
-
921
- # Handle 'use' key
922
- use_vals = content.get('use', [])
923
- if use_vals:
924
- markdown_lines.append(f"- **Use**:")
925
- for item in use_vals:
926
- markdown_lines.append(f" - {item}")
927
- else:
928
- markdown_lines.append(f"- **Use**: None")
929
-
930
- # Handle 'instruction' key
931
- instr = content.get('instruction')
932
- if isinstance(instr, str) and instr:
933
- markdown_lines.append(f"- **Instruction**: {instr}")
934
- else:
935
- markdown_lines.append(f"- **Instruction**: None")
936
  else:
937
- # Fallback for non-dict content
938
- markdown_lines.append(f"- {content}")
939
- markdown_lines.append("") # blank line between agents
940
- else:
941
- markdown_lines.append(f"**Non-Data Request**: {content.get('instruction')}")
942
-
943
- return "\n".join(markdown_lines).strip()
944
-
945
- # Return empty string if no complexity found
946
- return ""
947
 
948
- def format_complexity(instructions):
949
- markdown_lines = []
950
- complexity = None # Initialize complexity to avoid UnboundLocalError
951
-
952
- # Extract complexity from various possible locations in the structure
953
- if isinstance(instructions, dict):
954
- # Case 1: Direct complexity field
955
- if 'complexity' in instructions:
956
- complexity = instructions['complexity']
957
- # Case 2: Complexity in 'plan' object
958
- elif 'plan' in instructions and isinstance(instructions['plan'], dict):
959
- if 'complexity' in instructions['plan']:
960
- complexity = instructions['plan']['complexity']
961
  else:
962
- complexity = "unrelated"
963
-
964
- # Override for basic_qa_agent cases
965
- if 'plan' in instructions and isinstance(instructions['plan'], str) and "basic_qa_agent" in instructions['plan']:
966
- complexity = "unrelated"
967
- else:
968
- # If instructions is not a dict, default to unrelated
969
- complexity = "unrelated"
970
-
971
- if complexity:
972
- # Pink color scheme variations
973
- color_map = {
974
- "unrelated": "#FFB6B6", # Light pink
975
- "basic": "#FF9E9E", # Medium pink
976
- "intermediate": "#FF7F7F", # Main pink
977
- "advanced": "#FF5F5F" # Dark pink
978
- }
979
-
980
- indicator_map = {
981
- "unrelated": "○",
982
- "basic": "●",
983
- "intermediate": "●●",
984
- "advanced": "●●●"
985
- }
986
-
987
- color = color_map.get(complexity.lower(), "#FFB6B6") # Default to light pink
988
- indicator = indicator_map.get(complexity.lower(), "○")
989
-
990
- # Slightly larger display with pink styling
991
- markdown_lines.append(f"<div style='color: {color}; border: 2px solid {color}; padding: 2px 8px; border-radius: 12px; display: inline-block; font-size: 14.4px;'>{indicator} {complexity}</div>\n")
992
 
993
- return "\n".join(markdown_lines).strip()
994
 
995
- # Return empty string if no complexity found
996
- return ""
997
-
998
- def format_response_to_markdown(api_response, agent_name = None, datasets=None):
999
  try:
1000
  markdown = []
1001
  # logger.log_message(f"API response for {agent_name} at {time.strftime('%Y-%m-%d %H:%M:%S')}: {api_response}", level=logging.INFO)
@@ -1020,81 +438,64 @@ def format_response_to_markdown(api_response, agent_name = None, datasets=None):
1020
  if "memory" in agent or not content:
1021
  continue
1022
 
1023
- if "complexity" in content:
1024
- complexity_result = format_complexity(content)
1025
- if complexity_result: # Only append if not empty
1026
- markdown.append(f"{complexity_result}")
1027
-
1028
  markdown.append(f"\n## {agent.replace('_', ' ').title()}\n")
1029
 
1030
  if agent == "analytical_planner":
1031
- logger.log_message(f"Analytical planner content: {content}", level=logging.INFO)
1032
- if 'plan_desc' in content and content['plan_desc']:
1033
- markdown.append(f"### Reasoning {content['plan_desc']}")
1034
  if 'plan_instructions' in content:
1035
- plan_result = format_plan_instructions(content['plan_instructions'])
1036
- if plan_result: # Only append if not empty/None
1037
- markdown.append(f"{plan_result}")
1038
  else:
1039
- if content.get('rationale'):
1040
- markdown.append(f"### Reasoning {content['rationale']}")
1041
  else:
1042
  if "rationale" in content:
1043
- if content.get('rationale'):
1044
- markdown.append(f"### Reasoning{content['rationale']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1045
 
1046
- if 'code' in content and content['code'] is not None:
1047
- formatted_code = format_code_backticked_block(content['code'])
1048
- if formatted_code: # Check if formatted_code is not None
1049
- markdown.append(f"### Code Implementation\n{formatted_code}\n")
1050
- if 'answer' in content and content['answer']:
1051
- markdown.append(f"### Answer{content['answer']} Please ask a query about the data")
1052
  if 'summary' in content:
1053
- import re
1054
- summary_text = content['summary']
1055
- summary_text = re.sub(r'```python\n(.*?)\n```', '', summary_text, flags=re.DOTALL)
1056
-
1057
  markdown.append("### Summary\n")
1058
-
1059
- # Extract pre-list intro, bullet points, and post-list text
1060
- intro_match = re.split(r'\(\d+\)', summary_text, maxsplit=1)
1061
- if len(intro_match) > 1:
1062
- intro_text = intro_match[0].strip()
1063
- rest_text = "(1)" + intro_match[1] # reattach for bullet parsing
1064
- else:
1065
- intro_text = summary_text.strip()
1066
- rest_text = ""
1067
-
1068
- if intro_text:
1069
- markdown.append(f"{intro_text}\n")
1070
-
1071
- # Split bullets at numbered items like (1)...(8)
1072
- bullets = re.split(r'\(\d+\)', rest_text)
1073
- bullets = [b.strip(" ,.\n") for b in bullets if b.strip()]
1074
-
1075
- # Check for post-list content (anything after the last number)
1076
- for i, bullet in enumerate(bullets):
1077
- markdown.append(f"* {bullet}\n")
1078
-
1079
-
1080
-
1081
 
1082
  if 'refined_complete_code' in content and 'summary' in content:
1083
  try:
1084
  if content['refined_complete_code'] is not None and content['refined_complete_code'] != "":
1085
  clean_code = format_code_block(content['refined_complete_code'])
1086
  markdown_code = format_code_backticked_block(content['refined_complete_code'])
1087
- output, json_outputs, matplotlib_outputs = execute_code_from_markdown(clean_code, datasets)
1088
  elif "```python" in content['summary']:
1089
  clean_code = format_code_block(content['summary'])
1090
  markdown_code = format_code_backticked_block(content['summary'])
1091
- output, json_outputs, matplotlib_outputs = execute_code_from_markdown(clean_code, datasets)
1092
  except Exception as e:
1093
  logger.log_message(f"Error in execute_code_from_markdown: {str(e)}", level=logging.ERROR)
1094
  markdown_code = f"**Error**: {str(e)}"
1095
- output = None
1096
- json_outputs = []
1097
- matplotlib_outputs = []
1098
  # continue
1099
 
1100
  if markdown_code is not None:
@@ -1108,30 +509,45 @@ def format_response_to_markdown(api_response, agent_name = None, datasets=None):
1108
  markdown.append("### Plotly JSON Outputs\n")
1109
  for idx, json_output in enumerate(json_outputs):
1110
  markdown.append(f"```plotly\n{json_output}\n```\n")
1111
-
1112
- if matplotlib_outputs:
1113
- markdown.append("### Matplotlib/Seaborn Charts\n")
1114
- for idx, img_base64 in enumerate(matplotlib_outputs):
1115
- markdown.append(f"```matplotlib\n{img_base64}\n```\n")
1116
  # if agent_name is not None:
1117
  # if f"memory_{agent_name}" in api_response:
1118
  # markdown.append(f"### Memory\n{api_response[f'memory_{agent_name}']}\n")
1119
 
1120
  except Exception as e:
1121
  logger.log_message(f"Error in format_response_to_markdown: {str(e)}", level=logging.ERROR)
1122
- return f"error formating markdown {str(e)}"
1123
 
1124
  # logger.log_message(f"Generated markdown content for agent '{agent_name}' at {time.strftime('%Y-%m-%d %H:%M:%S')}: {markdown}, length: {len(markdown)}", level=logging.INFO)
1125
 
1126
  if not markdown or len(markdown) <= 1:
1127
- logger.log_message(
1128
- f"Invalid markdown content for agent '{agent_name}' at {time.strftime('%Y-%m-%d %H:%M:%S')}: "
1129
- f"Content: '{markdown}', Type: {type(markdown)}, Length: {len(markdown) if markdown else 0}, "
1130
- f"API Response: {api_response}",
1131
- level=logging.ERROR
1132
- )
1133
- return ""
1134
 
1135
  return '\n'.join(markdown)
1136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
 
 
 
8
  from src.utils.logger import Logger
9
  import textwrap
10
 
11
+ logger = Logger(__name__, level="INFO", see_time=False, console_log=False)
12
 
13
  @contextlib.contextmanager
14
  def stdoutIO(stdout=None):
 
19
  yield stdout
20
  sys.stdout = old
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def clean_print_statements(code_block):
24
  """
 
62
  return f'\n{code_clean}\n'
63
 
64
  def format_code_backticked_block(code_str):
 
 
 
 
 
 
 
 
65
  code_clean = re.sub(r'^```python\n?', '', code_str, flags=re.MULTILINE)
66
  code_clean = re.sub(r'\n```$', '', code_clean)
67
  # Only match assignments at top level (not indented)
 
70
 
71
  # Remove reading the csv file if it's already in the context
72
  modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', code_clean)
 
73
 
74
  # Only match assignments at top level (not indented)
75
  # 1. Remove 'df = pd.DataFrame()' if it's at the top level
 
93
  return f'```python\n{code_clean}\n```'
94
 
95
 
96
+ # In format_response.py, modify the execute_code function:
97
+ def execute_code_from_markdown(code_str, dataframe=None):
98
  import pandas as pd
99
  import plotly.express as px
100
  import plotly
 
105
  import re
106
  import traceback
107
  import sys
108
+ from io import StringIO
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
 
 
 
 
 
 
 
 
 
 
110
  context = {
111
  'pd': pd,
112
  'px': px,
 
117
  '__import__': __import__,
118
  'sns': sns,
119
  'np': np,
120
+ 'json_outputs': [] # List to store multiple Plotly JSON outputs
 
 
121
  }
 
 
 
 
 
 
 
 
122
 
123
  # Modify code to store multiple JSON outputs
124
  modified_code = re.sub(
125
  r'(\w*_?)fig(\w*)\.show\(\)',
126
  r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))',
127
+ code_str
128
  )
129
 
130
  modified_code = re.sub(
131
  r'(\w*_?)fig(\w*)\.to_html\(.*?\)',
132
  r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))',
133
  modified_code
134
+ )
135
+
136
  # Remove reading the csv file if it's already in the context
137
  modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', modified_code)
138
 
 
144
  modified_code,
145
  flags=re.MULTILINE
146
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  # If a dataframe is provided, add it to the context
149
+ if dataframe is not None:
150
+ context['df'] = dataframe
 
 
 
151
 
152
  # remove pd.read_csv() if it's already in the context
153
  modified_code = re.sub(r"pd\.read_csv\(\s*[\"\'].*?[\"\']\s*\)", '', modified_code)
154
 
155
  # Remove sample dataframe lines with multiple array values
156
  modified_code = re.sub(r"^# Sample DataFrames?.*?(\n|$)", '', modified_code, flags=re.MULTILINE | re.IGNORECASE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ # Remove plt.show() statements
159
+ modified_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', modified_code)
160
 
161
  # Only add df = pd.read_csv() if no dataframe was provided and the code contains pd.read_csv
162
+ if dataframe is None and 'pd.read_csv' not in modified_code:
163
+ modified_code = re.sub(
164
+ r'import pandas as pd',
165
+ r'import pandas as pd\n\n# Read Housing.csv\ndf = pd.read_csv("Housing.csv")',
166
+ modified_code
167
+ )
168
 
169
  # Identify code blocks by comments
170
  code_blocks = []
 
192
  all_outputs = []
193
  for block_name, block_code in code_blocks:
194
  try:
 
 
 
 
 
 
 
 
 
195
  with stdoutIO() as s:
196
  exec(block_code, context) # Execute the block
197
+ output = s.getvalue()
198
+ all_outputs.append((block_name, output, None)) # None means no error
 
 
 
 
 
 
 
 
 
199
  except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  error_traceback = traceback.format_exc()
201
 
202
  # Extract error message and error type
 
343
 
344
  all_outputs.append((block_name, None, formatted_error))
345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  # Compile all outputs and errors
347
  output_text = ""
348
  json_outputs = context.get('json_outputs', [])
 
349
  error_found = False
350
 
351
  for block_name, output, error in all_outputs:
 
356
  output_text += f"\n\n=== OUTPUT FROM {block_name.upper()}_AGENT ===\n{output}\n"
357
 
358
  if error_found:
359
+ return output_text, []
360
  else:
361
+ return output_text, json_outputs
362
 
363
 
364
  def format_plan_instructions(plan_instructions):
 
366
  Format any plan instructions (JSON string or dict) into markdown sections per agent.
367
  """
368
  # Parse input into a dict
 
 
 
 
 
369
  try:
370
  if isinstance(plan_instructions, str):
371
+ instructions = json.loads(plan_instructions)
 
 
 
 
 
 
 
 
 
 
372
  elif isinstance(plan_instructions, dict):
373
  instructions = plan_instructions
374
  else:
375
+ return f"Unsupported plan instructions type: {type(plan_instructions)}"
376
+ except json.JSONDecodeError:
377
+ return f"Invalid plan instructions: {plan_instructions}"
378
  # logger.log_message(f"Plan instructions: {instructions}", level=logging.INFO)
379
 
 
 
380
  markdown_lines = []
381
  for agent, content in instructions.items():
382
+ agent_title = agent.replace('_', ' ').title()
383
+ markdown_lines.append(f"#### {agent_title}")
384
+ if isinstance(content, dict):
385
+ # Handle 'create' key
386
+ create_vals = content.get('create', [])
387
+ if create_vals:
388
+ markdown_lines.append(f"- **Create**:")
389
+ for item in create_vals:
390
+ markdown_lines.append(f" - {item}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  else:
392
+ markdown_lines.append(f"- **Create**: None")
393
+
394
+ # Handle 'use' key
395
+ use_vals = content.get('use', [])
396
+ if use_vals:
397
+ markdown_lines.append(f"- **Use**:")
398
+ for item in use_vals:
399
+ markdown_lines.append(f" - {item}")
400
+ else:
401
+ markdown_lines.append(f"- **Use**: None")
402
 
403
+ # Handle 'instruction' key
404
+ instr = content.get('instruction')
405
+ if isinstance(instr, str) and instr:
406
+ markdown_lines.append(f"- **Instruction**: {instr}")
407
+ else:
408
+ markdown_lines.append(f"- **Instruction**: None")
 
 
 
 
 
 
 
409
  else:
410
+ # Fallback for non-dict content
411
+ markdown_lines.append(f"- {content}")
412
+ markdown_lines.append("") # blank line between agents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
+ return "\n".join(markdown_lines).strip()
415
 
416
+ def format_response_to_markdown(api_response, agent_name = None, dataframe=None):
 
 
 
417
  try:
418
  markdown = []
419
  # logger.log_message(f"API response for {agent_name} at {time.strftime('%Y-%m-%d %H:%M:%S')}: {api_response}", level=logging.INFO)
 
438
  if "memory" in agent or not content:
439
  continue
440
 
 
 
 
 
 
441
  markdown.append(f"\n## {agent.replace('_', ' ').title()}\n")
442
 
443
  if agent == "analytical_planner":
444
+ # logger.log_message(f"Analytical planner content: {content}", level=logging.INFO)
445
+ if 'plan_desc' in content:
446
+ markdown.append(f"### Reasoning\n{content['plan_desc']}\n")
447
  if 'plan_instructions' in content:
448
+ markdown.append(f"### Plan Instructions\n{format_plan_instructions(content['plan_instructions'])}\n")
 
 
449
  else:
450
+ markdown.append(f"### Reasoning\n{content['rationale']}\n")
 
451
  else:
452
  if "rationale" in content:
453
+ markdown.append(f"### Reasoning\n{content['rationale']}\n")
454
+
455
+ if 'code' in content:
456
+ markdown.append(f"### Code Implementation\n{format_code_backticked_block(content['code'])}\n")
457
+ # if agent_name is not None:
458
+ # # execute the code
459
+ # clean_code = format_code_block(content['code'])
460
+ # output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
461
+ # if output:
462
+ # markdown.append("### Execution Output\n")
463
+ # markdown.append(f"```output\n{output}\n```\n")
464
+
465
+ # if json_outputs:
466
+ # markdown.append("### Plotly JSON Outputs\n")
467
+ # for idx, json_output in enumerate(json_outputs):
468
+ # if len(json_output) > 1000000: # If JSON is larger than 1MB
469
+ # logger.log_message(f"Large JSON output detected: {len(json_output)} bytes", level=logging.WARNING)
470
+ # markdown.append(f"```plotly\n{json_output}\n```\n")
471
 
 
 
 
 
 
 
472
  if 'summary' in content:
473
+ # make the summary a bullet-point list
474
+ summary_lines = remove_code_block_from_summary(content['summary'])
475
+ summary_lines = content['summary'].split('\n')
476
+ # remove code block from summary
477
  markdown.append("### Summary\n")
478
+ for line in summary_lines:
479
+ if line != "":
480
+ if line.strip().startswith('•') or line.strip().startswith('-') or line.strip().startswith('*'):
481
+ line = line.strip().replace('•', '').replace('-', '').replace('*', '')
482
+ markdown.append(f"* {line.strip()}\n")
483
+ else:
484
+ markdown.append(f"{line.strip()}\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
486
  if 'refined_complete_code' in content and 'summary' in content:
487
  try:
488
  if content['refined_complete_code'] is not None and content['refined_complete_code'] != "":
489
  clean_code = format_code_block(content['refined_complete_code'])
490
  markdown_code = format_code_backticked_block(content['refined_complete_code'])
491
+ output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
492
  elif "```python" in content['summary']:
493
  clean_code = format_code_block(content['summary'])
494
  markdown_code = format_code_backticked_block(content['summary'])
495
+ output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
496
  except Exception as e:
497
  logger.log_message(f"Error in execute_code_from_markdown: {str(e)}", level=logging.ERROR)
498
  markdown_code = f"**Error**: {str(e)}"
 
 
 
499
  # continue
500
 
501
  if markdown_code is not None:
 
509
  markdown.append("### Plotly JSON Outputs\n")
510
  for idx, json_output in enumerate(json_outputs):
511
  markdown.append(f"```plotly\n{json_output}\n```\n")
 
 
 
 
 
512
  # if agent_name is not None:
513
  # if f"memory_{agent_name}" in api_response:
514
  # markdown.append(f"### Memory\n{api_response[f'memory_{agent_name}']}\n")
515
 
516
  except Exception as e:
517
  logger.log_message(f"Error in format_response_to_markdown: {str(e)}", level=logging.ERROR)
518
+ return f"{str(e)}"
519
 
520
  # logger.log_message(f"Generated markdown content for agent '{agent_name}' at {time.strftime('%Y-%m-%d %H:%M:%S')}: {markdown}, length: {len(markdown)}", level=logging.INFO)
521
 
522
  if not markdown or len(markdown) <= 1:
523
+ logger.log_message(f"Generated markdown (ERROR) content for agent '{agent_name}' at {time.strftime('%Y-%m-%d %H:%M:%S')}: {markdown}, length: {len(markdown)}, api_response: {api_response}", level=logging.INFO)
524
+ return "Please provide a valid query..."
 
 
 
 
 
525
 
526
  return '\n'.join(markdown)
527
 
528
+ # Example usage with dummy data
529
+ if __name__ == "__main__":
530
+ sample_response = {
531
+ "code_combiner_agent": {
532
+ "reasoning": "Sample reasoning for multiple charts.",
533
+ "refined_complete_code": """
534
+ ```python
535
+ import plotly.express as px
536
+ import pandas as pd
537
+
538
+ # Sample Data
539
+ df = pd.DataFrame({'Category': ['A', 'B', 'C'], 'Values': [10, 20, 30]})
540
+
541
+ # First Chart
542
+ fig = px.bar(df, x='Category', y='Values', title='Bar Chart')
543
+ fig.show()
544
+
545
+ # Second Chart
546
+ fig2 = px.pie(df, values='Values', names='Category', title='Pie Chart')
547
+ fig2.show()
548
+ ```
549
+ """
550
+ }
551
+ }
552
 
553
+ formatted_md = format_response_to_markdown(sample_response)
scripts/generate_test_data.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import random
4
+ from datetime import datetime, timedelta
5
+ import sqlite3
6
+
7
+ # Add parent directory to path so we can import modules
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ from init_db import ModelUsage, session_factory
11
+
12
+ # Models and providers to use in test data
13
+ MODELS = {
14
+ "gpt-3.5-turbo": {"provider": "OpenAI", "cost_per_1k": 0.0015},
15
+ "gpt-4": {"provider": "OpenAI", "cost_per_1k": 0.03},
16
+ "gpt-4o": {"provider": "OpenAI", "cost_per_1k": 0.01},
17
+ "gpt-4o-mini": {"provider": "OpenAI", "cost_per_1k": 0.0015},
18
+ "o1-mini": {"provider": "OpenAI", "cost_per_1k": 0.00015},
19
+ "claude-3-opus": {"provider": "Anthropic", "cost_per_1k": 0.015},
20
+ "claude-3-sonnet": {"provider": "Anthropic", "cost_per_1k": 0.008},
21
+ "claude-3-haiku": {"provider": "Anthropic", "cost_per_1k": 0.003},
22
+ "llama-3-8b": {"provider": "Groq", "cost_per_1k": 0.0005},
23
+ "llama-3-70b": {"provider": "Groq", "cost_per_1k": 0.002},
24
+ }
25
+
26
+ # User IDs to use (can be random if you don't have specific users)
27
+ USER_IDS = [1, 2, 3, 4, 5]
28
+
29
+ def generate_test_data(num_records=100):
30
+ """Generate test model usage data"""
31
+ session = session_factory()
32
+
33
+ try:
34
+ # Generate records for the past 30 days
35
+ end_date = datetime.utcnow()
36
+ start_date = end_date - timedelta(days=30)
37
+
38
+ for _ in range(num_records):
39
+ # Random timestamp within the date range
40
+ random_days = random.randint(0, 30)
41
+ timestamp = end_date - timedelta(days=random_days,
42
+ hours=random.randint(0, 23),
43
+ minutes=random.randint(0, 59))
44
+
45
+ # Select random model and user
46
+ model_name = random.choice(list(MODELS.keys()))
47
+ model_info = MODELS[model_name]
48
+ user_id = random.choice(USER_IDS)
49
+
50
+ # Generate random token counts
51
+ prompt_tokens = random.randint(100, 1000)
52
+ completion_tokens = random.randint(50, 500)
53
+ total_tokens = prompt_tokens + completion_tokens
54
+
55
+ # Calculate cost
56
+ cost = (total_tokens / 1000) * model_info["cost_per_1k"]
57
+
58
+ # Create model usage record
59
+ usage = ModelUsage(
60
+ user_id=user_id,
61
+ chat_id=random.randint(1, 50), # Random chat ID
62
+ model_name=model_name,
63
+ provider=model_info["provider"],
64
+ prompt_tokens=prompt_tokens,
65
+ completion_tokens=completion_tokens,
66
+ total_tokens=total_tokens,
67
+ query_size=prompt_tokens * 4, # Approximate characters
68
+ response_size=completion_tokens * 4, # Approximate characters
69
+ cost=cost,
70
+ timestamp=timestamp,
71
+ is_streaming=random.choice([True, False]),
72
+ request_time_ms=random.randint(500, 5000) # Between 0.5 and 5 seconds
73
+ )
74
+ session.add(usage)
75
+
76
+ session.commit()
77
+ print(f"Successfully generated {num_records} test records")
78
+
79
+ except Exception as e:
80
+ session.rollback()
81
+ print(f"Error generating test data: {e}")
82
+
83
+ finally:
84
+ session.close()
85
+
86
+ if __name__ == "__main__":
87
+ # Default to 100 records, but allow command line override
88
+ num_records = int(sys.argv[1]) if len(sys.argv) > 1 else 100
89
+ generate_test_data(num_records)
90
+ print("Done! The model_usage table has been populated with test data.")
scripts/init_production_db.py DELETED
@@ -1,191 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Production database initialization script.
4
- This ensures templates are populated properly and verifies database health.
5
- SAFE for PostgreSQL/RDS - only creates tables on SQLite databases.
6
- """
7
-
8
- import sys
9
- import os
10
- import logging
11
- from datetime import datetime, UTC
12
-
13
- # Add the project root to the Python path
14
- sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15
-
16
- from src.db.init_db import init_db, session_factory, engine, is_postgres_db
17
- from src.db.schemas.models import Base, AgentTemplate, UserTemplatePreference
18
- from scripts.populate_agent_templates import populate_templates
19
- from sqlalchemy import inspect, text
20
- from src.utils.logger import Logger
21
-
22
- logger = Logger("init_production_db", see_time=True, console_log=True)
23
-
24
- def get_database_type():
25
- """Get the database type (sqlite or postgresql)."""
26
- try:
27
- if is_postgres_db():
28
- return "postgresql"
29
- else:
30
- return "sqlite"
31
- except Exception as e:
32
- logger.log_message(f"Error determining database type: {e}", logging.ERROR)
33
- return "unknown"
34
-
35
- def check_table_exists(table_name: str) -> bool:
36
- """Check if a table exists in the database."""
37
- try:
38
- inspector = inspect(engine)
39
- tables = inspector.get_table_names()
40
- return table_name in tables
41
- except Exception as e:
42
- logger.log_message(f"Error checking table existence: {e}", logging.ERROR)
43
- return False
44
-
45
- def verify_database_schema():
46
- """Verify that all required tables exist. Only create tables on SQLite."""
47
- db_type = get_database_type()
48
- logger.log_message(f"🔍 Verifying database schema for {db_type.upper()} database...", logging.INFO)
49
-
50
- required_tables = [
51
- 'users', 'chats', 'messages', 'model_usage', 'code_executions',
52
- 'message_feedback', 'deep_analysis_reports', 'agent_templates',
53
- 'user_template_preferences'
54
- ]
55
-
56
- missing_tables = []
57
- existing_tables = []
58
-
59
- for table in required_tables:
60
- if not check_table_exists(table):
61
- missing_tables.append(table)
62
- logger.log_message(f"❌ Missing table: {table}", logging.WARNING)
63
- else:
64
- existing_tables.append(table)
65
- logger.log_message(f"✅ Table exists: {table}", logging.INFO)
66
-
67
- if missing_tables:
68
- if db_type == "sqlite":
69
- logger.log_message(f"🔧 Creating missing tables on SQLite: {missing_tables}", logging.INFO)
70
- try:
71
- # Safe to create tables on SQLite
72
- Base.metadata.create_all(engine)
73
- logger.log_message("✅ All tables created successfully on SQLite", logging.INFO)
74
- except Exception as e:
75
- logger.log_message(f"❌ Failed to create tables: {e}", logging.ERROR)
76
- raise
77
- else:
78
- # PostgreSQL/RDS - DO NOT create tables automatically
79
- logger.log_message(f"⚠️ WARNING: Missing tables detected in {db_type.upper()} database: {missing_tables}", logging.WARNING)
80
- logger.log_message("🛡️ SAFETY: Not creating tables automatically on PostgreSQL/RDS", logging.INFO)
81
- logger.log_message("📋 Please ensure these tables exist in your RDS database:", logging.INFO)
82
- for table in missing_tables:
83
- logger.log_message(f" - {table}", logging.INFO)
84
-
85
- # Continue without failing - the app might still work with existing tables
86
- if 'agent_templates' in missing_tables or 'user_template_preferences' in missing_tables:
87
- logger.log_message("⚠️ Template functionality may not work without agent_templates and user_template_preferences tables", logging.WARNING)
88
- else:
89
- logger.log_message(f"✅ All required tables exist in {db_type.upper()} database", logging.INFO)
90
-
91
- def verify_template_data():
92
- """Verify that agent templates are populated. Safe for all database types."""
93
- logger.log_message("📋 Verifying template data...", logging.INFO)
94
-
95
- session = session_factory()
96
- try:
97
- # Check if agent_templates table exists before querying
98
- if not check_table_exists('agent_templates'):
99
- logger.log_message("⚠️ agent_templates table does not exist, skipping template verification", logging.WARNING)
100
- return
101
-
102
- template_count = session.query(AgentTemplate).filter(AgentTemplate.is_active == True).count()
103
- logger.log_message(f"📊 Found {template_count} active templates", logging.INFO)
104
-
105
- if template_count == 0:
106
- logger.log_message("🔧 No templates found, populating...", logging.INFO)
107
- try:
108
- populate_templates()
109
-
110
- # Verify population worked
111
- new_count = session.query(AgentTemplate).filter(AgentTemplate.is_active == True).count()
112
- logger.log_message(f"✅ Templates populated. Total active templates: {new_count}", logging.INFO)
113
- except Exception as e:
114
- logger.log_message(f"❌ Template population failed: {e}", logging.ERROR)
115
- logger.log_message("⚠️ App will continue but template functionality may not work", logging.WARNING)
116
- else:
117
- logger.log_message("✅ Templates already populated", logging.INFO)
118
-
119
- except Exception as e:
120
- logger.log_message(f"❌ Error verifying templates: {e}", logging.ERROR)
121
- logger.log_message("⚠️ Template verification failed, but app will continue", logging.WARNING)
122
- finally:
123
- session.close()
124
-
125
- def test_template_api_functionality():
126
- """Test that template-related database operations work. Safe for all database types."""
127
- logger.log_message("🧪 Testing template API functionality...", logging.INFO)
128
-
129
- session = session_factory()
130
- try:
131
- # Check if agent_templates table exists before testing
132
- if not check_table_exists('agent_templates'):
133
- logger.log_message("⚠️ agent_templates table does not exist, skipping API test", logging.WARNING)
134
- return
135
-
136
- # Test basic template query
137
- templates = session.query(AgentTemplate).filter(AgentTemplate.is_active == True).limit(5).all()
138
- logger.log_message(f"✅ Successfully queried {len(templates)} templates", logging.INFO)
139
-
140
- if templates:
141
- sample_template = templates[0]
142
- logger.log_message(f"📄 Sample template: {sample_template.template_name} - {sample_template.display_name}", logging.INFO)
143
- else:
144
- logger.log_message("📭 No templates found in database", logging.INFO)
145
-
146
- except Exception as e:
147
- logger.log_message(f"❌ Template API test failed: {e}", logging.ERROR)
148
- logger.log_message("⚠️ Template API may not work properly", logging.WARNING)
149
- finally:
150
- session.close()
151
-
152
- def run_safe_initialization():
153
- """Run safe database initialization that respects production databases."""
154
- db_type = get_database_type()
155
- logger.log_message(f"🚀 Starting SAFE database initialization for {db_type.upper()}...", logging.INFO)
156
-
157
- if db_type == "postgresql":
158
- logger.log_message("🛡️ PostgreSQL/RDS detected - running in SAFE mode", logging.INFO)
159
- logger.log_message("📋 Will only verify schema and populate templates", logging.INFO)
160
- elif db_type == "sqlite":
161
- logger.log_message("💽 SQLite detected - full initialization mode", logging.INFO)
162
-
163
- try:
164
- # Step 1: Initialize database (safe for all types)
165
- logger.log_message("Step 1: Basic database initialization", logging.INFO)
166
- if db_type == "sqlite":
167
- init_db() # Only run full init on SQLite
168
- else:
169
- logger.log_message("Skipping init_db() for PostgreSQL (safety)", logging.INFO)
170
-
171
- # Step 2: Verify schema (safe - only creates tables on SQLite)
172
- logger.log_message("Step 2: Schema verification", logging.INFO)
173
- verify_database_schema()
174
-
175
- # Step 3: Verify template data (safe for all types)
176
- logger.log_message("Step 3: Template data verification", logging.INFO)
177
- verify_template_data()
178
-
179
- # Step 4: Test functionality (safe for all types)
180
- logger.log_message("Step 4: Functionality testing", logging.INFO)
181
- test_template_api_functionality()
182
-
183
- logger.log_message(f"🎉 Safe database initialization completed for {db_type.upper()}!", logging.INFO)
184
-
185
- except Exception as e:
186
- logger.log_message(f"💥 Database initialization failed: {e}", logging.ERROR)
187
- logger.log_message("⚠️ App may still start but some features might not work", logging.WARNING)
188
- # Don't raise - let the app try to start anyway
189
-
190
- if __name__ == "__main__":
191
- run_safe_initialization()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/populate_agent_templates.py DELETED
@@ -1,508 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- SQLite Agent Template Management Script
4
- Similar to manage_templates.py but optimized for local SQLite development.
5
- Reads agents from agents_config.json and manages SQLite database.
6
- """
7
-
8
- import sys
9
- import os
10
- import json
11
- import requests
12
- from datetime import datetime, UTC
13
- from pathlib import Path
14
-
15
- # Add the project root to the Python path
16
- script_dir = os.path.dirname(os.path.abspath(__file__))
17
- backend_dir = os.path.dirname(script_dir)
18
- project_root = os.path.dirname(os.path.dirname(backend_dir))
19
-
20
- # Change to backend directory to ensure proper path resolution
21
- os.chdir(backend_dir)
22
- sys.path.append(backend_dir)
23
-
24
- from src.db.init_db import session_factory, DATABASE_URL
25
- from src.db.schemas.models import AgentTemplate
26
- from sqlalchemy.exc import IntegrityError
27
-
28
- def get_database_type():
29
- """Detect database type from DATABASE_URL"""
30
- if DATABASE_URL.startswith('postgresql'):
31
- return "postgresql"
32
- elif DATABASE_URL.startswith('sqlite'):
33
- return "sqlite"
34
- else:
35
- return "unknown"
36
-
37
- def load_agents_config():
38
- """Load agents configuration from agents_config.json"""
39
- # Try multiple possible locations for agents_config.json
40
- possible_paths = [
41
- os.path.join(backend_dir, 'agents_config.json'), # Backend directory (copied file)
42
- os.path.join(project_root, 'agents_config.json'), # Project root
43
- '/app/agents_config.json', # Container root (HF Spaces)
44
- 'agents_config.json' # Current directory
45
- ]
46
-
47
- config_path = None
48
- for path in possible_paths:
49
- if os.path.exists(path):
50
- config_path = path
51
- print(f"📖 Found agents_config.json at: {config_path}")
52
- break
53
-
54
- if not config_path:
55
- paths_str = '\n '.join(possible_paths)
56
- raise FileNotFoundError(f"agents_config.json not found in any of these locations:\n {paths_str}")
57
-
58
- with open(config_path, 'r', encoding='utf-8') as f:
59
- config = json.load(f)
60
-
61
- return config.get('templates', [])
62
-
63
- def download_icon(icon_url, template_name):
64
- """Download icon from URL and save to frontend directory"""
65
- if not icon_url or not icon_url.startswith('http'):
66
- print(f"⏭️ Skipping icon download for {template_name} (not a URL: {icon_url})")
67
- return icon_url
68
-
69
- try:
70
- # Determine frontend directory
71
- frontend_dir = os.path.join(project_root, 'Auto-Analyst-CS', 'auto-analyst-frontend')
72
- public_dir = os.path.join(frontend_dir, 'public')
73
-
74
- if not os.path.exists(public_dir):
75
- print(f"⚠️ Frontend public directory not found: {public_dir}")
76
- return icon_url
77
-
78
- # Parse the path from icon_url
79
- if '/icons/templates/' in icon_url:
80
- relative_path = icon_url.split('/icons/templates/')[-1]
81
- icon_dir = os.path.join(public_dir, 'icons', 'templates')
82
- else:
83
- # Fallback: use filename from URL
84
- filename = icon_url.split('/')[-1]
85
- if not filename.endswith(('.svg', '.png', '.jpg', '.jpeg')):
86
- filename += '.svg'
87
- relative_path = filename
88
- icon_dir = os.path.join(public_dir, 'icons', 'templates')
89
-
90
- # Create icon directory if it doesn't exist
91
- os.makedirs(icon_dir, exist_ok=True)
92
-
93
- # Download and save icon
94
- icon_path = os.path.join(icon_dir, relative_path)
95
-
96
- # Skip if already exists
97
- if os.path.exists(icon_path):
98
- print(f"📁 Icon already exists: {relative_path}")
99
- return f"/icons/templates/{relative_path}"
100
-
101
- response = requests.get(icon_url, timeout=10)
102
- response.raise_for_status()
103
-
104
- with open(icon_path, 'wb') as f:
105
- f.write(response.content)
106
-
107
- print(f"📥 Downloaded icon: {relative_path}")
108
- return f"/icons/templates/{relative_path}"
109
-
110
- except Exception as e:
111
- print(f"❌ Failed to download icon for {template_name}: {str(e)}")
112
- return icon_url
113
-
114
- def sync_agents_from_config():
115
- """Synchronize agents from agents_config.json to SQLite database"""
116
- session = session_factory()
117
- db_type = get_database_type()
118
-
119
- # if db_type != "sqlite":
120
- # print(f"⚠️ This script is designed for SQLite, but detected {db_type}")
121
- # print("Consider using manage_templates.py for PostgreSQL")
122
- # return
123
-
124
- try:
125
- # Load configuration
126
- print(f"📖 Loading agents from agents_config.json...")
127
- templates_config = load_agents_config()
128
-
129
- if not templates_config:
130
- print("❌ No templates found in agents_config.json")
131
- return
132
-
133
- # Track statistics
134
- created_count = 0
135
- updated_count = 0
136
- skipped_count = 0
137
-
138
- print(f"🔍 Processing {len(templates_config)} templates for SQLite database")
139
- print(f"📋 Database URL: {DATABASE_URL}")
140
-
141
- # Group templates by category for display
142
- categories = {}
143
- for template_data in templates_config:
144
- category = template_data.get('category', 'Uncategorized')
145
- if category not in categories:
146
- categories[category] = []
147
- categories[category].append(template_data)
148
-
149
- # Process templates by category
150
- for category, templates in categories.items():
151
- print(f"\n📁 {category}:")
152
-
153
- for template_data in templates:
154
- template_name = template_data["template_name"]
155
-
156
- # Check if template already exists
157
- existing = session.query(AgentTemplate).filter(
158
- AgentTemplate.template_name == template_name
159
- ).first()
160
-
161
- # Download icon if it's a URL
162
- icon_url = template_data.get("icon_url", "")
163
- if icon_url.startswith('http'):
164
- icon_url = download_icon(icon_url, template_name)
165
-
166
- if existing:
167
- # Update existing template
168
- existing.display_name = template_data["display_name"]
169
- existing.description = template_data["description"]
170
- existing.icon_url = icon_url
171
- existing.prompt_template = template_data["prompt_template"]
172
- existing.category = template_data.get("category", "Uncategorized")
173
- existing.is_premium_only = template_data.get("is_premium_only", False)
174
- existing.is_active = template_data.get("is_active", True)
175
- existing.variant_type = template_data.get("variant_type", "individual")
176
- existing.base_agent = template_data.get("base_agent", template_name)
177
- existing.updated_at = datetime.now(UTC)
178
-
179
- variant_icon = "🤖" if template_data.get("variant_type") == "planner" else "👤"
180
- premium_icon = "🔒" if template_data.get("is_premium_only") else "🆓"
181
- print(f"🔄 Updated: {template_name} {variant_icon} {premium_icon}")
182
- updated_count += 1
183
- else:
184
- # Create new template
185
- template = AgentTemplate(
186
- template_name=template_name,
187
- display_name=template_data["display_name"],
188
- description=template_data["description"],
189
- icon_url=icon_url,
190
- prompt_template=template_data["prompt_template"],
191
- category=template_data.get("category", "Uncategorized"),
192
- is_premium_only=template_data.get("is_premium_only", False),
193
- is_active=template_data.get("is_active", True),
194
- variant_type=template_data.get("variant_type", "individual"),
195
- base_agent=template_data.get("base_agent", template_name),
196
- created_at=datetime.now(UTC),
197
- updated_at=datetime.now(UTC)
198
- )
199
-
200
- session.add(template)
201
- variant_icon = "🤖" if template_data.get("variant_type") == "planner" else "👤"
202
- premium_icon = "🔒" if template_data.get("is_premium_only") else "🆓"
203
- print(f"✅ Created: {template_name} {variant_icon} {premium_icon}")
204
- created_count += 1
205
-
206
- # Handle removals if specified in config
207
- remove_list = []
208
- # Re-load the full config to check for removals
209
- try:
210
- full_config_path = None
211
- possible_paths = [
212
- os.path.join(backend_dir, 'agents_config.json'),
213
- os.path.join(project_root, 'agents_config.json'),
214
- '/app/agents_config.json',
215
- 'agents_config.json'
216
- ]
217
-
218
- for path in possible_paths:
219
- if os.path.exists(path):
220
- full_config_path = path
221
- break
222
-
223
- if full_config_path:
224
- with open(full_config_path, 'r', encoding='utf-8') as f:
225
- full_config = json.load(f)
226
- if 'remove' in full_config:
227
- remove_list = full_config['remove']
228
- except Exception as e:
229
- print(f"⚠️ Could not load removal list: {e}")
230
-
231
- # Remove templates marked for removal
232
- if remove_list:
233
- print(f"\n🗑️ --- Processing Removals ---")
234
- for template_name in remove_list:
235
- existing = session.query(AgentTemplate).filter(
236
- AgentTemplate.template_name == template_name
237
- ).first()
238
-
239
- if existing:
240
- session.delete(existing)
241
- print(f"🗑️ Removed: {template_name}")
242
- else:
243
- print(f"⏭️ Skipping removal: {template_name} (not found)")
244
-
245
- # Commit all changes
246
- session.commit()
247
-
248
- print(f"\n📊 --- Summary ---")
249
- print(f"✅ Templates created: {created_count}")
250
- print(f"🔄 Templates updated: {updated_count}")
251
- print(f"⏭️ Templates skipped: {skipped_count}")
252
-
253
- # Show total count in database
254
- total_count = session.query(AgentTemplate).count()
255
- free_count = session.query(AgentTemplate).filter(AgentTemplate.is_premium_only == False).count()
256
- premium_count = session.query(AgentTemplate).filter(AgentTemplate.is_premium_only == True).count()
257
- individual_count = session.query(AgentTemplate).filter(AgentTemplate.variant_type == 'individual').count()
258
- planner_count = session.query(AgentTemplate).filter(AgentTemplate.variant_type == 'planner').count()
259
-
260
- print(f"🗄️ Total templates in database: {total_count}")
261
- print(f"🆓 Free templates: {free_count}")
262
- print(f"🔒 Premium templates: {premium_count}")
263
- print(f"👤 Individual variants: {individual_count}")
264
- print(f"🤖 Planner variants: {planner_count}")
265
-
266
- except Exception as e:
267
- session.rollback()
268
- print(f"❌ Error syncing templates: {str(e)}")
269
- raise
270
- finally:
271
- session.close()
272
-
273
- def list_templates():
274
- """List all existing templates in the database"""
275
- session = session_factory()
276
-
277
- try:
278
- templates = session.query(AgentTemplate).order_by(
279
- AgentTemplate.category,
280
- AgentTemplate.is_premium_only,
281
- AgentTemplate.template_name
282
- ).all()
283
-
284
- if not templates:
285
- print("No templates found in database.")
286
- return
287
-
288
- print(f"\n--- Existing Templates ({len(templates)} total) ---")
289
-
290
- current_category = None
291
- for template in templates:
292
- if template.category != current_category:
293
- current_category = template.category
294
- print(f"\n📁 {current_category}:")
295
-
296
- status = "🔒 Premium" if template.is_premium_only else "🆓 Free"
297
- active = "✅ Active" if template.is_active else "❌ Inactive"
298
- variant = getattr(template, 'variant_type', 'individual')
299
- variant_icon = "🤖" if variant == "planner" else "👤"
300
-
301
- print(f" • {template.template_name} ({template.display_name})")
302
- print(f" {status} - {active} - {variant_icon} {variant}")
303
- print(f" 📝 {template.description}")
304
-
305
- except Exception as e:
306
- print(f"❌ Error listing templates: {str(e)}")
307
- finally:
308
- session.close()
309
-
310
- def remove_all_templates():
311
- """Remove all templates from database (for testing)"""
312
- session = session_factory()
313
-
314
- try:
315
- deleted_count = session.query(AgentTemplate).delete()
316
- session.commit()
317
- print(f"🗑️ Removed {deleted_count} templates from database")
318
-
319
- except Exception as e:
320
- session.rollback()
321
- print(f"❌ Error removing templates: {str(e)}")
322
- finally:
323
- session.close()
324
-
325
- def validate_config():
326
- """Validate the agents_config.json structure"""
327
- try:
328
- templates_config = load_agents_config()
329
-
330
- print(f"📋 Validating agents_config.json...")
331
- print(f"✅ Found {len(templates_config)} templates")
332
-
333
- # Check required fields
334
- required_fields = ['template_name', 'display_name', 'description', 'prompt_template']
335
- issues = []
336
-
337
- for i, template in enumerate(templates_config):
338
- for field in required_fields:
339
- if field not in template:
340
- issues.append(f"Template {i}: Missing required field '{field}'")
341
-
342
- if issues:
343
- print(f"❌ Validation issues found:")
344
- for issue in issues:
345
- print(f" • {issue}")
346
- else:
347
- print(f"✅ Configuration is valid")
348
-
349
- # Show summary by category
350
- categories = {}
351
- for template in templates_config:
352
- category = template.get('category', 'Uncategorized')
353
- if category not in categories:
354
- categories[category] = {'free': 0, 'premium': 0, 'individual': 0, 'planner': 0}
355
-
356
- if template.get('is_premium_only', False):
357
- categories[category]['premium'] += 1
358
- else:
359
- categories[category]['free'] += 1
360
-
361
- if template.get('variant_type', 'individual') == 'planner':
362
- categories[category]['planner'] += 1
363
- else:
364
- categories[category]['individual'] += 1
365
-
366
- print(f"\n📊 Summary by category:")
367
- for category, counts in categories.items():
368
- total = counts['free'] + counts['premium']
369
- print(f" 📁 {category}: {total} templates")
370
- print(f" 🆓 Free: {counts['free']} | 🔒 Premium: {counts['premium']}")
371
- print(f" 👤 Individual: {counts['individual']} | 🤖 Planner: {counts['planner']}")
372
-
373
- except Exception as e:
374
- print(f"❌ Error validating config: {str(e)}")
375
-
376
- def create_minimal_templates():
377
- """Create a minimal set of essential templates for container environments"""
378
- session = session_factory()
379
-
380
- try:
381
- print("🔧 Creating minimal template set...")
382
-
383
- # Define minimal essential templates
384
- minimal_templates = [
385
- {
386
- "template_name": "preprocessing_agent",
387
- "display_name": "Data Preprocessing Agent",
388
- "description": "Cleans and prepares DataFrame using Pandas and NumPy",
389
- "icon_url": "/icons/templates/preprocessing_agent.svg",
390
- "category": "Data Manipulation",
391
- "is_premium_only": False,
392
- "variant_type": "individual",
393
- "base_agent": "preprocessing_agent",
394
- "is_active": True,
395
- "prompt_template": "You are a preprocessing agent that cleans and prepares data using Pandas and NumPy. Handle missing values, detect column types, and convert date strings to datetime. Generate clean Python code for data preprocessing based on the user's analysis goals."
396
- },
397
- {
398
- "template_name": "data_viz_agent",
399
- "display_name": "Data Visualization Agent",
400
- "description": "Creates interactive visualizations using Plotly",
401
- "icon_url": "/icons/templates/data_viz_agent.svg",
402
- "category": "Data Visualization",
403
- "is_premium_only": False,
404
- "variant_type": "individual",
405
- "base_agent": "data_viz_agent",
406
- "is_active": True,
407
- "prompt_template": "You are a data visualization agent. Create interactive visualizations using Plotly based on user requirements. Generate appropriate chart types, apply styling, and ensure visualizations effectively communicate insights."
408
- },
409
- {
410
- "template_name": "sk_learn_agent",
411
- "display_name": "Machine Learning Agent",
412
- "description": "Trains ML models using scikit-learn",
413
- "icon_url": "/icons/templates/sk_learn_agent.svg",
414
- "category": "Data Modelling",
415
- "is_premium_only": False,
416
- "variant_type": "individual",
417
- "base_agent": "sk_learn_agent",
418
- "is_active": True,
419
- "prompt_template": "You are a machine learning agent. Use scikit-learn to train and evaluate ML models including classification, regression, and clustering. Provide feature importance insights and model performance metrics."
420
- }
421
- ]
422
-
423
- created_count = 0
424
-
425
- for template_data in minimal_templates:
426
- template_name = template_data["template_name"]
427
-
428
- # Check if template already exists
429
- existing = session.query(AgentTemplate).filter(
430
- AgentTemplate.template_name == template_name
431
- ).first()
432
-
433
- if not existing:
434
- template = AgentTemplate(
435
- template_name=template_name,
436
- display_name=template_data["display_name"],
437
- description=template_data["description"],
438
- icon_url=template_data["icon_url"],
439
- prompt_template=template_data["prompt_template"],
440
- category=template_data["category"],
441
- is_premium_only=template_data["is_premium_only"],
442
- is_active=template_data["is_active"],
443
- variant_type=template_data["variant_type"],
444
- base_agent=template_data["base_agent"],
445
- created_at=datetime.now(UTC),
446
- updated_at=datetime.now(UTC)
447
- )
448
-
449
- session.add(template)
450
- print(f"✅ Created minimal template: {template_name}")
451
- created_count += 1
452
- else:
453
- print(f"⏭️ Template already exists: {template_name}")
454
-
455
- session.commit()
456
- print(f"📊 Created {created_count} minimal templates")
457
-
458
- except Exception as e:
459
- session.rollback()
460
- print(f"❌ Error creating minimal templates: {str(e)}")
461
- raise
462
- finally:
463
- session.close()
464
-
465
- def populate_templates():
466
- """Legacy compatibility function for backward compatibility"""
467
- print("⚠️ Legacy populate_templates() called - checking for agents_config.json...")
468
-
469
- # Check if agents_config.json exists anywhere
470
- possible_paths = [
471
- os.path.join(backend_dir, 'agents_config.json'),
472
- os.path.join(project_root, 'agents_config.json'),
473
- '/app/agents_config.json',
474
- 'agents_config.json'
475
- ]
476
-
477
- config_exists = any(os.path.exists(path) for path in possible_paths)
478
-
479
- if config_exists:
480
- print("📖 Found agents_config.json - using sync_agents_from_config()")
481
- sync_agents_from_config()
482
- else:
483
- print("⚠️ agents_config.json not found - using fallback minimal templates")
484
- print("💡 Creating essential templates for container environment")
485
- create_minimal_templates()
486
-
487
- if __name__ == "__main__":
488
- import argparse
489
-
490
- parser = argparse.ArgumentParser(description="SQLite Agent Template Management")
491
- parser.add_argument("action", choices=["sync", "list", "remove-all", "validate"],
492
- help="Action to perform")
493
-
494
- args = parser.parse_args()
495
-
496
- if args.action == "sync":
497
- print("🚀 Synchronizing agents from agents_config.json to SQLite...")
498
- sync_agents_from_config()
499
- elif args.action == "list":
500
- list_templates()
501
- elif args.action == "validate":
502
- validate_config()
503
- elif args.action == "remove-all":
504
- confirm = input("⚠️ Are you sure you want to remove ALL templates? (yes/no): ")
505
- if confirm.lower() == "yes":
506
- remove_all_templates()
507
- else:
508
- print("Operation cancelled.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/setup_analytics_data.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ from datetime import datetime, timedelta
4
+ import sqlite3
5
+
6
+ # Add parent directory to path so we can import modules
7
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
+
9
+ from src.db.schemas.models import ModelUsage, User
10
+ from src.db.init_db import session_factory, init_db
11
+ from scripts.generate_test_data import generate_test_data
12
+ from scripts.create_test_user import create_test_users
13
+
14
+ def check_database():
15
+ """Check if database exists and has the required tables"""
16
+ db_path = os.path.join(
17
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
18
+ "chat_database.db"
19
+ )
20
+
21
+ if not os.path.exists(db_path):
22
+ print(f"Database file not found at {db_path}")
23
+ print("Creating database...")
24
+ init_db()
25
+ return False
26
+
27
+ conn = sqlite3.connect(db_path)
28
+ cursor = conn.cursor()
29
+
30
+ # Check if model_usage table exists
31
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='model_usage'")
32
+ if not cursor.fetchone():
33
+ print("model_usage table does not exist.")
34
+ print("Creating tables...")
35
+ init_db()
36
+ return False
37
+
38
+ # Count records in model_usage table
39
+ cursor.execute("SELECT COUNT(*) FROM model_usage")
40
+ count = cursor.fetchone()[0]
41
+ print(f"Found {count} records in model_usage table")
42
+
43
+ conn.close()
44
+ return count > 0
45
+
46
+ def setup_analytics():
47
+ """Set up analytics data for testing"""
48
+ # Check database status
49
+ has_data = check_database()
50
+
51
+ # Create test users
52
+ print("\nCreating test users...")
53
+ create_test_users()
54
+
55
+ # Generate model usage data if needed
56
+ if not has_data:
57
+ print("\nGenerating model usage test data...")
58
+ generate_test_data(100)
59
+ else:
60
+ print("\nDatabase already has model usage data. Skipping generation.")
61
+ choice = input("Generate additional data anyway? (y/n): ")
62
+ if choice.lower() == 'y':
63
+ records = int(input("How many records to generate? [default: 100]: ") or "100")
64
+ generate_test_data(records)
65
+
66
+ # Verify data was created
67
+ session = session_factory()
68
+ try:
69
+ # Count model usage records
70
+ usage_count = session.query(ModelUsage).count()
71
+ print(f"\nTotal model usage records: {usage_count}")
72
+
73
+ # Count users
74
+ user_count = session.query(User).count()
75
+ print(f"Total users: {user_count}")
76
+
77
+ # Check recent data
78
+ yesterday = datetime.utcnow() - timedelta(days=1)
79
+ recent_count = session.query(ModelUsage).filter(ModelUsage.timestamp >= yesterday).count()
80
+ print(f"Records from the last 24 hours: {recent_count}")
81
+
82
+ # Get model breakdown
83
+ models = {}
84
+ providers = {}
85
+
86
+ for usage in session.query(ModelUsage).all():
87
+ if usage.model_name not in models:
88
+ models[usage.model_name] = 0
89
+ models[usage.model_name] += 1
90
+
91
+ if usage.provider not in providers:
92
+ providers[usage.provider] = 0
93
+ providers[usage.provider] += 1
94
+
95
+ print("\nModel breakdown:")
96
+ for model, count in models.items():
97
+ print(f" {model}: {count} records")
98
+
99
+ print("\nProvider breakdown:")
100
+ for provider, count in providers.items():
101
+ print(f" {provider}: {count} records")
102
+
103
+ finally:
104
+ session.close()
105
+
106
+ print("\nSetup complete!")
107
+ print("To access the analytics dashboard:")
108
+ print("1. Make sure the backend server is running")
109
+ print("2. In your browser, set localStorage.adminApiKey to 'default-admin-key-change-me'")
110
+ print(" (or the value in your ADMIN_API_KEY environment variable)")
111
+ print("3. Go to http://localhost:3000/analytics/dashboard")
112
+
113
+ if __name__ == "__main__":
114
+ setup_analytics()
scripts/test_agent_parsing.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def parse_agents(agent_string):
3
+ """
4
+ Parse a string containing agent names separated by ->, (, ), or commas
5
+ and return a list of agent names.
6
+ """
7
+ if not agent_string or not agent_string.strip():
8
+ return []
9
+
10
+ # Replace parentheses with spaces to handle cases with parentheses
11
+ import re
12
+ cleaned_string = re.sub(r'\(.*?\)', '', agent_string)
13
+
14
+ # Split by -> to get individual agent segments
15
+ agent_segments = cleaned_string.split('->')
16
+
17
+ # Process each segment to extract agent names
18
+ agents = []
19
+ for segment in agent_segments:
20
+ # Split by comma and strip whitespace
21
+ segment_agents = [agent.strip() for agent in segment.split(',') if agent.strip()]
22
+ agents.extend(segment_agents)
23
+
24
+ return agents
25
+
26
+ sample = "preprocessing_agent -> statistical_analytics_agent"
27
+ agents = parse_agents(sample)
28
+ print(agents)
29
+
30
+ # Test with different formats
31
+ test_samples = [
32
+ "preprocessing_agent -> data_viz_agent",
33
+ "preprocessing_agent(dataset, goal)",
34
+ "preprocessing_agent -> statistical_analytics_agent, data_viz_agent",
35
+ "preprocessing_agent, statistical_analytics_agent -> data_viz_agent",
36
+ "",
37
+ "preprocessing_agent(dataset, goal) -> data_viz_agent(dataset)"
38
+ ]
39
+
40
+ for test in test_samples:
41
+ print(f"Input: {test}")
42
+ print(f"Output: {parse_agents(test)}")
scripts/test_model_usage.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import time
3
+ import uuid
4
+ import os
5
+ import sys
6
+
7
+ # Add the parent directory to the path so we can import modules
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ from ai_manager import AI_Manager
11
+ from init_db import session_factory, ModelUsage
12
+
13
+ def test_ai_manager_directly():
14
+ """Test the AI Manager directly to see if it records usage"""
15
+ print("Starting direct AI Manager test...")
16
+
17
+ # Create a test AI Manager
18
+ ai_manager = AI_Manager()
19
+
20
+ # Generate a unique user ID and chat ID for testing
21
+ test_user_id = int(time.time()) % 10000
22
+ test_chat_id = int(uuid.uuid4().int % 100000)
23
+ test_model = "test-direct-model"
24
+
25
+ print(f"Using test user_id: {test_user_id}, chat_id: {test_chat_id}")
26
+
27
+ # Call save_usage_to_db directly
28
+ ai_manager.save_usage_to_db(
29
+ user_id=test_user_id,
30
+ chat_id=test_chat_id,
31
+ model_name=test_model,
32
+ provider="Test Provider",
33
+ prompt_tokens=150,
34
+ completion_tokens=250,
35
+ total_tokens=400,
36
+ query_size=600,
37
+ response_size=1200,
38
+ cost=0.002,
39
+ request_time_ms=200,
40
+ is_streaming=False
41
+ )
42
+
43
+ print("Usage data saved directly via AI Manager")
44
+
45
+ # Check if it was actually saved
46
+ session = session_factory()
47
+ try:
48
+ # Count records for our test user
49
+ count = session.query(ModelUsage).filter(
50
+ ModelUsage.user_id == test_user_id,
51
+ ModelUsage.model_name == test_model
52
+ ).count()
53
+
54
+ if count > 0:
55
+ print(f"SUCCESS: Found {count} records for test user {test_user_id}")
56
+ records = session.query(ModelUsage).filter(
57
+ ModelUsage.user_id == test_user_id
58
+ ).all()
59
+
60
+ for record in records:
61
+ print(f" - {record.usage_id}: {record.model_name}, {record.total_tokens} tokens, ${record.cost}")
62
+ else:
63
+ print(f"FAILURE: No records found for test user {test_user_id}")
64
+ print("Check the database connection and ModelUsage table")
65
+ finally:
66
+ session.close()
67
+
68
+ def test_api_endpoint():
69
+ """Test the API endpoint that creates test usage records"""
70
+ print("\nTesting API endpoint for creating test usage...")
71
+
72
+ admin_key = os.getenv("ADMIN_API_KEY", "admin-api-key-change-me")
73
+ base_url = "http://localhost:8000"
74
+
75
+ # Generate a unique user ID for testing
76
+ test_user_id = int(time.time()) % 10000
77
+ test_model = "test-api-model"
78
+
79
+ # Call the API endpoint
80
+ response = requests.post(
81
+ f"{base_url}/analytics/debug/create_test_usage",
82
+ params={
83
+ "user_id": test_user_id,
84
+ "model_name": test_model
85
+ },
86
+ headers={
87
+ "X-Admin-API-Key": admin_key
88
+ }
89
+ )
90
+
91
+ if response.status_code == 200:
92
+ data = response.json()
93
+ print(f"SUCCESS: Created test usage via API endpoint")
94
+ print(f"Response: {data}")
95
+
96
+ # Verify the record was created
97
+ verification = requests.get(
98
+ f"{base_url}/analytics/debug/user_usage/{test_user_id}",
99
+ headers={
100
+ "X-Admin-API-Key": admin_key
101
+ }
102
+ ).json()
103
+
104
+ print(f"User {test_user_id} has {verification.get('total_records', 0)} records")
105
+ if verification.get('recent_records'):
106
+ print(f"Recent records: {verification['recent_records']}")
107
+ else:
108
+ print(f"FAILURE: API endpoint returned status {response.status_code}")
109
+ print(f"Response: {response.text}")
110
+
111
+ if __name__ == "__main__":
112
+ # Run both tests
113
+ test_ai_manager_directly()
114
+ test_api_endpoint()
scripts/test_token_counting.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+ sys.path.append(str(Path(__file__).resolve().parent.parent))
4
+
5
+ from src.managers.ai_manager import AI_Manager
6
+
7
+ ai_manager = AI_Manager()
8
+
9
+ input_prompt = "What are the key insights from this data? "
10
+
11
+ output = """
12
+ ## Analytical Planner
13
+
14
+ ### Reasoning
15
+ To extract key insights from the housing dataset, it is essential to first preprocess the data to ensure its quality and suitability for analysis. Following preprocessing, performing statistical analysis will help identify significant patterns and relationships within the data. Finally, visualizing the results will provide a clear and interpretable representation of the insights derived.
16
+
17
+
18
+ ## Preprocessing Agent
19
+
20
+ ### Reasoning
21
+ To derive key insights from the Housing Dataset, it is essential to start with data preprocessing and introductory analysis. This involves categorizing the data into numeric and categorical features, handling missing values appropriately, and preparing the data for further exploration. By organizing the data effectively and understanding the relationships between different variables, we can uncover meaningful patterns and trends that inform our analysis objectives.
22
+
23
+ ### Code Implementation
24
+ ```python
25
+ import numpy as np
26
+ import pandas as pd
27
+
28
+ # Define a safe datetime conversion function
29
+ def safe_to_datetime(date):
30
+ try:
31
+ return pd.to_datetime(date, errors='coerce', cache=False)
32
+ except (ValueError, TypeError):
33
+ return pd.NaT
34
+
35
+ # Create copies of the original dataframe
36
+ df_copy = df.copy()
37
+
38
+ # Identify categorical and numeric columns
39
+ categorical_columns = df_copy.select_dtypes(include=['object', 'category']).columns.tolist()
40
+ numeric_columns = df_copy.select_dtypes(include=[np.number]).columns.tolist()
41
+
42
+ # Handle missing values
43
+ # For numeric columns, fill missing values with the median
44
+ df_copy[numeric_columns] = df_copy[numeric_columns].fillna(df_copy[numeric_columns].median())
45
+
46
+ # For categorical columns, fill missing values with the mode
47
+ for col in categorical_columns:
48
+ df_copy[col] = df_copy[col].fillna(df_copy[col].mode()[0])
49
+
50
+ # Convert string dates to datetime format if any date columns exist
51
+ # Example: If there's a column named 'date', uncomment the following lines
52
+ # if 'date' in df_copy.columns:
53
+ # df_copy['date'] = df_copy['date'].apply(safe_to_datetime)
54
+
55
+ # Create a correlation matrix for numeric columns
56
+ correlation_matrix = df_copy[numeric_columns].corr()
57
+
58
+ # Display the first few rows of the processed dataframe and the correlation matrix
59
+ print(df_copy.head())
60
+ print("\nCorrelation Matrix:\n", correlation_matrix)
61
+ ```
62
+
63
+ ### Summary
64
+ 1. **Import Libraries**: The necessary libraries, NumPy and Pandas, are imported to handle numerical operations and data manipulation.
65
+
66
+ 2. **Datetime Conversion Function**: A `safe_to_datetime` function is defined to gracefully handle the conversion of string dates to datetime objects, ensuring that any errors during conversion result in `NaT` (Not a Time) values without interrupting the workflow.
67
+
68
+ 3. **Dataframe Copy**: A copy of the original dataframe `df` is created to preserve the original data during preprocessing.
69
+
70
+ 4. **Identify Column Types**:
71
+ - **Categorical Columns**: Identified using `select_dtypes` to include data types `object` and `category`.
72
+ - **Numeric Columns**: Identified by selecting columns with numeric data types using `np.number`.
73
+
74
+ 5. **Handle Missing Values**:
75
+ - **Numeric Columns**: Missing values are filled with the median of each numeric column to maintain the central tendency without being skewed by outliers.
76
+ - **Categorical Columns**: Missing values are filled with the mode (most frequent value) to preserve the most common category.
77
+
78
+ 6. **Datetime Conversion**: Although the current dataset does not contain date columns, the preparation is made to handle such conversions if needed in the future by checking for date-specific columns and applying the `safe_to_datetime` function.
79
+
80
+ 7. **Correlation Matrix**: A correlation matrix is generated for the numeric columns to identify the strength and direction of relationships between different numerical variables, which is crucial for understanding how features interact with each other.
81
+
82
+ 8. **Display Outputs**: The first few rows of the processed dataframe and the correlation matrix are printed to provide an initial view of the data and its interrelationships, laying the groundwork for deeper exploratory data analysis.
83
+
84
+
85
+ ## Statistical Analytics Agent
86
+
87
+ ### Reasoning
88
+ To extract key insights from the Housing Dataset, we will perform a multiple linear regression analysis. This will allow us to identify which factors significantly influence housing prices. By analyzing both numerical variables (such as area, bedrooms, bathrooms) and categorical variables (like mainroad, furnishingstatus), we can determine the key drivers of price variations. Proper handling of missing values and categorical data will ensure the robustness of our analysis.
89
+
90
+ ### Code Implementation
91
+ ```python
92
+ import pandas as pd
93
+ import numpy as np
94
+ import statsmodels.api as sm
95
+
96
+ # Create a copy of the dataframe
97
+ df_copy = df.copy()
98
+
99
+ try:
100
+ # Define the dependent variable
101
+ y = df_copy['price']
102
+
103
+ # Define the independent variables
104
+ X = df_copy.drop('price', axis=1)
105
+
106
+ # Check for missing values and drop rows with any missing values
107
+ X = X.dropna()
108
+ y = y.loc[X.index]
109
+
110
+ # Convert categorical variables to 'category' dtype
111
+ categorical_columns = X.select_dtypes(include=['object', 'category']).columns
112
+ for col in categorical_columns:
113
+ X[col] = X[col].astype('category')
114
+
115
+ # Add a constant term to the predictors
116
+ X = sm.add_constant(X)
117
+
118
+ # Fit the OLS regression model
119
+ model = sm.OLS(y.astype(float), X.astype(float)).fit()
120
+
121
+ # Print the model summary
122
+ print(model.summary())
123
+
124
+ except Exception as e:
125
+ print(f"An error occurred: {e}")
126
+ ```
127
+
128
+ ### Summary
129
+ The code performs a multiple linear regression analysis to identify the key factors affecting housing prices. It begins by creating a copy of the original dataframe to preserve the data. The dependent variable `price` is separated from the independent variables. Rows with any missing values are removed to ensure data quality. Categorical variables are converted to the 'category' data type, which allows statsmodels to handle them appropriately in the regression. A constant term is added to the independent variables to account for the intercept in the model. The Ordinary Least Squares (OLS) regression model is then fitted using the prepared data. Finally, the summary of the regression model is printed, providing detailed statistics on the significance and impact of each predictor on the housing price.
130
+
131
+
132
+ ## Data Viz Agent
133
+
134
+ ### Reasoning
135
+ To extract key insights from the housing dataset, it's important to analyze the relationship between house area and price, as well as understand how factors like the number of bedrooms and bathrooms influence pricing. A scatter plot of area versus price can reveal trends and correlations, while color-coding by the number of bedrooms can provide additional depth to the analysis.
136
+
137
+ ### Code Implementation
138
+ ```python
139
+ import plotly.express as px
140
+
141
+ # Check if the dataset has more than 50,000 rows and sample if necessary
142
+ if len(df) > 50000:
143
+ df_sample = df.sample(n=50000)
144
+ else:
145
+ df_sample = df.copy()
146
+
147
+ # Create a scatter plot of area vs price, colored by number of bedrooms
148
+ fig = px.scatter(df_sample,
149
+ x='area',
150
+ y='price',
151
+ color='bedrooms',
152
+ title='Housing Price vs Area Colored by Number of Bedrooms',
153
+ labels={'area': 'Area (sq ft)', 'price': 'Price'},
154
+ hover_data=['bathrooms', 'stories'])
155
+
156
+ # Update layout for clarity and styling
157
+ fig.update_layout(
158
+ template='plotly_white',
159
+ xaxis_title='Area (sq ft)',
160
+ yaxis_title='Price',
161
+ legend_title='Bedrooms'
162
+ )
163
+
164
+ fig.to_html(full_html=False)
165
+ ```
166
+
167
+ ### Summary
168
+ The scatter plot visualizes the relationship between the area of the houses and their prices. By color-coding the points based on the number of bedrooms, it becomes easier to observe how bedroom count correlates with both area and price. This helps in identifying trends such as whether larger houses with more bedrooms tend to be priced higher.
169
+ """
170
+
171
+
172
+ input_tokens = len(ai_manager.tokenizer.encode(input_prompt))
173
+ output_tokens = len(ai_manager.tokenizer.encode(output))
174
+
175
+ print(f"Input tokens: {input_tokens}")
176
+ print(f"Output tokens: {output_tokens}")
scripts/tier_maker.py CHANGED
@@ -2,16 +2,8 @@ from src.utils.model_registry import MODEL_COSTS, MODEL_TIERS
2
 
3
  # divide models in 3 tiers based on cost per 1k tokens
4
  # tier 1: < $0.0005
5
- # tier 2: < $0.001
6
- # tier 3: > $0.05
7
- # tier 4: > $0.1
8
-
9
- TIERS_COST = {
10
- "tier1": 0.0005,
11
- "tier2": 0.001,
12
- "tier3": 0.05,
13
- "tier4": 0.1
14
- }
15
 
16
  def get_tier(model_name):
17
  for provider, models in MODEL_COSTS.items():
@@ -24,7 +16,7 @@ def get_tier_1():
24
  tier_1 = []
25
  for provider, models in MODEL_COSTS.items():
26
  for model, cost in models.items():
27
- if cost["input"] + cost["output"] < TIERS_COST["tier1"]:
28
  tier_1.append(model)
29
  return tier_1
30
 
@@ -32,7 +24,7 @@ def get_tier_2():
32
  tier_2 = []
33
  for provider, models in MODEL_COSTS.items():
34
  for model, cost in models.items():
35
- if cost["input"] + cost["output"] >= TIERS_COST["tier1"] and cost["input"] + cost["output"] < TIERS_COST["tier2"]:
36
  tier_2.append(model)
37
  return tier_2
38
 
@@ -40,17 +32,9 @@ def get_tier_3():
40
  tier_3 = []
41
  for provider, models in MODEL_COSTS.items():
42
  for model, cost in models.items():
43
- if cost["input"] + cost["output"] >= TIERS_COST["tier2"] and cost["input"] + cost["output"] < TIERS_COST["tier3"]:
44
  tier_3.append(model)
45
- return tier_3
46
-
47
- def get_tier_4():
48
- tier_4 = []
49
- for provider, models in MODEL_COSTS.items():
50
- for model, cost in models.items():
51
- if cost["input"] + cost["output"] >= TIERS_COST["tier3"]:
52
- tier_4.append(model)
53
- return tier_4
54
 
55
  # Print current tier definitions from registry
56
  import json
@@ -74,11 +58,6 @@ model_tiers = {
74
  "name": "Premium",
75
  "credits": 5,
76
  "models": get_tier_3()
77
- },
78
- "tier4": {
79
- "name": "Premium Plus",
80
- "credits": 10,
81
- "models": get_tier_4()
82
  }
83
  }
84
 
 
2
 
3
  # divide models in 3 tiers based on cost per 1k tokens
4
  # tier 1: < $0.0005
5
+ # tier 2: $0.0005 - $0.001
6
+ # tier 3: > $0.001
 
 
 
 
 
 
 
 
7
 
8
  def get_tier(model_name):
9
  for provider, models in MODEL_COSTS.items():
 
16
  tier_1 = []
17
  for provider, models in MODEL_COSTS.items():
18
  for model, cost in models.items():
19
+ if cost["input"] + cost["output"] < 0.0005:
20
  tier_1.append(model)
21
  return tier_1
22
 
 
24
  tier_2 = []
25
  for provider, models in MODEL_COSTS.items():
26
  for model, cost in models.items():
27
+ if cost["input"] + cost["output"] >= 0.0005 and cost["input"] + cost["output"] < 0.001:
28
  tier_2.append(model)
29
  return tier_2
30
 
 
32
  tier_3 = []
33
  for provider, models in MODEL_COSTS.items():
34
  for model, cost in models.items():
35
+ if cost["input"] + cost["output"] >= 0.001:
36
  tier_3.append(model)
37
+ return tier_3
 
 
 
 
 
 
 
 
38
 
39
  # Print current tier definitions from registry
40
  import json
 
58
  "name": "Premium",
59
  "credits": 5,
60
  "models": get_tier_3()
 
 
 
 
 
61
  }
62
  }
63
 
scripts/verify_session_state.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import uuid
3
+ import time
4
+
5
+ def test_session_workflow():
6
+ # Base URL
7
+ base_url = "http://localhost:8000"
8
+
9
+ # Create a unique session ID
10
+ session_id = str(uuid.uuid4())
11
+ print(f"Testing with session ID: {session_id}")
12
+
13
+ # Step 1: Create a user and associate with session
14
+ print("\n1. Creating user...")
15
+ username = f"test_user_{session_id[:8]}"
16
+ email = f"{username}@example.com"
17
+ login_response = requests.post(
18
+ f"{base_url}/auth/login",
19
+ json={"username": username, "email": email, "session_id": session_id}
20
+ ).json()
21
+ print(f"Login response: {login_response}")
22
+
23
+ # Step 2: Verify session state
24
+ print("\n2. Verifying session state...")
25
+ session_state = requests.get(f"{base_url}/debug/session/{session_id}").json()
26
+ print(f"Session state: {session_state}")
27
+
28
+ # Step 3: Make a chat request with this session
29
+ print("\n3. Making a test chat request...")
30
+ chat_response = requests.post(
31
+ f"{base_url}/chat/data_explorer",
32
+ json={"query": "Show me a summary of the dataset"},
33
+ params={"session_id": session_id}
34
+ ).json()
35
+ print(f"Chat response received: {len(str(chat_response))} bytes")
36
+
37
+ # Step 4: Verify session state again
38
+ print("\n4. Verifying session state after chat...")
39
+ session_state = requests.get(f"{base_url}/debug/session/{session_id}").json()
40
+ print(f"Updated session state: {session_state}")
41
+
42
+ # Step 5: Check analytics data
43
+ print("\n5. Checking analytics data...")
44
+ time.sleep(1) # Wait a moment for data to be saved
45
+ admin_key = "default-admin-key-change-me" # Adjust to your admin key
46
+
47
+ # Check general model usage
48
+ analytics_response = requests.get(
49
+ f"{base_url}/analytics/debug/model_usage",
50
+ headers={"X-Admin-API-Key": admin_key}
51
+ ).json()
52
+ print(f"Total records in database: {analytics_response.get('total_records', 0)}")
53
+
54
+ if 'sample_records' in analytics_response and analytics_response['sample_records']:
55
+ print(f"Latest usage records: {analytics_response['sample_records']}")
56
+ else:
57
+ print("No sample records found in general model usage")
58
+
59
+ # Check user-specific usage
60
+ user_id = login_response.get('user_id')
61
+ if user_id:
62
+ user_usage = requests.get(
63
+ f"{base_url}/analytics/debug/user_usage/{user_id}",
64
+ headers={"X-Admin-API-Key": admin_key}
65
+ ).json()
66
+ print(f"User {user_id} has {user_usage.get('total_records', 0)} usage records")
67
+
68
+ if user_usage.get('recent_records'):
69
+ print(f"Recent usage: {user_usage['recent_records']}")
70
+ else:
71
+ print(f"No usage records found for user {user_id}")
72
+
73
+ print("\nTest completed!")
74
+
75
+ if __name__ == "__main__":
76
+ test_session_workflow()
src/agents/agents.py CHANGED
The diff for this file is too large to render. See raw diff
 
src/agents/deep_agents.py DELETED
@@ -1,1110 +0,0 @@
1
- import asyncio
2
- import ast
3
- import json
4
- import os
5
- import dspy
6
- import numpy as np
7
- import pandas as pd
8
- from dotenv import load_dotenv
9
- from src.utils.logger import Logger
10
- import logging
11
- import datetime
12
- import re
13
- import textwrap
14
-
15
- def clean_print_statements(code_block):
16
- """
17
- This function cleans up any `print()` statements that might contain unwanted `\n` characters.
18
- It ensures print statements are properly formatted without unnecessary newlines.
19
- """
20
- # This regex targets print statements, even if they have newlines inside
21
- return re.sub(r'print\((.*?)(\\n.*?)(.*?)\)', r'print(\1\3)', code_block, flags=re.DOTALL)
22
-
23
-
24
- def clean_unicode_chars(text):
25
- """
26
- Clean Unicode characters that might cause encoding issues.
27
- Replaces common Unicode characters with ASCII equivalents.
28
- """
29
- if not isinstance(text, str):
30
- return text
31
-
32
- # Replace common Unicode characters with ASCII equivalents
33
- replacements = {
34
- '\u2192': ' -> ', # Right arrow
35
- '\u2190': ' <- ', # Left arrow
36
- '\u2194': ' <-> ', # Left-right arrow
37
- '\u2500': '-', # Box drawing horizontal
38
- '\u2502': '|', # Box drawing vertical
39
- '\u2026': '...', # Ellipsis
40
- '\u2013': '-', # En dash
41
- '\u2014': '-', # Em dash
42
- '\u201c': '"', # Left double quotation mark
43
- '\u201d': '"', # Right double quotation mark
44
- '\u2018': "'", # Left single quotation mark
45
- '\u2019': "'", # Right single quotation mark
46
- }
47
-
48
- for unicode_char, ascii_replacement in replacements.items():
49
- text = text.replace(unicode_char, ascii_replacement)
50
-
51
- # Remove any remaining non-ASCII characters
52
- text = text.encode('ascii', 'ignore').decode('ascii')
53
-
54
- return text
55
-
56
-
57
- def remove_main_block(code):
58
- # Match the __main__ block
59
- pattern = r'(?m)^if\s+__name__\s*==\s*["\']__main__["\']\s*:\s*\n((?:\s+.*\n?)*)'
60
-
61
- match = re.search(pattern, code)
62
- if match:
63
- main_block = match.group(1)
64
-
65
- # Dedent the code block inside __main__
66
- dedented_block = textwrap.dedent(main_block)
67
-
68
- # Remove \n from any print statements in the block (also handling multiline print cases)
69
- dedented_block = clean_print_statements(dedented_block)
70
- # Replace the block in the code
71
- cleaned_code = re.sub(pattern, dedented_block, code)
72
-
73
- # Optional: Remove leading newlines if any
74
- cleaned_code = cleaned_code.strip()
75
-
76
- return cleaned_code
77
- return code
78
-
79
-
80
- # Configure Plotly to prevent auto-display
81
- def configure_plotly_no_display():
82
- """Configure Plotly to prevent automatic browser display"""
83
- try:
84
- import plotly.io as pio
85
-
86
- # Set environment variables to prevent browser opening
87
- os.environ['BROWSER'] = ''
88
- os.environ['PLOTLY_RENDERER'] = 'json'
89
-
90
- # Configure Plotly renderers
91
- pio.renderers.default = 'json'
92
- pio.templates.default = 'plotly_white'
93
-
94
- # Disable Kaleido auto-display if available
95
- try:
96
- import plotly.graph_objects as go
97
- # Configure figure defaults to not auto-display
98
- go.Figure.show = lambda self, *args, **kwargs: None
99
- except ImportError:
100
- pass
101
-
102
- except ImportError:
103
- print("Warning: Plotly not available for configuration")
104
-
105
- # Call the configuration function immediately
106
- configure_plotly_no_display()
107
-
108
- logger = Logger("deep_agents", see_time=True, console_log=True, level=logging.DEBUG)
109
- load_dotenv()
110
-
111
- class deep_questions(dspy.Signature):
112
- """
113
- You are a data analysis assistant.
114
- Your role is to take a user's high-level analytical goal and generate a set of deep, targeted follow-up questions. These questions should guide an analyst toward a more thorough understanding of the goal by encouraging exploration, segmentation, and causal reasoning.
115
- Instructions:
116
- - Generate up to 5 insightful, data-relevant questions.
117
- - Use the dataset structure to tailor your questions (e.g., look at the available columns, data types, and what kind of information they can reveal).
118
- - The questions should help the user decompose their analytic goal and explore it from multiple angles (e.g., time trends, customer segments, usage behavior, external factors, feedback).
119
- - Each question should be specific enough to guide actionable analysis or investigation.
120
- - Use a clear and concise style, but maintain depth.
121
- Inputs:
122
- - goal: The user's analytical goal or main question they want to explore
123
- - dataset_info: A description of the dataset the user is querying, including:
124
- - What the dataset represents
125
- - Key columns and their data types
126
- Output:
127
- - deep_questions: A list of up to 5 specific, data-driven questions that support the analytic goal
128
- ---
129
- Example:
130
- Analytical Goal:
131
- Understand why churn has been rising
132
- Dataset Info:
133
- Customer Retention Dataset tracking subscription activity over time.
134
- Columns:
135
- - customer_id (string)
136
- - join_date (date)
137
- - churn_date (date, nullable)
138
- - is_churned (boolean)
139
- - plan_type (string: 'basic', 'premium', 'enterprise')
140
- - region (string)
141
- - last_login_date (date)
142
- - avg_weekly_logins (float)
143
- - support_tickets_last_30d (int)
144
- - satisfaction_score (float, 0–10 scale)
145
- Decomposed Questions:
146
- 1. How has the churn rate changed month-over-month, and during which periods was the increase most pronounced?
147
- 2. Are specific plan types or regions showing a higher churn rate relative to others?
148
- 3. What is the average satisfaction score and support ticket count among churned users compared to retained users?
149
- 4. Do churned users exhibit different login behavior (e.g., avg_weekly_logins) in the weeks leading up to their churn date?
150
- 5. What is the tenure distribution (time from join_date to churn_date) among churned customers, and are short-tenure users more likely to churn?
151
- """
152
- goal = dspy.InputField(desc="User analytical goal — what main insight or question they want to answer")
153
- dataset_info = dspy.InputField(desc="A description of the dataset: what it represents, and the main columns with data types")
154
- deep_questions:str = dspy.OutputField(desc="A list of up to five questions that help deeply explore the analytical goal using the dataset")
155
-
156
- class deep_synthesizer(dspy.Signature):
157
- """
158
- You are a data analysis synthesis expert.
159
- Your job is to take the outputs from a multi-agent data analytics system - including the original user query, the code summaries from each agent, and the actual printed results from running those code blocks - and synthesize them into a comprehensive, well-structured final report.
160
- This report should:
161
- - Explain what steps were taken and why (based on the query)
162
- - Summarize the code logic used by each agent, without including raw code
163
- - Highlight key findings and results from the code outputs
164
- - Offer clear, actionable insights tied back to the user's original question
165
- - Be structured, readable, and suitable for decision-makers or analysts
166
- Instructions:
167
- - Begin with a brief restatement of the original query and what it aimed to solve
168
- - Organize your report step-by-step or by analytical theme (e.g., segmentation, trend analysis, etc.)
169
- - For each part, summarize what was analyzed, how (based on code summaries), and what the result was (based on printed output)
170
- - End with a final set of synthesized conclusions and potential next steps or recommendations
171
- Inputs:
172
- - query: The user's original analytical question or goal
173
- - summaries: A list of natural language descriptions of what each agent's code did
174
- - print_outputs: A list of printed outputs (results) from running each agent's code
175
- Output:
176
- - synthesized_report: A structured and readable report that ties all parts together, grounded in the code logic and results
177
- Example use:
178
- You are not just summarizing outputs - you're telling a story that answers the user's query using real data.
179
- """
180
-
181
- query = dspy.InputField(desc="The original user query or analytical goal")
182
- summaries = dspy.InputField(desc="List of code summaries - each describing what a particular agent's code did")
183
- print_outputs = dspy.InputField(desc="List of print outputs - the actual data insights generated by the code")
184
- synthesized_report = dspy.OutputField(desc="The final, structured report that synthesizes all the information into clear insights")
185
-
186
- def clean_and_store_code(code, session_datasets=None):
187
- """
188
- Cleans and stores code execution results in a standardized format.
189
-
190
- Args:
191
- code (str): Raw code text to execute
192
- session_datasets (dict): Dictionary of datasets from session state
193
-
194
- Returns:
195
- dict: Execution results containing printed_output, plotly_figs, and error info
196
- """
197
- import io
198
- import sys
199
- import re
200
- import plotly.express as px
201
- import plotly.graph_objects as go
202
- from plotly.subplots import make_subplots
203
- import plotly.io as pio
204
-
205
- # Make all session datasets available globally if provided
206
- if session_datasets is not None:
207
- for dataset_name, dataset_df in session_datasets.items():
208
- globals()[dataset_name] = dataset_df
209
-
210
- # Initialize output containers
211
- output_dict = {
212
- 'exec_result': None,
213
- 'printed_output': '',
214
- 'plotly_figs': [],
215
- 'error': None
216
- }
217
-
218
- try:
219
- # Clean the code
220
- cleaned_code = code.strip()
221
-
222
- cleaned_code = cleaned_code.replace('```python', '').replace('```', '')
223
-
224
-
225
- # Fix try statement syntax
226
- cleaned_code = cleaned_code.replace('try\n', 'try:\n')
227
-
228
- # Remove code patterns that would make the code unrunnable
229
- invalid_patterns = [
230
- '```', # Code block markers
231
- '\\n', # Raw newlines
232
- '\\t', # Raw tabs
233
- '\\r', # Raw carriage returns
234
- ]
235
-
236
- for pattern in invalid_patterns:
237
- if pattern in cleaned_code:
238
- cleaned_code = cleaned_code.replace(pattern, '')
239
-
240
-
241
- # Remove reading the csv file if it's already in the context
242
- cleaned_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', cleaned_code)
243
-
244
- # Only match assignments at top level (not indented)
245
- # 1. Remove 'df = pd.DataFrame()' if it's at the top level
246
- cleaned_code = re.sub(
247
- r"^df\s*=\s*pd\.DataFrame\(\s*\)\s*(#.*)?$",
248
- '',
249
- cleaned_code,
250
- flags=re.MULTILINE
251
- )
252
- cleaned_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', cleaned_code)
253
- # Remove all .show() method calls more comprehensively
254
- cleaned_code = re.sub(r'\b\w*\.show\(\)', '', cleaned_code)
255
- cleaned_code = re.sub(r'^\s*\w*fig\w*\.show\(\)\s*;?\s*$', '', cleaned_code, flags=re.MULTILINE)
256
-
257
- # Additional patterns to catch more .show() variations
258
- cleaned_code = re.sub(r'\.show\(\s*\)', '', cleaned_code) # .show() with optional spaces
259
- cleaned_code = re.sub(r'\.show\(\s*renderer\s*=\s*[\'"][^\'\"]*[\'"]\s*\)', '', cleaned_code) # .show(renderer='...')
260
- cleaned_code = re.sub(r'plotly_figs\[\d+\]\.show\(\)', '', cleaned_code) # plotly_figs[0].show()
261
-
262
- # More comprehensive patterns
263
- cleaned_code = re.sub(r'\.show\([^)]*\)', '', cleaned_code) # .show(any_args)
264
- cleaned_code = re.sub(r'fig\w*\.show\(\s*[^)]*\s*\)', '', cleaned_code) # fig*.show(any_args)
265
- cleaned_code = re.sub(r'\w+_fig\w*\.show\(\s*[^)]*\s*\)', '', cleaned_code) # *_fig*.show(any_args)
266
-
267
- cleaned_code = remove_main_block(cleaned_code)
268
-
269
- # Clean Unicode characters that might cause encoding issues
270
- cleaned_code = clean_unicode_chars(cleaned_code)
271
-
272
- # Capture printed output
273
- old_stdout = sys.stdout
274
- captured_output = io.StringIO()
275
- sys.stdout = captured_output
276
-
277
- # Create execution environment with common imports and session data
278
- exec_globals = {
279
- '__builtins__': __builtins__,
280
- 'pd': __import__('pandas'),
281
- 'np': __import__('numpy'),
282
- 'px': px,
283
- 'go': go,
284
- 'make_subplots': make_subplots,
285
- 'plotly_figs': [],
286
- 'print': print,
287
- }
288
-
289
- # Add session DataFrame if available
290
- if session_datasets is not None:
291
- for dataset_name, dataset_df in session_datasets.items():
292
- exec_globals[dataset_name] = dataset_df
293
-
294
- # Add other common libraries that might be needed
295
- try:
296
- exec_globals['sm'] = __import__('statsmodels.api', fromlist=[''])
297
- exec_globals['train_test_split'] = __import__('sklearn.model_selection', fromlist=['train_test_split']).train_test_split
298
- exec_globals['LinearRegression'] = __import__('sklearn.linear_model', fromlist=['LinearRegression']).LinearRegression
299
- exec_globals['mean_absolute_error'] = __import__('sklearn.metrics', fromlist=['mean_absolute_error']).mean_absolute_error
300
- exec_globals['r2_score'] = __import__('sklearn.metrics', fromlist=['r2_score']).r2_score
301
- exec_globals['LabelEncoder'] = __import__('sklearn.preprocessing', fromlist=['LabelEncoder']).LabelEncoder
302
- exec_globals['warnings'] = __import__('warnings')
303
- except ImportError as e:
304
- print(f"Warning: Could not import some optional libraries: {e}")
305
-
306
- # Execute the code
307
- exec(cleaned_code, exec_globals)
308
-
309
- # Restore stdout
310
- sys.stdout = old_stdout
311
-
312
- # Get the captured output
313
- printed_output = captured_output.getvalue()
314
- output_dict['printed_output'] = printed_output
315
- # Extract plotly figures from the execution environment
316
- if 'plotly_figs' in exec_globals:
317
- plotly_figs = exec_globals['plotly_figs']
318
- if isinstance(plotly_figs, list):
319
- output_dict['plotly_figs'] = plotly_figs
320
- else:
321
- output_dict['plotly_figs'] = [plotly_figs] if plotly_figs else []
322
-
323
- # Also check for any figure variables that might have been created
324
- for var_name, var_value in exec_globals.items():
325
- if hasattr(var_value, 'to_json') and hasattr(var_value, 'show'):
326
- # This looks like a Plotly figure
327
- if var_value not in output_dict['plotly_figs']:
328
- output_dict['plotly_figs'].append(var_value)
329
-
330
- except Exception as e:
331
- # Restore stdout in case of error
332
- sys.stdout = old_stdout
333
- error_msg = str(e)
334
- output_dict['error'] = error_msg
335
- output_dict['printed_output'] = f"Error executing code: {error_msg}"
336
- print(f"Code execution error: {error_msg}")
337
-
338
- return output_dict
339
-
340
- def score_code(args, code, datasets=None):
341
- """
342
- Cleans and stores code execution results in a standardized format.
343
- Safely handles execution errors and returns clean output even if execution fails.
344
- Ensures plotly figures are properly created and captured.
345
-
346
- Args:
347
- args: Arguments (unused but required for dspy.Refine)
348
- code: Code object with combined_code attribute
349
- datasets: Dictionary of datasets from session state (optional)
350
-
351
- Returns:
352
- int: Score (0=error, 1=success, 2=success with plots)
353
- """
354
-
355
- code_text = code.combined_code
356
- try:
357
- # Fix try statement syntax
358
- code_text = code_text.replace('try\n', 'try:\n')
359
- code_text = code_text.replace('```python', '').replace('```', '')
360
-
361
-
362
- # Remove code patterns that would make the code unrunnable
363
- invalid_patterns = [
364
- '```', '\\n', '\\t', '\\r'
365
- ]
366
-
367
- for pattern in invalid_patterns:
368
- if pattern in code_text:
369
- code_text = code_text.replace(pattern, '')
370
-
371
- cleaned_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', code_text)
372
- # Remove all .show() method calls more comprehensively
373
- cleaned_code = re.sub(r'\b\w*\.show\(\)', '', cleaned_code)
374
- cleaned_code = re.sub(r'^\s*\w*fig\w*\.show\(\)\s*;?\s*$', '', cleaned_code, flags=re.MULTILINE)
375
-
376
- # Additional patterns to catch more .show() variations
377
- cleaned_code = re.sub(r'\.show\(\s*\)', '', cleaned_code) # .show() with optional spaces
378
- cleaned_code = re.sub(r'\.show\(\s*renderer\s*=\s*[\'"][^\'\"]*[\'"]\s*\)', '', cleaned_code) # .show(renderer='...')
379
- cleaned_code = re.sub(r'plotly_figs\[\d+\]\.show\(\)', '', cleaned_code) # plotly_figs[0].show()
380
-
381
- # More comprehensive patterns
382
- cleaned_code = re.sub(r'\.show\([^)]*\)', '', cleaned_code) # .show(any_args)
383
- cleaned_code = re.sub(r'fig\w*\.show\(\s*[^)]*\s*\)', '', cleaned_code) # fig*.show(any_args)
384
- cleaned_code = re.sub(r'\w+_fig\w*\.show\(\s*[^)]*\s*\)', '', cleaned_code) # *_fig*.show(any_args)
385
-
386
- cleaned_code = remove_main_block(cleaned_code)
387
-
388
- # Capture stdout using StringIO
389
- from io import StringIO
390
- import sys
391
- import plotly.graph_objects as go
392
- import pandas as pd
393
- import numpy as np
394
-
395
- stdout_capture = StringIO()
396
- original_stdout = sys.stdout
397
- sys.stdout = stdout_capture
398
-
399
- # Execute code in a new namespace with datasets available
400
- local_vars = {}
401
-
402
- # Add datasets to the execution context if provided
403
- if datasets:
404
- local_vars.update(datasets)
405
-
406
- # Add common imports to the execution context
407
- local_vars.update({
408
- 'pd': pd,
409
- 'np': np,
410
- 'go': go,
411
- 'plt': __import__('matplotlib.pyplot'),
412
- 'sns': __import__('seaborn'),
413
- })
414
-
415
- exec(cleaned_code, globals(), local_vars)
416
-
417
- # Capture any plotly figures from local namespace
418
- plotly_figs = []
419
- for var_name, var in local_vars.items():
420
- if isinstance(var, go.Figure):
421
- if not var.layout.title:
422
- var.update_layout(title=f"Figure {len(plotly_figs) + 1}")
423
- if not var.layout.template:
424
- var.update_layout(template="plotly_white")
425
- plotly_figs.append(var)
426
- elif isinstance(var, (list, tuple)):
427
- for item in var:
428
- if isinstance(item, go.Figure):
429
- if not item.layout.title:
430
- item.update_layout(title=f"Figure {len(plotly_figs) + 1}")
431
- if not item.layout.template:
432
- item.update_layout(template="plotly_white")
433
- plotly_figs.append(item)
434
-
435
- # Restore stdout and get captured output
436
- sys.stdout = original_stdout
437
- captured_output = stdout_capture.getvalue()
438
- stdout_capture.close()
439
-
440
- # Calculate score based on execution and plot generation
441
- score = 2 if plotly_figs else 1
442
-
443
- return score
444
-
445
- except Exception as e:
446
- # Restore stdout in case of error
447
- if 'stdout_capture' in locals():
448
- sys.stdout = original_stdout
449
- stdout_capture.close()
450
-
451
- return 0
452
-
453
-
454
- class deep_planner(dspy.Signature):
455
- """
456
- You are an advanced multi-question planning agent. Your task is to generate the most optimized and minimal plan
457
- to answer up to 5 analytical questions using available agents.
458
- Your responsibilities:
459
- 1. Feasibility: Verify that the goal is achievable using the provided datasets and agent descriptions.
460
- 2. Optimization:
461
- - Batch up to 2 similar questions per agent call.
462
- - Reuse outputs across questions wherever possible.
463
- - Avoid unnecessary agents or redundant processing.
464
- - Minimize total agent calls while preserving correctness.
465
- 3. Clarity:
466
- - Define clear variable usage (create/use).
467
- - Specify concise step-by-step instructions per agent.
468
- - Use dependency arrows (->) to indicate required agent outputs used by others.
469
- Inputs:
470
- - deep_questions: A list of up to 5 deep analytical questions (e.g., ["q1", "q2", ..., "q5"])
471
- - dataset: The available dataset(s) in memory or context
472
- - agents_desc: Dictionary containing each agent's name and its capabilities or descriptions
473
- Outputs:
474
- - plan_instructions: Detailed per-agent variable flow and functionality in the format:
475
- {
476
- "agent_x": {
477
- "create": ["cleaned_data: DataFrame - cleaned version of the input dataset"],
478
- "use": ["df: DataFrame - raw input dataset"],
479
- "instruction": "Clean the dataset by handling null values and standardizing formats."
480
- },
481
- "agent_y": {
482
- "create": ["analysis_results: dict - results of correlation analysis"],
483
- "use": ["cleaned_data: DataFrame - output from @agent_x"],
484
- "instruction": "Perform correlation analysis to identify strong predictors."
485
- }
486
- }
487
- Output Goal:
488
- Generate a small, clean, optimized execution plan using minimal agent calls, reusable outputs, and well-structured dependencies.
489
- USE THE EXACT NAME OF THE AGENTS IN THE INSTRUCTIONS
490
- """
491
-
492
- deep_questions = dspy.InputField(desc="List of up to 5 deep analytical questions to answer")
493
- dataset = dspy.InputField(desc="Available datasets, use 'df' as the working dataset")
494
- agents_desc = dspy.InputField(desc="Descriptions of available agents and their functions")
495
- plan_instructions = dspy.OutputField(desc="Variable-level instructions for each agent used in the plan")
496
-
497
- class deep_plan_fixer(dspy.Signature):
498
- """
499
- You are a plan instruction fixer agent. Your task is to take potentially malformed plan instructions
500
- and convert them into a properly structured dictionary format that can be safely evaluated.
501
- Your responsibilities:
502
- 1. Parse and validate the input plan instructions
503
- 2. Convert the instructions into a proper dictionary format
504
- 3. Ensure all agent instructions follow the required structure:
505
- {
506
- "@agent_name": {
507
- "create": ["variable: type - description"],
508
- "use": ["variable: type - description"],
509
- "instruction": "clear instruction text"
510
- }
511
- }
512
- 4. Handle any malformed or missing components
513
- 5. Return a properly formatted dictionary string that can be safely evaluated
514
- Inputs:
515
- - plan_instructions: The potentially malformed plan instructions to fix
516
- Outputs:
517
- - fixed_plan: A properly formatted dictionary string that can be safely evaluated
518
- """
519
-
520
- plan_instructions = dspy.InputField(desc="The potentially malformed plan instructions to fix")
521
- fixed_plan = dspy.OutputField(desc="Properly formatted dictionary string that can be safely evaluated")
522
-
523
- class final_conclusion(dspy.Signature):
524
- """
525
- You are a high-level analytics reasoning engine.
526
- Your task is to take multiple synthesized analytical results (each answering part of the original query) and produce a cohesive final conclusion that directly addresses the user's original question.
527
- This is not just a summary — it's a judgment. Use evidence from the synthesized findings to:
528
- - Answer the original question with clarity
529
- - Highlight the most important insights
530
- - Offer any causal reasoning or patterns discovered
531
- - Suggest next steps or strategic recommendations where appropriate
532
- Instructions:
533
- - Focus on relevance to the original query
534
- - Do not just repeat what the synthesized sections say — instead, infer, interpret, and connect dots
535
- - Prioritize clarity and insight over detail
536
- - End with a brief "Next Steps" section if applicable
537
- Inputs:
538
- - query: The original user question or goal
539
- - synthesized_sections: A list of synthesized result sections from the deep_synthesizer step (each covering part of the analysis)
540
- Output:
541
- - final_summary: A cohesive final conclusion that addresses the query, draws insight, and offers high-level guidance
542
- ---
543
- Example Output Structure:
544
- **Conclusion**
545
- Summarize the overall answer to the user's question, using the most compelling evidence across the synthesized sections.
546
- **Key Takeaways**
547
- - Bullet 1
548
- - Bullet 2
549
- - Bullet 3
550
- **Recommended Next Steps**
551
- (Optional based on context)
552
- """
553
-
554
- query = dspy.InputField(desc="The user's original query or analytical goal")
555
- synthesized_sections = dspy.InputField(desc="List of synthesized outputs — each one corresponding to a sub-part of the analysis")
556
- final_conclusion = dspy.OutputField(desc="A cohesive, conclusive answer that addresses the query and integrates key insights")
557
-
558
-
559
-
560
-
561
- class deep_code_synthesizer(dspy.Signature):
562
- """
563
- You are a code synthesis and optimization engine that combines and fixes code from multiple analytical agents.
564
- Your task is to take code outputs from preprocessing, statistical analysis, machine learning, and visualization agents, then:
565
- - Combine them into a single, coherent analysis pipeline
566
- - Fix any errors or inconsistencies between agent outputs
567
- - Ensure proper data flow between steps
568
- - Optimize the combined code for efficiency
569
- - Add necessary imports and dependencies
570
- - Handle any data type mismatches or conversion issues
571
- - Validate and normalize data types between agent outputs (e.g., ensure DataFrame operations maintain DataFrame type)
572
- - Convert between common data structures (lists, dicts, DataFrames) as needed
573
- - Add type hints and validation checks
574
- - Ensure consistent variable naming across agents
575
- - Ensure all visualizations use Plotly exclusively
576
- - Create comprehensive visualizations that show all important variables and relationships
577
- - Store all Plotly figures in a list for later use in the report
578
- Instructions:
579
- - Review each agent's code for correctness and completeness
580
- - Ensure variables are properly passed between steps with consistent types
581
- - Fix any syntax errors or logical issues
582
- - Add error handling and type validation where needed
583
- - Optimize code structure and performance
584
- - Maintain consistent coding style
585
- - Add clear comments explaining the analysis flow
586
- - Add data type conversion functions where needed
587
- - Validate input/output types between agent steps
588
- - Handle edge cases where agents might return different data structures
589
- - Convert any non-Plotly visualizations to Plotly format
590
- - Ensure all important variables are visualized appropriately
591
- - Store all Plotly figures in a list called plotly_figs
592
- - Include appropriate titles, labels, and legends for all visualizations
593
- - Use consistent styling across all Plotly visualizations
594
- - DONOT COMMENT OUT ANYTHING AS THE CODE SHOULD RUN & SHOW OUTPUTS
595
- - THE DATASET IS ALREADY LOADED, DON'T CREATE FAKE DATA. 'df' is always loaded
596
- Inputs:
597
- - deep_questions- The five deep questions this system is answering
598
- - dataset_info - Information about the dataset structure and types
599
- - planner_instructions - the plan according to the planner, ensure that the final code makes everything coherent
600
- - code - List of all agent code
601
- Output:
602
- - combined_code: - A single, optimized Python script that combines all analysis steps with proper type handling and Plotly visualizations
603
- """
604
- deep_questions = dspy.InputField(desc="The five deep questions this system is answering")
605
- dataset_info = dspy.InputField(desc="Information about the dataset")
606
- planner_instructions = dspy.InputField(desc="The planner instructions for each")
607
- code = dspy.InputField(desc="The code generated by all agents")
608
- combined_code = dspy.OutputField(desc="A single, optimized Python script that combines all analysis steps")
609
-
610
- class deep_code_fix(dspy.Signature):
611
- """
612
- You are a code debugging and fixing agent that analyzes and repairs code errors.
613
-
614
- Your task is to:
615
- - Analyze error messages and identify root causes
616
- - Fix syntax errors, logical issues, and runtime problems
617
- - Ensure proper data type handling and conversions
618
- - Add appropriate error handling and validation
619
- - Maintain code style and documentation
620
- - Preserve the original analysis intent
621
-
622
- Instructions:
623
- - Carefully analyze the error message and stack trace
624
- - Identify the specific line(s) causing the error
625
- - Determine if the issue is syntax, logic, or runtime related
626
- - Fix the code while maintaining its original purpose
627
- - Add appropriate error handling if needed
628
- - Ensure the fix doesn't introduce new issues
629
- - Document the changes made
630
-
631
- Inputs:
632
- - code: The code that generated the error
633
- - error: The error message and stack trace
634
-
635
- Output:
636
- - fixed_code: The repaired code with error handling
637
- - fix_explanation: Explanation of what was fixed and why
638
- """
639
- code = dspy.InputField(desc="The code that generated the error")
640
- error = dspy.InputField(desc="The error message and stack trace")
641
- fixed_code = dspy.OutputField(desc="The repaired code with error handling")
642
- fix_explanation = dspy.OutputField(desc="Explanation of what was fixed and why")
643
-
644
-
645
- chart_instructions = """
646
- Chart Styling Guidelines:
647
- 1. General Styling:
648
- - Use a clean, professional color palette (e.g., Tableau, ColorBrewer)
649
- - Include clear titles and axis labels
650
- - Add appropriate legends
651
- - Use consistent font sizes and styles
652
- - Include grid lines where helpful
653
- - Add hover information for interactive plots
654
- 2. Specific Chart Types:
655
- - Bar Charts:
656
- * Use horizontal bars for many categories
657
- * Sort bars by value when appropriate
658
- * Use consistent bar widths
659
- * Add value labels on bars
660
-
661
- - Line Charts:
662
- * Use distinct line styles/colors
663
- * Add markers at data points
664
- * Include trend lines when relevant
665
- * Show confidence intervals if applicable
666
-
667
- - Scatter Plots:
668
- * Use appropriate marker sizes
669
- * Add regression lines when needed
670
- * Use color to show additional dimensions
671
- * Include density contours for large datasets
672
-
673
- - Heatmaps:
674
- * Use diverging color schemes for correlation
675
- * Include value annotations
676
- * Sort rows/columns by similarity
677
- * Add clear color scale legend
678
- 3. Data Visualization Best Practices:
679
- - Start axes at zero when appropriate
680
- - Use log scales for wide-ranging data
681
- - Include reference lines/benchmarks
682
- - Add annotations for important points
683
- - Show uncertainty where relevant
684
- - Use consistent color encoding
685
- - Include data source and timestamp
686
- - Add clear figure captions
687
- 4. Interactive Features:
688
- - Enable zooming and panning
689
- - Add tooltips with detailed information
690
- - Include download options
691
- - Allow toggling of data series
692
- - Enable cross-filtering between charts
693
- 5. Accessibility:
694
- - Use colorblind-friendly palettes
695
- - Include alt text for all visualizations
696
- - Ensure sufficient contrast
697
- - Make interactive elements keyboard accessible
698
- - Provide text alternatives for key insights
699
- """
700
-
701
-
702
-
703
-
704
- class deep_analysis_module(dspy.Module):
705
- def __init__(self,agents, agents_desc):
706
- # logger.log_message(f"Initializing deep_analysis_module with {agents} agents: {list(agents.keys())}", level=logging.INFO)
707
-
708
- # self.agent = agents
709
- self.agents = {}
710
- for key in agents.keys():
711
- self.agents[key] = dspy.asyncify(dspy.Predict(agents[key]))
712
-
713
- # for sig in self.agen
714
- # Make all dspy operations async using asyncify
715
- self.deep_questions = dspy.asyncify(dspy.Predict(deep_questions))
716
- self.deep_planner = dspy.asyncify(dspy.Predict(deep_planner))
717
- self.deep_synthesizer = dspy.asyncify(dspy.Predict(deep_synthesizer))
718
- # Keep both asyncified and non-asyncified versions for code synthesizer
719
- self.deep_code_synthesizer_sync = dspy.Predict(deep_code_synthesizer) # For dspy.Refine
720
- self.deep_code_synthesizer = dspy.asyncify(dspy.Predict(deep_code_synthesizer)) # For async use
721
- self.deep_plan_fixer = dspy.asyncify(dspy.Predict(deep_plan_fixer))
722
- self.deep_code_fixer = dspy.asyncify(dspy.Predict(deep_code_fix))
723
- self.styling_instructions = chart_instructions
724
- self.agents_desc = agents_desc
725
- self.final_conclusion = dspy.asyncify(dspy.ChainOfThought(final_conclusion))
726
-
727
- # logger.log_message(f"Deep analysis module initialized successfully with agents: {list(self.agents.keys())}", level=logging.INFO)
728
-
729
- async def execute_deep_analysis_streaming(self, goal, dataset_info, session_datasets=None):
730
- """
731
- Execute deep analysis with streaming progress updates.
732
- This is an async generator that yields progress updates incrementally.
733
- """
734
- logger.log_message(f"🔍 DEEP ANALYSIS STREAMING START - goal: {goal[:100]}...", level=logging.DEBUG)
735
- logger.log_message(f"🔍 DEEP ANALYSIS STREAMING START - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
736
- logger.log_message(f"🔍 DEEP ANALYSIS STREAMING START - session_datasets type: {type(session_datasets)}, keys: {list(session_datasets.keys()) if session_datasets else 'None'}", level=logging.DEBUG)
737
-
738
- # Make all session datasets available globally for code execution
739
- if session_datasets is not None:
740
- for dataset_name, dataset_df in session_datasets.items():
741
- globals()[dataset_name] = dataset_df
742
- logger.log_message(f"🔍 MADE DATASET AVAILABLE GLOBALLY - {dataset_name}: shape {dataset_df.shape}", level=logging.DEBUG)
743
-
744
- try:
745
- # Step 1: Generate deep questions (20% progress)
746
- yield {
747
- "step": "questions",
748
- "status": "processing",
749
- "message": "Generating analytical questions...",
750
- "progress": 10
751
- }
752
-
753
- logger.log_message(f"🔍 CALLING DEEP_QUESTIONS - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
754
- questions = await self.deep_questions(goal=goal, dataset_info=dataset_info)
755
- logger.log_message("Questions generated")
756
-
757
- yield {
758
- "step": "questions",
759
- "status": "completed",
760
- "content": questions.deep_questions,
761
- "progress": 20
762
- }
763
-
764
- # Step 2: Create analysis plan (40% progress)
765
- yield {
766
- "step": "planning",
767
- "status": "processing",
768
- "message": "Creating analysis plan...",
769
- "progress": 25
770
- }
771
-
772
- question_list = [q.strip() for q in questions.deep_questions.split('\n') if q.strip()]
773
- logger.log_message(f" CALLING DEEP_PLANNER - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
774
- deep_plan = await self.deep_planner(
775
- deep_questions=questions.deep_questions,
776
- dataset=dataset_info,
777
- agents_desc=str(self.agents_desc)
778
- )
779
- logger.log_message("Plan created")
780
-
781
- # Parse plan instructions
782
- try:
783
- plan_instructions = ast.literal_eval(deep_plan.plan_instructions)
784
- if not isinstance(plan_instructions, dict):
785
- plan_instructions = json.loads(deep_plan.plan_instructions)
786
- keys = [key for key in plan_instructions.keys()]
787
-
788
- if not all(key in self.agents for key in keys):
789
- raise ValueError(f"Invalid agent key(s) in plan instructions. Available agents: {list(self.agents.keys())}")
790
- logger.log_message(f"Plan instructions: {plan_instructions}", logging.INFO)
791
- logger.log_message(f"Keys: {keys}", logging.INFO)
792
- except (ValueError, SyntaxError, json.JSONDecodeError) as e:
793
- try:
794
- deep_plan = await self.deep_plan_fixer(plan_instructions=deep_plan.plan_instructions)
795
- plan_instructions = ast.literal_eval(deep_plan.fixed_plan)
796
- if not isinstance(plan_instructions, dict):
797
- plan_instructions = json.loads(deep_plan.fixed_plan)
798
- keys = [key for key in plan_instructions.keys()]
799
- except (ValueError, SyntaxError, json.JSONDecodeError) as e:
800
- logger.log_message(f"Error parsing plan instructions: {e}", logging.ERROR)
801
- raise e
802
-
803
- logger.log_message("Instructions parsed")
804
-
805
- yield {
806
- "step": "planning",
807
- "status": "completed",
808
- "content": deep_plan.plan_instructions,
809
- "progress": 40
810
- }
811
-
812
- # Step 3: Execute agent tasks (60% progress)
813
- yield {
814
- "step": "agent_execution",
815
- "status": "processing",
816
- "message": "Executing analysis agents...",
817
- "progress": 45
818
- }
819
-
820
- logger.log_message(f"🔍 CREATING DSPY EXAMPLES - dataset_info type: {type(dataset_info)}, length: {len(dataset_info) if isinstance(dataset_info, str) else 'N/A'}", level=logging.DEBUG)
821
-
822
-
823
- # tasks = [self.agents[key](**q) for q, key in zip(queries, keys)]
824
- tasks = []
825
- for key in keys:
826
- logger.log_message(f"Preparing agent task for key: {key}", level=logging.DEBUG)
827
- logger.log_message(f"Plan instructions for {key}: {plan_instructions[key]}", level=logging.DEBUG)
828
- if "data_viz" in key or "viz" in key.lower() or "visual" in key.lower() or "plot" in key.lower() or "chart" in key.lower():
829
- logger.log_message(f"Agent {key} identified as visualization agent. Adding styling_index.", level=logging.DEBUG)
830
- task = self.agents[key](
831
- goal=questions.deep_questions,
832
- dataset=dataset_info,
833
- plan_instructions=str(plan_instructions[key]),
834
- styling_index=self.styling_instructions
835
- )
836
- logger.log_message(f"Visualization agent task for {key} created.", level=logging.DEBUG)
837
- tasks.append(task)
838
- else:
839
- logger.log_message(f"Agent {key} identified as non-visualization agent.", level=logging.DEBUG)
840
- task = self.agents[key](
841
- goal=questions.deep_questions,
842
- dataset=dataset_info,
843
- plan_instructions=str(plan_instructions[key])
844
- )
845
- logger.log_message(f"Non-visualization agent task for {key} created.", level=logging.DEBUG)
846
- tasks.append(task)
847
-
848
-
849
-
850
-
851
-
852
-
853
- logger.log_message("Passed that stage")
854
- # DEBUG: Log what parameters each agent will receive
855
-
856
- # Await all tasks to complete
857
- summaries = []
858
- codes = []
859
- logger.log_message("Tasks started")
860
-
861
- completed_tasks = 0
862
- for task in asyncio.as_completed(tasks):
863
- result = await task
864
- summaries.append(result.summary)
865
- codes.append(result.code)
866
- completed_tasks += 1
867
-
868
- # Update progress for each completed agent
869
- agent_progress = 45 + (completed_tasks / len(tasks)) * 15 # 45% to 60%
870
- yield {
871
- "step": "agent_execution",
872
- "status": "processing",
873
- "message": f"Completed {completed_tasks}/{len(tasks)} analysis agents...",
874
- "progress": int(agent_progress)
875
- }
876
- logger.log_message(f"Done with agent {completed_tasks}/{len(tasks)}")
877
-
878
- yield {
879
- "step": "agent_execution",
880
- "status": "completed",
881
- "message": "All analysis agents completed",
882
- "progress": 60
883
- }
884
-
885
- # Step 4: Code synthesis (80% progress)
886
- yield {
887
- "step": "code_synthesis",
888
- "status": "processing",
889
- "message": "Analyzing code...",
890
- "progress": 65
891
- }
892
-
893
- # Safely extract code from agent outputs
894
- code = []
895
- for c in codes:
896
- try:
897
- cleaned_code = remove_main_block(c)
898
- if "```python" in cleaned_code:
899
- parts = cleaned_code.split("```python")
900
- if len(parts) > 1:
901
- extracted = parts[1].split("```")[0] if "```" in parts[1] else parts[1]
902
- code.append(extracted.replace('try\n','try:\n'))
903
- else:
904
- code.append(cleaned_code.replace('try\n','try:\n'))
905
- else:
906
- code.append(cleaned_code.replace('try\n','try:\n'))
907
- except Exception as e:
908
- logger.log_message(f"Warning: Error processing code block: {e}", logging.WARNING)
909
- code.append(c.replace('try\n','try:\n'))
910
-
911
- # Create deep coder without asyncify to avoid source inspection issues
912
- def create_score_code_with_datasets(datasets):
913
- """
914
- Creates a score function that includes the session datasets.
915
-
916
- Args:
917
- datasets: Dictionary of datasets from session_state['datasets']
918
-
919
- Returns:
920
- A reward function compatible with dspy.Refine
921
- """
922
- def score_code_with_datasets(args, pred):
923
- return score_code(args, pred, datasets) # FIXED: Use positional argument
924
-
925
- return score_code_with_datasets
926
-
927
- # Then in your deep analysis method:
928
- # Create score function with datasets
929
- score_fn = create_score_code_with_datasets(self.datasets)
930
- deep_coder = dspy.Refine(module=self.deep_code_synthesizer_sync, N=5, reward_fn=score_fn, threshold=1.0, fail_count=10)
931
-
932
- # Check if we have valid API key
933
- anthropic_key = os.environ.get('ANTHROPIC_API_KEY')
934
- if not anthropic_key:
935
- raise ValueError("ANTHROPIC_API_KEY environment variable is not set")
936
-
937
- try:
938
- # Create the LM instance that will be used
939
- thread_lm = dspy.LM("anthropic/claude-sonnet-4-6", api_key=anthropic_key, max_tokens=17000)
940
-
941
- logger.log_message("Starting code generation...")
942
- start_time = datetime.datetime.now()
943
- logger.log_message(f"Code generation started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
944
-
945
- # Define the blocking function to run in thread
946
- def run_deep_coder():
947
- with dspy.context(lm=thread_lm):
948
- return deep_coder(
949
- deep_questions=str(questions.deep_questions),
950
- dataset_info=dataset_info,
951
- planner_instructions=str(plan_instructions),
952
- code=str(code)
953
- )
954
-
955
- # Use asyncio.to_thread for better async integration
956
- deep_code = await asyncio.to_thread(run_deep_coder)
957
-
958
- logger.log_message(f"Code generation completed at: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
959
- except Exception as e:
960
- logger.log_message(f"Error during code generation: {str(e)}", logging.ERROR)
961
- raise e
962
-
963
- code = deep_code.combined_code
964
- code = code.replace('```python', '').replace('```', '')
965
-
966
- # Clean Unicode characters that might cause encoding issues
967
- code = clean_unicode_chars(code)
968
-
969
- yield {
970
- "step": "code_synthesis",
971
- "status": "completed",
972
- "message": "Code synthesis completed",
973
- "progress": 80
974
- }
975
-
976
- # Step 5: Execute code (85% progress)
977
- yield {
978
- "step": "code_execution",
979
- "status": "processing",
980
- "message": "Executing code...",
981
- "progress": 82
982
- }
983
-
984
- # Execute the code with error handling and session DataFrame
985
- try:
986
- # Run code execution in thread pool to avoid blocking
987
- import concurrent.futures
988
- with concurrent.futures.ThreadPoolExecutor() as executor:
989
- future = executor.submit(clean_and_store_code, code, session_datasets)
990
- output = future.result(timeout=300) # 5 minute timeout
991
-
992
- logger.log_message(f"Deep Code executed")
993
-
994
- if output.get('error'):
995
- logger.log_message(f"Warning: Code execution had errors: {output['error']}", logging.ERROR)
996
-
997
- print_outputs = [output['printed_output']]
998
- plotly_figs = [output['plotly_figs']]
999
-
1000
- except Exception as e:
1001
- logger.log_message(f"Error during code execution: {str(e)}", logging.ERROR)
1002
- output = {
1003
- 'exec_result': None,
1004
- 'printed_output': f"Code execution failed: {str(e)}",
1005
- 'plotly_figs': [],
1006
- 'error': str(e)
1007
- }
1008
- print_outputs = [output['printed_output']]
1009
- plotly_figs = [output['plotly_figs']]
1010
-
1011
- yield {
1012
- "step": "code_execution",
1013
- "status": "completed",
1014
- "message": "Code execution completed",
1015
- "progress": 85
1016
- }
1017
-
1018
- # Step 6: Synthesis (90% progress)
1019
- yield {
1020
- "step": "synthesis",
1021
- "status": "processing",
1022
- "message": "Synthesizing results...",
1023
- "progress": 87
1024
- }
1025
-
1026
- synthesis = []
1027
- try:
1028
- synthesis_result = await self.deep_synthesizer(
1029
- query=goal,
1030
- summaries=str(summaries),
1031
- print_outputs=str(output['printed_output'])
1032
- )
1033
- synthesis.append(synthesis_result)
1034
- except Exception as e:
1035
- logger.log_message(f"Error during synthesis: {str(e)}", logging.ERROR)
1036
- synthesis.append(type('obj', (object,), {'synthesized_report': f"Synthesis failed: {str(e)}"})())
1037
-
1038
- logger.log_message("Synthesis done")
1039
-
1040
- yield {
1041
- "step": "synthesis",
1042
- "status": "completed",
1043
- "message": "Synthesis completed",
1044
- "progress": 90
1045
- }
1046
-
1047
- # Step 7: Final conclusion (100% progress)
1048
- yield {
1049
- "step": "conclusion",
1050
- "status": "processing",
1051
- "message": "Generating final conclusion...",
1052
- "progress": 95
1053
- }
1054
-
1055
- try:
1056
- final_conclusion = await self.final_conclusion(
1057
- query=goal,
1058
- synthesized_sections=str([s.synthesized_report for s in synthesis])
1059
- )
1060
- except Exception as e:
1061
- logger.log_message(f"Error during final conclusion: {str(e)}", logging.ERROR)
1062
- final_conclusion = type('obj', (object,), {'final_conclusion': f"Final conclusion failed: {str(e)}"})()
1063
-
1064
- logger.log_message("Conclusion Made")
1065
-
1066
- return_dict = {
1067
- 'goal': goal,
1068
- 'deep_questions': questions.deep_questions,
1069
- 'deep_plan': deep_plan.plan_instructions,
1070
- 'summaries': summaries,
1071
- 'code': code,
1072
- 'plotly_figs': plotly_figs,
1073
- 'synthesis': [s.synthesized_report for s in synthesis],
1074
- 'final_conclusion': final_conclusion.final_conclusion
1075
- }
1076
-
1077
- yield {
1078
- "step": "conclusion",
1079
- "status": "completed",
1080
- "message": "Analysis completed successfully",
1081
- "progress": 100,
1082
- "final_result": return_dict
1083
- }
1084
-
1085
- logger.log_message("Return dict created")
1086
-
1087
- except Exception as e:
1088
- logger.log_message(f"Error in deep analysis: {str(e)}", logging.ERROR)
1089
- yield {
1090
- "step": "error",
1091
- "status": "failed",
1092
- "message": f"Deep analysis failed: {str(e)}",
1093
- "progress": 0,
1094
- "error": str(e)
1095
- }
1096
-
1097
-
1098
- async def execute_deep_analysis(self, goal, dataset_info, session_df=None):
1099
- """
1100
- Legacy method for backward compatibility.
1101
- Executes the streaming analysis and returns the final result.
1102
- """
1103
- final_result = None
1104
- async for update in self.execute_deep_analysis_streaming(goal, dataset_info, session_df):
1105
- if update.get("step") == "conclusion" and update.get("status") == "completed":
1106
- final_result = update.get("final_result")
1107
- elif update.get("step") == "error":
1108
- raise Exception(update.get("message", "Unknown error"))
1109
-
1110
- return final_result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/retrievers/retrievers.py CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import pandas as pd
4
  import numpy as np
5
- from datetime import datetime
6
 
7
  # instructions also stored here
8
  instructions ="""
@@ -34,7 +34,7 @@ PLEASE READ THE INSTRUCTIONS! Thank you
34
  def return_vals(df,c):
35
  if isinstance(df[c].iloc[10], (int, float, complex)):
36
  return {'max_value':max(df[c]),'min_value': min(df[c]), 'mean_value':np.mean(df[c])}
37
- elif(isinstance(df[c].iloc[10],datetime)):
38
  return {str(max(df[c])), str(min(df[c])), str(np.mean(df[c]))}
39
  else:
40
  return {'top_10_values':df[c].value_counts()[:10], 'total_categoy_count':len(df[c].unique())}
@@ -52,8 +52,9 @@ def correct_num(df,c):
52
  # does most of the pre-processing
53
  def make_data(df, desc):
54
  dict_ = {}
55
- dict_['dataset_python_name'] = "The data is loaded as df"
56
  dict_['Description'] = desc
 
57
  # dict_['all_column_names'] = str(list(df.columns[:20]))
58
 
59
  # for c in df.columns:
@@ -65,3 +66,88 @@ def make_data(df, desc):
65
  return dict_
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import pandas as pd
4
  import numpy as np
5
+ import datetime
6
 
7
  # instructions also stored here
8
  instructions ="""
 
34
  def return_vals(df,c):
35
  if isinstance(df[c].iloc[10], (int, float, complex)):
36
  return {'max_value':max(df[c]),'min_value': min(df[c]), 'mean_value':np.mean(df[c])}
37
+ elif(isinstance(df[c].iloc[10],datetime.datetime)):
38
  return {str(max(df[c])), str(min(df[c])), str(np.mean(df[c]))}
39
  else:
40
  return {'top_10_values':df[c].value_counts()[:10], 'total_categoy_count':len(df[c].unique())}
 
52
  # does most of the pre-processing
53
  def make_data(df, desc):
54
  dict_ = {}
55
+ dict_['df_name'] = "The data is loaded as df"
56
  dict_['Description'] = desc
57
+ dict_['dataframe_head_view'] = df.head(2).to_markdown()
58
  # dict_['all_column_names'] = str(list(df.columns[:20]))
59
 
60
  # for c in df.columns:
 
66
  return dict_
67
 
68
 
69
+
70
+ # These are stored styling instructions for data_viz_agent, helps generate good graphs
71
+ styling_instructions =[
72
+ """
73
+ Dont ignore any of these instructions.
74
+ For a line chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
75
+ Always give a title and make bold using html tag axis label and try to use multiple colors if more than one line
76
+ Annotate the min and max of the line
77
+ Display numbers in thousand(K) or Million(M) if larger than 1000/100000
78
+ Show percentages in 2 decimal points with '%' sign
79
+ Default size of chart should be height =1200 and width =1000
80
+
81
+ """
82
+
83
+ , """
84
+ Dont ignore any of these instructions.
85
+ For a bar chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
86
+ Always give a title and make bold using html tag axis label
87
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000.
88
+ Annotate the values of the bar chart
89
+ If variable is a percentage show in 2 decimal points with '%' sign.
90
+ Default size of chart should be height =1200 and width =1000
91
+ """
92
+ ,
93
+
94
+ """
95
+ For a histogram chart choose a bin_size of 50
96
+ Do not ignore any of these instructions
97
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
98
+ Always give a title and make bold using html tag axis label
99
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
100
+ If variable is a percentage show in 2 decimal points with '%'
101
+ Default size of chart should be height =1200 and width =1000
102
+ """,
103
+
104
+
105
+ """
106
+ For a pie chart only show top 10 categories, bundle rest as others
107
+ Do not ignore any of these instructions
108
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
109
+ Always give a title and make bold using html tag axis label
110
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
111
+ If variable is a percentage show in 2 decimal points with '%'
112
+ Default size of chart should be height =1200 and width =1000
113
+ """,
114
+
115
+ """
116
+ Do not ignore any of these instructions
117
+ always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
118
+ Always give a title and make bold using html tag axis label
119
+ Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
120
+ Don't add K/M if number already in , or value is not a number
121
+ If variable is a percentage show in 2 decimal points with '%'
122
+ Default size of chart should be height =1200 and width =1000
123
+ """,
124
+ """
125
+ For a heat map
126
+ Use the 'plotly_white' template for a clean, white background.
127
+ Set a chart title
128
+ Style the X-axis with a black line color, 0.2 line width, 1 grid width, format 1000/1000000 as K/M
129
+ Do not format non-numerical numbers
130
+ .style the Y-axis with a black line color, 0.2 line width, 1 grid width format 1000/1000000 as K/M
131
+ Do not format non-numerical numbers
132
+
133
+ . Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
134
+ """,
135
+ """
136
+ For a Histogram, used for returns/distribution plotting
137
+ Use the 'plotly_white' template for a clean, white background.
138
+ Set a chart title
139
+ Style the X-axis 1 grid width, format 1000/1000000 as K/M
140
+ Do not format non-numerical numbers
141
+ .style the Y-axis, 1 grid width format 1000/1000000 as K/M
142
+ Do not format non-numerical numbers
143
+
144
+ Use an opacity of 0.75
145
+
146
+ Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
147
+ """
148
+
149
+ ]
150
+
151
+
152
+
153
+
src/agents/test_agent.py ADDED
@@ -0,0 +1,1223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dspy
2
+ import src.agents.memory_agents as m
3
+ import asyncio
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ import os
6
+ from dotenv import load_dotenv
7
+ # import logging
8
+ from src.utils.logger import Logger
9
+ load_dotenv()
10
+
11
+ logger = Logger("agents", see_time=True, console_log=False)
12
+
13
+
14
+ AGENTS_WITH_DESCRIPTION = {
15
+ "preprocessing_agent": "Cleans and prepares a DataFrame using Pandas and NumPy—handles missing values, detects column types, and converts date strings to datetime.",
16
+ "statistical_analytics_agent": "Performs statistical analysis (e.g., regression, seasonal decomposition) using statsmodels, with proper handling of categorical data and missing values.",
17
+ "sk_learn_agent": "Trains and evaluates machine learning models using scikit-learn, including classification, regression, and clustering with feature importance insights.",
18
+ "data_viz_agent": "Generates interactive visualizations with Plotly, selecting the best chart type to reveal trends, comparisons, and insights based on the analysis goal."
19
+ }
20
+
21
+ PLANNER_AGENTS_WITH_DESCRIPTION = {
22
+ "planner_preprocessing_agent": (
23
+ "Cleans and prepares a DataFrame using Pandas and NumPy—"
24
+ "handles missing values, detects column types, and converts date strings to datetime. "
25
+ "Outputs a cleaned DataFrame for the planner_statistical_analytics_agent."
26
+ ),
27
+ "planner_statistical_analytics_agent": (
28
+ "Takes the cleaned DataFrame from preprocessing, performs statistical analysis "
29
+ "(e.g., regression, seasonal decomposition) using statsmodels with proper handling "
30
+ "of categorical data and remaining missing values. "
31
+ "Produces summary statistics and model diagnostics for the planner_sk_learn_agent."
32
+ ),
33
+ "planner_sk_learn_agent": (
34
+ "Receives summary statistics and the cleaned data, trains and evaluates machine "
35
+ "learning models using scikit-learn (classification, regression, clustering), "
36
+ "and generates performance metrics and feature importance. "
37
+ "Passes the trained models and evaluation results to the planner_data_viz_agent."
38
+ ),
39
+ "planner_data_viz_agent": (
40
+ "Consumes trained models and evaluation results to create interactive visualizations "
41
+ "with Plotly—selects the best chart type, applies styling, and annotates insights. "
42
+ "Delivers ready-to-share figures that communicate model performance and key findings."
43
+ ),
44
+ }
45
+
46
+ def get_agent_description(agent_name, is_planner=False):
47
+ if is_planner:
48
+ return PLANNER_AGENTS_WITH_DESCRIPTION[agent_name.lower()] if agent_name.lower() in PLANNER_AGENTS_WITH_DESCRIPTION else "No description available for this agent"
49
+ else:
50
+ return AGENTS_WITH_DESCRIPTION[agent_name.lower()] if agent_name.lower() in AGENTS_WITH_DESCRIPTION else "No description available for this agent"
51
+
52
+
53
+ # Agent to make a Chat history name from a query
54
+ class chat_history_name_agent(dspy.Signature):
55
+ """You are an agent that takes a query and returns a name for the chat history"""
56
+ query = dspy.InputField(desc="The query to make a name for")
57
+ name = dspy.OutputField(desc="A name for the chat history (max 3 words)")
58
+
59
+ class dataset_description_agent(dspy.Signature):
60
+ """You are an AI agent that generates a detailed description of a given dataset for both users and analysis agents.
61
+ Your description should serve two key purposes:
62
+ 1. Provide users with context about the dataset's purpose, structure, and key attributes.
63
+ 2. Give analysis agents critical data handling instructions to prevent common errors.
64
+
65
+ For data handling instructions, you must always include Python data types and address the following:
66
+ - Data type warnings (e.g., numeric columns stored as strings that need conversion).
67
+ - Null value handling recommendations.
68
+ - Format inconsistencies that require preprocessing.
69
+ - Explicit warnings about columns that appear numeric but are stored as strings (e.g., '10' vs 10).
70
+ - Explicit Python data types for each major column (e.g., int, float, str, bool, datetime).
71
+ - Columns with numeric values that should be treated as categorical (e.g., zip codes, IDs).
72
+ - Any date parsing or standardization required (e.g., MM/DD/YYYY to datetime).
73
+ - Any other technical considerations that would affect downstream analysis or modeling.
74
+ - List all columns and their data types with exact case sensitive spelling
75
+
76
+ If an existing description is provided, enhance it with both business context and technical guidance for analysis agents, preserving accurate information from the existing description or what the user has written.
77
+
78
+ Ensure the description is comprehensive and provides actionable insights for both users and analysis agents.
79
+
80
+
81
+ Example:
82
+ This housing dataset contains property details including price, square footage, bedrooms, and location data.
83
+ It provides insights into real estate market trends across different neighborhoods and property types.
84
+
85
+ TECHNICAL CONSIDERATIONS FOR ANALYSIS:
86
+ - price (str): Appears numeric but is stored as strings with a '$' prefix and commas (e.g., "$350,000"). Requires cleaning with str.replace('$','').replace(',','') and conversion to float.
87
+ - square_footage (str): Contains unit suffix like 'sq ft' (e.g., "1,200 sq ft"). Remove suffix and commas before converting to int.
88
+ - bedrooms (int): Correctly typed but may contain null values (~5% missing) – consider imputation or filtering.
89
+ - zip_code (int): Numeric column but should be treated as str or category to preserve leading zeros and prevent unintended numerical analysis.
90
+ - year_built (float): May contain missing values (~15%) – consider mean/median imputation or exclusion depending on use case.
91
+ - listing_date (str): Dates stored in "MM/DD/YYYY" format – convert to datetime using pd.to_datetime().
92
+ - property_type (str): Categorical column with inconsistent capitalization (e.g., "Condo", "condo", "CONDO") – normalize to lowercase for consistent grouping.
93
+ """
94
+ dataset = dspy.InputField(desc="The dataset to describe, including headers, sample data, null counts, and data types.")
95
+ existing_description = dspy.InputField(desc="An existing description to improve upon (if provided).", default="")
96
+ description = dspy.OutputField(desc="A comprehensive dataset description with business context and technical guidance for analysis agents.")
97
+
98
+
99
+ class analytical_planner(dspy.Signature):
100
+ """You are a **data analytics planner agent** responsible for generating the **most efficient plan**—using the **fewest number of variables and agents necessary**—to accomplish a **user-defined goal**. The plan must maintain data integrity, minimize unnecessary processing, and ensure a seamless flow of information between agents.
101
+
102
+ ---
103
+
104
+ ### **Inputs**:
105
+
106
+ 1. **Datasets**: Pre-processed or raw datasets ready for analysis.
107
+ 2. **Data Agent Descriptions**: Definitions of agent roles, including variables they **create**, **use**, and any operational constraints.
108
+ 3. **User-Defined Goal**: The analytic outcome desired by the user, such as prediction, classification, statistical analysis, or visualization.
109
+
110
+ ---
111
+
112
+ ### **Responsibilities**:
113
+
114
+ 1. **Goal Feasibility Check**:
115
+
116
+ * Assess if the goal is achievable using the available data and agents.
117
+ * Request clarification if the goal is underspecified or ambiguous.
118
+
119
+ 2. **Minimal Plan Construction**:
120
+
121
+ * Identify the **smallest set of variables and agents** needed to fulfill the goal.
122
+ * Eliminate redundant steps and avoid unnecessary data transformations.
123
+ * Construct a **logically ordered pipeline** where each agent only appears if essential to the output.
124
+
125
+ 3. **Plan Instructions with Variable Purposes**:
126
+
127
+ * Define **precise instructions** for each agent, explicitly specifying:
128
+
129
+ * **'create'**: Variables to be generated and their **purpose** (e.g., "varA: cleaned version of raw\_data, needed for modeling").
130
+ * **'use'**: Variables needed as input and their **role** (e.g., "raw\_data: unprocessed input for cleaning").
131
+ * **'instruction'**: A brief, clear rationale for the agent's role, why the variables are necessary, and how they contribute to the user-defined goal.
132
+
133
+ 4. **Efficiency and Clarity**:
134
+
135
+ * Ensure each agent's role is distinct and purposeful.
136
+ * Avoid over-processing or using intermediate variables unless required.
137
+ * Prioritize **direct paths** to achieving the goal.
138
+
139
+ ---
140
+
141
+ ### **Output Format**:
142
+
143
+ 1. **Plan**:
144
+
145
+ ```
146
+ plan: AgentX -> AgentY -> AgentZ
147
+ ```
148
+
149
+ 2. **Plan Instructions (with Variable Descriptions)**:
150
+
151
+ ```json
152
+ plan_instructions: {
153
+ "AgentX": {
154
+ "create": ["varA: cleaned version of raw_data, required for feature generation"],
155
+ "use": ["raw_data: initial unprocessed dataset"],
156
+ "instruction": "Clean raw_data to produce varA, which is used by AgentY to generate features."
157
+ },
158
+ "AgentY": {
159
+ "create": ["varB: engineered features derived from varA for use in modeling"],
160
+ "use": ["varA: cleaned dataset"],
161
+ "instruction": "Generate varB from varA, preparing inputs for modeling by AgentZ."
162
+ },
163
+ "AgentZ": {
164
+ "create": ["final_output: prediction results derived from model using varB"],
165
+ "use": ["varB: features for prediction"],
166
+ "instruction": "Use varB to produce final_output as specified in the user goal."
167
+ }
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ### **Key Principles**:
174
+
175
+ 1. **Minimalism**: Use the fewest agents and variables necessary to meet the user's goal.
176
+ 2. **Efficiency**: Avoid redundant or non-essential transformations.
177
+ 3. **Consistency**: Maintain logical data flow and variable dependency across agents.
178
+ 4. **Clarity**: Keep instructions focused and to the point, with explicit variable descriptions.
179
+ 5. **Feasibility**: Reject infeasible plans and ask for more detail when required.
180
+ 6. **Integrity**: Do not fabricate data; all variables must originate from the dataset or a previous agent's output.
181
+
182
+ ---
183
+
184
+ ### **Special Conditions**:
185
+
186
+ 1. **Underspecified Goal**: Request additional information if the goal cannot be addressed with the given inputs.
187
+ 2. **Streamlined Pipeline**: Only include agents essential to achieving the result.
188
+ 3. **Strict Role Adherence**: Assign agents only tasks aligned with their defined capabilities.
189
+
190
+ ---
191
+
192
+ Would you like a quick example plan using this format?
193
+ """
194
+ dataset = dspy.InputField(desc="Available datasets loaded in the system, use this df, columns set df as copy of df")
195
+ Agent_desc = dspy.InputField(desc="The agents available in the system")
196
+ goal = dspy.InputField(desc="The user defined goal")
197
+
198
+ plan = dspy.OutputField(desc="The plan that would achieve the user defined goal", prefix='Plan:')
199
+ plan_instructions = dspy.OutputField(desc="Detailed variable-level instructions per agent for the plan")
200
+ class planner_preprocessing_agent(dspy.Signature):
201
+ """
202
+ ### **Updated Prompt for the Preprocessing Agent**
203
+
204
+ You are a preprocessing agent in a multi-agent data analytics system.
205
+
206
+ You are given:
207
+
208
+ * A **dataset** (already loaded as `df`).
209
+ * A **user-defined analysis goal** (e.g., predictive modeling, exploration, cleaning).
210
+ * **Agent-specific plan instructions** that tell you what variables you are expected to **create** and what variables you are **receiving** from previous agents.
211
+
212
+ ### Your responsibilities:
213
+
214
+ * **Follow the provided plan** and create only the required variables listed in the 'create' section of the plan instructions.
215
+ * **Do not create fake data** or introduce any variables that aren't explicitly part of the instructions.
216
+ * **Do not read data from CSV**; the dataset (`df`) is already loaded and ready for processing.
217
+ * Generate Python code using **NumPy** and **Pandas** to preprocess the data and produce any intermediate variables as specified in the plan instructions.
218
+
219
+ ### Best practices for preprocessing:
220
+
221
+ 1. **Create a copy of the DataFrame**:
222
+ Always work with a copy of the original dataset to avoid modifying it directly.
223
+
224
+ ```python
225
+ df_cleaned = df.copy()
226
+ ```
227
+
228
+ 2. **Identify and separate columns** into:
229
+
230
+ * `numeric_columns`: Columns with numerical data.
231
+ * `categorical_columns`: Columns with categorical data.
232
+
233
+ 3. **Handle missing values** appropriately (e.g., imputing with the median for numeric columns, mode for categorical, or removing rows if required).
234
+
235
+ 4. **Convert string-based date columns to datetime** using the provided safe conversion method:
236
+
237
+ ```python
238
+ def safe_to_datetime(date):
239
+ try:
240
+ return pd.to_datetime(date, errors='coerce', cache=False)
241
+ except (ValueError, TypeError):
242
+ return pd.NaT
243
+ df_cleaned['datetime_column'] = df_cleaned['datetime_column'].apply(safe_to_datetime)
244
+ ```
245
+
246
+ Apply this method to any date columns identified in the dataset.
247
+
248
+ 5. **Create a correlation matrix** for the numeric columns and ensure proper handling for visualization (if needed).
249
+
250
+ 6. **Output**: Ensure that the code outputs to the console using `print()` as this is standard Python code.
251
+
252
+ 7. **Ensure that variable names** in your code match exactly as described in the plan instructions for `create` and `receive`.
253
+
254
+ 8. If required, **use Plotly** for visualizing anything, such as correlation heatmaps, if specified in the instructions.
255
+
256
+ ### **Important Rules**:
257
+
258
+ * Follow the **plan instructions** precisely and ensure that no unnecessary variables are created.
259
+ * Do not generate fake data, always assume the required variables are available and ready to use.
260
+ * Do not modify the index of the DataFrame.
261
+ * Stick strictly to the preprocessing tasks listed in the instructions.
262
+ * **If visualizations are needed**, ensure they are done using **Plotly** and not **matplotlib**.
263
+
264
+ ### Output:
265
+
266
+ 1. **Code**: Python code that performs the requested preprocessing steps as per the plan instructions.
267
+ 2. **Summary**: A brief explanation of what preprocessing was done and why (e.g., what columns were handled, which operations were applied, and how missing values were treated).
268
+
269
+ ---
270
+
271
+ ### **Example Input and Output**
272
+
273
+ **Input:**
274
+
275
+ * **Dataset**: `df`
276
+ * **Goal**: Prepare data for predictive modeling.
277
+ * **Plan Instructions**:
278
+
279
+ * `create`: `df_cleaned`, `numeric_columns`, `categorical_columns`, `correlation_matrix`
280
+ * `receive`: `df`
281
+
282
+ **Output:**
283
+
284
+ ```python
285
+ import pandas as pd
286
+ import numpy as np
287
+
288
+ # Create a copy of the DataFrame to avoid modifying the original
289
+ df_cleaned = df.copy()
290
+
291
+ # Handling missing values and preparing numeric/categorical columns
292
+ numeric_columns = df_cleaned.select_dtypes(include=['number']).columns.tolist()
293
+ categorical_columns = df_cleaned.select_dtypes(include=['object']).columns.tolist()
294
+
295
+ # Handle missing values for numeric columns (e.g., fill with median)
296
+ for col in numeric_columns:
297
+ df_cleaned[col].fillna(df_cleaned[col].median(), inplace=True)
298
+
299
+ # Handle missing values for categorical columns (e.g., fill with mode)
300
+ for col in categorical_columns:
301
+ df_cleaned[col].fillna(df_cleaned[col].mode()[0], inplace=True)
302
+
303
+ # Safe conversion of date columns to datetime
304
+ def safe_to_datetime(date):
305
+ try:
306
+ return pd.to_datetime(date, errors='coerce', cache=False)
307
+ except (ValueError, TypeError):
308
+ return pd.NaT
309
+
310
+ date_columns = df_cleaned.select_dtypes(include=['object']).columns[df_cleaned.dtypes == 'object'].str.contains('date', case=False)
311
+ for col in date_columns:
312
+ df_cleaned[col] = df_cleaned[col].apply(safe_to_datetime)
313
+
314
+ # Creating a correlation matrix for numeric columns
315
+ correlation_matrix = df_cleaned[numeric_columns].corr()
316
+
317
+ # Final cleaned dataframe
318
+ # df_cleaned is now ready for use in the next agent
319
+
320
+ # Print results
321
+ print("Correlation Matrix:")
322
+ print(correlation_matrix)
323
+ ```
324
+
325
+ **Summary:**
326
+
327
+ * **Data cleaning**: Missing values were handled for both numeric and categorical columns using median and mode imputation, respectively.
328
+ * **Datetime conversion**: Any date-related columns were safely converted to datetime using `safe_to_datetime`.
329
+ * **Correlation matrix**: A correlation matrix was generated for numeric columns to assess their relationships.
330
+
331
+
332
+ """
333
+ dataset = dspy.InputField(desc="The dataset, preloaded as df")
334
+ goal = dspy.InputField(desc="User-defined goal for the analysis")
335
+ plan_instructions = dspy.InputField(desc="Agent-level instructions about what to create and receive")
336
+
337
+ code = dspy.OutputField(desc="Generated Python code for preprocessing")
338
+ summary = dspy.OutputField(desc="Explanation of what was done and why")
339
+
340
+ class planner_data_viz_agent(dspy.Signature):
341
+ """
342
+ ### **Data Visualization Agent Definition**
343
+
344
+ You are the **data visualization agent** in a multi-agent analytics pipeline. Your primary responsibility is to **generate visualizations** based on the **user-defined goal** and the **plan instructions**.
345
+
346
+ You are provided with:
347
+
348
+ * **goal**: A user-defined goal outlining the type of visualization the user wants (e.g., "plot sales over time with trendline").
349
+ * **dataset**: The dataset (e.g., `df_cleaned`) which will be passed to you by other agents in the pipeline. **Do not assume or create any variables** — **the data is already present and valid** when you receive it.
350
+ * **styling_index**: Specific styling instructions (e.g., axis formatting, color schemes) for the visualization.
351
+ * **plan_instructions**: A dictionary containing:
352
+
353
+ * **'create'**: List of **visualization components** you must generate (e.g., 'scatter_plot', 'bar_chart').
354
+ * **'use'**: List of **variables you must use** to generate the visualizations. This includes datasets and any other variables provided by the other agents.
355
+ * **'instructions'**: A list of additional instructions related to the creation of the visualizations, such as requests for trendlines or axis formats.
356
+
357
+ ---
358
+
359
+ ### **Responsibilities**:
360
+
361
+ 1. **Strict Use of Provided Variables**:
362
+
363
+ * You must **never create fake data**. Only use the variables and datasets that are explicitly **provided** to you in the `plan_instructions['use']` section. All the required data **must already be available**.
364
+ * If any variable listed in `plan_instructions['use']` is missing or invalid, **you must return an error** and not proceed with any visualization.
365
+
366
+ 2. **Visualization Creation**:
367
+
368
+ * Based on the **'create'** section of the `plan_instructions`, generate the **required visualization** using **Plotly**. For example, if the goal is to plot a time series, you might generate a line chart.
369
+ * Respect the **user-defined goal** in determining which type of visualization to create.
370
+
371
+ 3. **Performance Optimization**:
372
+
373
+ * If the dataset contains **more than 50,000 rows**, you **must sample** the data to **5,000 rows** to improve performance. Use this method:
374
+
375
+ ```python
376
+ if len(df) > 50000:
377
+ df = df.sample(5000, random_state=42)
378
+ ```
379
+
380
+ 4. **Layout and Styling**:
381
+
382
+ * Apply formatting and layout adjustments as defined by the **styling_index**. This may include:
383
+
384
+ * Axis labels and title formatting.
385
+ * Tick formats for axes.
386
+ * Color schemes or color maps for visual elements.
387
+ * You must ensure that all axes (x and y) have **consistent formats** (e.g., using `K`, `M`, or 1,000 format, but not mixing formats).
388
+
389
+ 5. **Trendlines**:
390
+
391
+ * Trendlines should **only be included** if explicitly requested in the **'instructions'** section of `plan_instructions`.
392
+
393
+ 6. **Displaying the Visualization**:
394
+
395
+ * Use Plotly's `fig.show()` method to display the created chart.
396
+ * **Never** output raw datasets or the **goal** itself. Only the visualization code and the chart should be returned.
397
+
398
+ 7. **Error Handling**:
399
+
400
+ * If the required dataset or variables are missing or invalid (i.e., not included in `plan_instructions['use']`), return an error message indicating which specific variable is missing or invalid.
401
+ * If the **goal** or **create** instructions are ambiguous or invalid, return an error stating the issue.
402
+
403
+ 8. **No Data Modification**:
404
+
405
+ * **Never** modify the provided dataset or generate new data. If the data needs preprocessing or cleaning, assume it's already been done by other agents.
406
+
407
+ ---
408
+
409
+ ### **Strict Conditions**:
410
+
411
+ * You **never** create any data.
412
+ * You **only** use the data and variables passed to you.
413
+ * If any required data or variable is missing or invalid, **you must stop** and return a clear error message.
414
+
415
+ By following these conditions and responsibilities, your role is to ensure that the **visualizations** are generated as per the user goal, using the valid data and instructions given to you.
416
+
417
+ """
418
+ goal = dspy.InputField(desc="User-defined chart goal (e.g. trendlines, scatter plots)")
419
+ dataset = dspy.InputField(desc="Details of the dataframe (`df`) and its columns")
420
+ styling_index = dspy.InputField(desc="Instructions for plot styling and layout formatting")
421
+ plan_instructions = dspy.InputField(desc="Variables to create and receive for visualization purposes")
422
+
423
+ code = dspy.OutputField(desc="Plotly Python code for the visualization")
424
+ summary = dspy.OutputField(desc="Plain-language summary of what is being visualized")
425
+
426
+ class planner_statistical_analytics_agent(dspy.Signature):
427
+ """
428
+ **Agent Definition:**
429
+
430
+ You are a statistical analytics agent in a multi-agent data analytics pipeline.
431
+
432
+ You are given:
433
+
434
+ * A dataset (usually a cleaned or transformed version like `df_cleaned`).
435
+ * A user-defined goal (e.g., regression, seasonal decomposition).
436
+ * Agent-specific **plan instructions** specifying:
437
+
438
+ * Which **variables** you are expected to **CREATE** (e.g., `regression_model`).
439
+ * Which **variables** you will **USE** (e.g., `df_cleaned`, `target_variable`).
440
+ * A set of **instructions** outlining additional processing or handling for these variables (e.g., handling missing values, adding constants, transforming features, etc.).
441
+
442
+ **Your Responsibilities:**
443
+
444
+ * Use the `statsmodels` library to implement the required statistical analysis.
445
+ * Ensure that all strings are handled as categorical variables via `C(col)` in model formulas.
446
+ * Always add a constant using `sm.add_constant()`.
447
+ * Do **not** modify the DataFrame's index.
448
+ * Convert `X` and `y` to float before fitting the model.
449
+ * Handle missing values before modeling.
450
+ * Avoid any data visualization (that is handled by another agent).
451
+ * Write output to the console using `print()`.
452
+
453
+ **If the goal is regression:**
454
+
455
+ * Use `statsmodels.OLS` with proper handling of categorical variables and adding a constant term.
456
+ * Handle missing values appropriately.
457
+
458
+ **If the goal is seasonal decomposition:**
459
+
460
+ * Use `statsmodels.tsa.seasonal_decompose`.
461
+ * Ensure the time series and period are correctly provided (i.e., `period` should not be `None`).
462
+
463
+ **You must not:**
464
+
465
+ * You must always create the variables in `plan_instructions['CREATE']`.
466
+ * **Never create the `df` variable**. Only work with the variables passed via the `plan_instructions`.
467
+ * Rely on hardcoded column names — use those passed via `plan_instructions`.
468
+ * Introduce or modify intermediate variables unless they are explicitly listed in `plan_instructions['CREATE']`.
469
+
470
+ **Instructions to Follow:**
471
+
472
+ 1. **CREATE** only the variables specified in `plan_instructions['CREATE']`. Do not create any intermediate or new variables.
473
+ 2. **USE** only the variables specified in `plan_instructions['USE']` to carry out the task.
474
+ 3. Follow any **additional instructions** in `plan_instructions['INSTRUCTIONS']` (e.g., preprocessing steps, encoding, handling missing values).
475
+ 4. **Do not reassign or modify** any variables passed via `plan_instructions`. These should be used as-is.
476
+
477
+ **Example Workflow:**
478
+ Given that the `plan_instructions` specifies variables to **CREATE** and **USE**, and includes instructions, your approach should look like this:
479
+
480
+ 1. Use `df_cleaned` and the variables like `X` and `y` from `plan_instructions` for analysis.
481
+ 2. Follow instructions for preprocessing (e.g., handle missing values or scale features).
482
+ 3. If the goal is regression:
483
+
484
+ * Use `sm.OLS` for model fitting.
485
+ * Handle categorical variables via `C(col)` and add a constant term.
486
+ 4. If the goal is seasonal decomposition:
487
+
488
+ * Ensure `period` is provided and use `sm.tsa.seasonal_decompose`.
489
+ 5. Store the output variable as specified in `plan_instructions['CREATE']`.
490
+
491
+ ### Example Code Structure:
492
+
493
+ ```python
494
+ import statsmodels.api as sm
495
+
496
+ def statistical_model(X, y, goal, period=None):
497
+ try:
498
+ X = X.dropna()
499
+ y = y.loc[X.index].dropna()
500
+ X = X.loc[y.index]
501
+
502
+ for col in X.select_dtypes(include=['object', 'category']).columns:
503
+ X[col] = X[col].astype('category')
504
+
505
+ # Add constant term to X
506
+ X = sm.add_constant(X)
507
+
508
+ if goal == 'regression':
509
+ formula = 'y ~ ' + ' + '.join([f'C({col})' if X[col].dtype.name == 'category' else col for col in X.columns])
510
+ model = sm.OLS(y.astype(float), X.astype(float)).fit()
511
+ regression_model = model.summary() # Specify as per CREATE instructions
512
+ return regression_model
513
+
514
+ elif goal == 'seasonal_decompose':
515
+ if period is None:
516
+ raise ValueError("Period must be specified for seasonal decomposition")
517
+ decomposition = sm.tsa.seasonal_decompose(y, period=period)
518
+ seasonal_decomposition = decomposition # Specify as per CREATE instructions
519
+ return seasonal_decomposition
520
+
521
+ else:
522
+ raise ValueError("Unknown goal specified.")
523
+
524
+ except Exception as e:
525
+ return f"An error occurred: {e}"
526
+ ```
527
+
528
+ **Summary:**
529
+
530
+ 1. Always **USE** the variables passed in `plan_instructions['USE']` to carry out the task.
531
+ 2. Only **CREATE** the variables specified in `plan_instructions['CREATE']`. Do not create any additional variables.
532
+ 3. Follow any **additional instructions** in `plan_instructions['INSTRUCTIONS']` (e.g., handling missing values, adding constants).
533
+ 4. Ensure reproducibility by setting the random state appropriately and handling categorical variables.
534
+ 5. Focus on statistical analysis and avoid any unnecessary data manipulation.
535
+
536
+ **Output:**
537
+
538
+ * The **code** implementing the statistical analysis, including all required steps.
539
+ * A **summary** of what the statistical analysis does, how it's performed, and why it fits the goal.
540
+
541
+ """
542
+ dataset = dspy.InputField(desc="Preprocessed dataset, often named df_cleaned")
543
+ goal = dspy.InputField(desc="The user's statistical analysis goal, e.g., regression or seasonal_decompose")
544
+ plan_instructions = dspy.InputField(desc="Instructions on variables to create and receive for statistical modeling")
545
+
546
+ code = dspy.OutputField(desc="Python code for statistical modeling using statsmodels")
547
+ summary = dspy.OutputField(desc="Explanation of statistical analysis steps")
548
+
549
+
550
+ class planner_sk_learn_agent(dspy.Signature):
551
+ """
552
+ **Agent Definition:**
553
+
554
+ You are a machine learning agent in a multi-agent data analytics pipeline.
555
+
556
+ You are given:
557
+
558
+ * A dataset (often cleaned and feature-engineered).
559
+ * A user-defined goal (e.g., classification, regression, clustering).
560
+ * Agent-specific **plan instructions** specifying:
561
+
562
+ * Which **variables** you are expected to **CREATE** (e.g., `trained_model`, `predictions`).
563
+ * Which **variables** you will **USE** (e.g., `df_cleaned`, `target_variable`, `feature_columns`).
564
+ * A set of **instructions** outlining additional processing or handling for these variables (e.g., handling missing values, applying transformations, or other task-specific guidelines).
565
+
566
+ **Your Responsibilities:**
567
+
568
+ * Use the scikit-learn library to implement the appropriate ML pipeline.
569
+ * Always split data into training and testing sets where applicable.
570
+ * Use `print()` for all outputs.
571
+ * Ensure your code is:
572
+
573
+ * **Reproducible**: Set `random_state=42` wherever applicable.
574
+ * **Modular**: Avoid deeply nested code.
575
+ * **Focused on model building**, not visualization (leave plotting to the `data_viz_agent`).
576
+ * Your task may include:
577
+
578
+ * Preprocessing inputs (e.g., encoding).
579
+ * Model selection and training.
580
+ * Evaluation (e.g., accuracy, RMSE, classification report).
581
+
582
+ **You must not:**
583
+
584
+ * Visualize anything (that's another agent's job).
585
+ * Rely on hardcoded column names — use those passed via `plan_instructions`.
586
+ * **Never create or modify any variables not explicitly mentioned in `plan_instructions['CREATE']`.**
587
+ * **Never create the `df` variable**. You will **only** work with the variables passed via the `plan_instructions`.
588
+ * Do not introduce intermediate variables unless they are listed in `plan_instructions['CREATE']`.
589
+
590
+ **Instructions to Follow:**
591
+
592
+ 1. **CREATE** only the variables specified in the `plan_instructions['CREATE']` list. Do not create any intermediate or new variables.
593
+ 2. **USE** only the variables specified in the `plan_instructions['USE']` list. You are **not allowed** to create or modify any variables not listed in the plan instructions.
594
+ 3. Follow any **processing instructions** in the `plan_instructions['INSTRUCTIONS']` list. This might include tasks like handling missing values, scaling features, or encoding categorical variables. Always perform these steps on the variables specified in the `plan_instructions`.
595
+ 4. Do **not reassign or modify** any variables passed via `plan_instructions`. These should be used as-is.
596
+
597
+ **Example Workflow:**
598
+ Given that the `plan_instructions` specifies variables to **CREATE** and **USE**, and includes instructions, your approach should look like this:
599
+
600
+ 1. Use `df_cleaned` and `feature_columns` from the `plan_instructions` to extract your features (`X`).
601
+ 2. Use `target_column` from `plan_instructions` to extract your target (`y`).
602
+ 3. If instructions are provided (e.g., scale or encode), follow them.
603
+ 4. Split data into training and testing sets using `train_test_split`.
604
+ 5. Train the model based on the received goal (classification, regression, etc.).
605
+ 6. Store the output variables as specified in `plan_instructions['CREATE']`.
606
+
607
+ ### Example Code Structure:
608
+
609
+ ```python
610
+ from sklearn.model_selection import train_test_split
611
+ from sklearn.linear_model import LogisticRegression
612
+ from sklearn.metrics import classification_report
613
+ from sklearn.preprocessing import StandardScaler
614
+
615
+ # Ensure that all variables follow plan instructions:
616
+ # Use received inputs: df_cleaned, feature_columns, target_column
617
+ X = df_cleaned[feature_columns]
618
+ y = df_cleaned[target_column]
619
+
620
+ # Apply any preprocessing instructions (e.g., scaling if instructed)
621
+ if 'scale' in plan_instructions['INSTRUCTIONS']:
622
+ scaler = StandardScaler()
623
+ X = scaler.fit_transform(X)
624
+
625
+ # Split the data into training and testing sets
626
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
627
+
628
+ # Select and train the model (based on the task)
629
+ model = LogisticRegression(random_state=42)
630
+ model.fit(X_train, y_train)
631
+
632
+ # Generate predictions
633
+ predictions = model.predict(X_test)
634
+
635
+ # Create the variable specified in 'plan_instructions': 'metrics'
636
+ metrics = classification_report(y_test, predictions)
637
+
638
+ # Print the results
639
+ print(metrics)
640
+
641
+ # Ensure the 'metrics' variable is returned as requested in the plan
642
+ ```
643
+
644
+ **Summary:**
645
+
646
+ 1. Always **USE** the variables passed in `plan_instructions['USE']` to build the pipeline.
647
+ 2. Only **CREATE** the variables specified in `plan_instructions['CREATE']`. Do not create any additional variables.
648
+ 3. Follow any **additional instructions** in `plan_instructions['INSTRUCTIONS']` (e.g., preprocessing steps).
649
+ 4. Ensure reproducibility by setting `random_state=42` wherever necessary.
650
+ 5. Focus on model building, evaluation, and saving the required outputs—avoid any unnecessary variables.
651
+
652
+ **Output:**
653
+
654
+ * The **code** implementing the ML task, including all required steps.
655
+ * A **summary** of what the model does, how it is evaluated, and why it fits the goal.
656
+
657
+
658
+
659
+ """
660
+ dataset = dspy.InputField(desc="Input dataset, often cleaned and feature-selected (e.g., df_cleaned)")
661
+ goal = dspy.InputField(desc="The user's machine learning goal (e.g., classification or regression)")
662
+ plan_instructions = dspy.InputField(desc="Instructions indicating what to create and what variables to receive")
663
+
664
+ code = dspy.OutputField(desc="Scikit-learn based machine learning code")
665
+ summary = dspy.OutputField(desc="Explanation of the ML approach and evaluation")
666
+
667
+ class goal_refiner_agent(dspy.Signature):
668
+ # Called to refine the query incase user query not elaborate
669
+ """You take a user-defined goal given to a AI data analyst planner agent,
670
+ you make the goal more elaborate using the datasets available and agent_desc"""
671
+ dataset = dspy.InputField(desc="Available datasets loaded in the system, use this df,columns set df as copy of df")
672
+ Agent_desc = dspy.InputField(desc= "The agents available in the system")
673
+ goal = dspy.InputField(desc="The user defined goal ")
674
+ refined_goal = dspy.OutputField(desc='Refined goal that helps the planner agent plan better')
675
+
676
+ class preprocessing_agent(dspy.Signature):
677
+ """You are a AI data-preprocessing agent. Generate clean and efficient Python code using NumPy and Pandas to perform introductory data preprocessing on a pre-loaded DataFrame df, based on the user's analysis goals.
678
+
679
+ Preprocessing Requirements:
680
+
681
+ 1. Identify Column Types
682
+ - Separate columns into numeric and categorical using:
683
+ categorical_columns = df.select_dtypes(include=[object, 'category']).columns.tolist()
684
+ numeric_columns = df.select_dtypes(include=[np.number]).columns.tolist()
685
+
686
+ 2. Handle Missing Values
687
+ - Numeric columns: Impute missing values using the mean of each column
688
+ - Categorical columns: Impute missing values using the mode of each column
689
+
690
+ 3. Convert Date Strings to Datetime
691
+ - For any column suspected to represent dates (in string format), convert it to datetime using:
692
+ def safe_to_datetime(date):
693
+ try:
694
+ return pd.to_datetime(date, errors='coerce', cache=False)
695
+ except (ValueError, TypeError):
696
+ return pd.NaT
697
+ df['datetime_column'] = df['datetime_column'].apply(safe_to_datetime)
698
+ - Replace 'datetime_column' with the actual column names containing date-like strings
699
+
700
+ Important Notes:
701
+ - Do NOT create a correlation matrix — correlation analysis is outside the scope of preprocessing
702
+ - Do NOT generate any plots or visualizations
703
+
704
+ Output Instructions:
705
+ 1. Include the full preprocessing Python code
706
+ 2. Provide a brief bullet-point summary of the steps performed. Example:
707
+ • Identified 5 numeric and 4 categorical columns
708
+ • Filled missing numeric values with column means
709
+ • Filled missing categorical values with column modes
710
+ • Converted 1 date column to datetime format
711
+ """
712
+ dataset = dspy.InputField(desc="Available datasets loaded in the system, use this df, column_names set df as copy of df")
713
+ goal = dspy.InputField(desc="The user defined goal could ")
714
+ code = dspy.OutputField(desc ="The code that does the data preprocessing and introductory analysis")
715
+ summary = dspy.OutputField(desc="A concise bullet-point summary of the preprocessing operations performed")
716
+
717
+
718
+
719
+ class statistical_analytics_agent(dspy.Signature):
720
+ # Statistical Analysis Agent, builds statistical models using StatsModel Package
721
+ """
722
+ You are a statistical analytics agent. Your task is to take a dataset and a user-defined goal and output Python code that performs the appropriate statistical analysis to achieve that goal. Follow these guidelines:
723
+
724
+ IMPORTANT: You may be provided with previous interaction history. The section marked "### Current Query:" contains the user's current request. Any text in "### Previous Interaction History:" is for context only and is NOT part of the current request.
725
+
726
+ Data Handling:
727
+
728
+ Always handle strings as categorical variables in a regression using statsmodels C(string_column).
729
+ Do not change the index of the DataFrame.
730
+ Convert X and y into float when fitting a model.
731
+ Error Handling:
732
+
733
+ Always check for missing values and handle them appropriately.
734
+ Ensure that categorical variables are correctly processed.
735
+ Provide clear error messages if the model fitting fails.
736
+ Regression:
737
+
738
+ For regression, use statsmodels and ensure that a constant term is added to the predictor using sm.add_constant(X).
739
+ Handle categorical variables using C(column_name) in the model formula.
740
+ Fit the model with model = sm.OLS(y.astype(float), X.astype(float)).fit().
741
+ Seasonal Decomposition:
742
+
743
+ Ensure the period is set correctly when performing seasonal decomposition.
744
+ Verify the number of observations works for the decomposition.
745
+ Output:
746
+
747
+ Ensure the code is executable and as intended.
748
+ Also choose the correct type of model for the problem
749
+ Avoid adding data visualization code.
750
+
751
+ Use code like this to prevent failing:
752
+ import pandas as pd
753
+ import numpy as np
754
+ import statsmodels.api as sm
755
+
756
+ def statistical_model(X, y, goal, period=None):
757
+ try:
758
+ # Check for missing values and handle them
759
+ X = X.dropna()
760
+ y = y.loc[X.index].dropna()
761
+
762
+ # Ensure X and y are aligned
763
+ X = X.loc[y.index]
764
+
765
+ # Convert categorical variables
766
+ for col in X.select_dtypes(include=['object', 'category']).columns:
767
+ X[col] = X[col].astype('category')
768
+
769
+ # Add a constant term to the predictor
770
+ X = sm.add_constant(X)
771
+
772
+ # Fit the model
773
+ if goal == 'regression':
774
+ # Handle categorical variables in the model formula
775
+ formula = 'y ~ ' + ' + '.join([f'C({col})' if X[col].dtype.name == 'category' else col for col in X.columns])
776
+ model = sm.OLS(y.astype(float), X.astype(float)).fit()
777
+ return model.summary()
778
+
779
+ elif goal == 'seasonal_decompose':
780
+ if period is None:
781
+ raise ValueError("Period must be specified for seasonal decomposition")
782
+ decomposition = sm.tsa.seasonal_decompose(y, period=period)
783
+ return decomposition
784
+
785
+ else:
786
+ raise ValueError("Unknown goal specified. Please provide a valid goal.")
787
+
788
+ except Exception as e:
789
+ return f"An error occurred: {e}"
790
+
791
+ # Example usage:
792
+ result = statistical_analysis(X, y, goal='regression')
793
+ print(result)
794
+
795
+ If visualizing use plotly
796
+
797
+ Provide a concise bullet-point summary of the statistical analysis performed.
798
+
799
+ Example Summary:
800
+ • Applied linear regression with OLS to predict house prices based on 5 features
801
+ • Model achieved R-squared of 0.78
802
+ • Significant predictors include square footage (p<0.001) and number of bathrooms (p<0.01)
803
+ • Detected strong seasonal pattern with 12-month periodicity
804
+ • Forecast shows 15% growth trend over next quarter
805
+
806
+ """
807
+ dataset = dspy.InputField(desc="Available datasets loaded in the system, use this df,columns set df as copy of df")
808
+ goal = dspy.InputField(desc="The user defined goal for the analysis to be performed")
809
+ code = dspy.OutputField(desc ="The code that does the statistical analysis using statsmodel")
810
+ summary = dspy.OutputField(desc="A concise bullet-point summary of the statistical analysis performed and key findings")
811
+
812
+
813
+ class sk_learn_agent(dspy.Signature):
814
+ # Machine Learning Agent, performs task using sci-kit learn
815
+ """You are a machine learning agent.
816
+ Your task is to take a dataset and a user-defined goal, and output Python code that performs the appropriate machine learning analysis to achieve that goal.
817
+ You should use the scikit-learn library.
818
+
819
+ IMPORTANT: You may be provided with previous interaction history. The section marked "### Current Query:" contains the user's current request. Any text in "### Previous Interaction History:" is for context only and is NOT part of the current request.
820
+
821
+ Make sure your output is as intended!
822
+
823
+ Provide a concise bullet-point summary of the machine learning operations performed.
824
+
825
+ Example Summary:
826
+ • Trained a Random Forest classifier on customer churn data with 80/20 train-test split
827
+ • Model achieved 92% accuracy and 88% F1-score
828
+ • Feature importance analysis revealed that contract length and monthly charges are the strongest predictors of churn
829
+ • Implemented K-means clustering (k=4) on customer shopping behaviors
830
+ • Identified distinct segments: high-value frequent shoppers (22%), occasional big spenders (35%), budget-conscious regulars (28%), and rare visitors (15%)
831
+
832
+ """
833
+ dataset = dspy.InputField(desc="Available datasets loaded in the system, use this df,columns. set df as copy of df")
834
+ goal = dspy.InputField(desc="The user defined goal ")
835
+ code = dspy.OutputField(desc ="The code that does the Exploratory data analysis")
836
+ summary = dspy.OutputField(desc="A concise bullet-point summary of the machine learning analysis performed and key results")
837
+
838
+
839
+
840
+ class story_teller_agent(dspy.Signature):
841
+ # Optional helper agent, which can be called to build a analytics story
842
+ # For all of the analysis performed
843
+ """ You are a story teller agent, taking output from different data analytics agents, you compose a compelling story for what was done """
844
+ agent_analysis_list =dspy.InputField(desc="A list of analysis descriptions from every agent")
845
+ story = dspy.OutputField(desc="A coherent story combining the whole analysis")
846
+
847
+ class code_combiner_agent(dspy.Signature):
848
+ # Combines code from different agents into one script
849
+ """ You are a code combine agent, taking Python code output from many agents and combining the operations into 1 output
850
+ You also fix any errors in the code.
851
+
852
+ IMPORTANT: You may be provided with previous interaction history. The section marked "### Current Query:" contains the user's current request. Any text in "### Previous Interaction History:" is for context only and is NOT part of the current request.
853
+
854
+ Double check column_names/dtypes using dataset, also check if applied logic works for the datatype
855
+ df = df.copy()
856
+ Also add this to display Plotly chart
857
+ fig.show()
858
+
859
+ Make sure your output is as intended!
860
+
861
+ Provide a concise bullet-point summary of the code integration performed.
862
+
863
+ Example Summary:
864
+ • Integrated preprocessing, statistical analysis, and visualization code into a single workflow.
865
+ • Fixed variable scope issues, standardized DataFrame handling (e.g., using `df.copy()`), and corrected errors.
866
+ • Validated column names and data types against the dataset definition to prevent runtime issues.
867
+ • Ensured visualizations are displayed correctly (e.g., added `fig.show()`).
868
+
869
+ """
870
+ dataset = dspy.InputField(desc="Use this double check column_names, data types")
871
+ agent_code_list =dspy.InputField(desc="A list of code given by each agent")
872
+ refined_complete_code = dspy.OutputField(desc="Refined complete code base")
873
+ summary = dspy.OutputField(desc="A concise 4 bullet-point summary of the code integration performed and improvements made")
874
+
875
+
876
+ class data_viz_agent(dspy.Signature):
877
+ # Visualizes data using Plotly
878
+ """
879
+ You are an AI agent responsible for generating interactive data visualizations using Plotly.
880
+
881
+ IMPORTANT Instructions:
882
+
883
+ - The section marked "### Current Query:" contains the user's request. Any text in "### Previous Interaction History:" is for context only and should NOT be treated as part of the current request.
884
+ - You must only use the tools provided to you. This agent handles visualization only.
885
+ - If len(df) > 50000, always sample the dataset before visualization using:
886
+ if len(df) > 50000:
887
+ df = df.sample(50000, random_state=1)
888
+
889
+ - Each visualization must be generated as a **separate figure** using go.Figure().
890
+ Do NOT use subplots under any circumstances.
891
+
892
+ - Each figure must be returned individually using:
893
+ fig.to_html(full_html=False)
894
+
895
+ - Use update_layout with xaxis and yaxis **only once per figure**.
896
+
897
+ - Enhance readability and clarity by:
898
+ • Using low opacity (0.4-0.7) where appropriate
899
+ • Applying visually distinct colors for different elements or categories
900
+
901
+ - Make sure the visual **answers the user's specific goal**:
902
+ • Identify what insight or comparison the user is trying to achieve
903
+ • Choose the visualization type and features (e.g., color, size, grouping) to emphasize that goal
904
+ • For example, if the user asks for "trends in revenue," use a time series line chart; if they ask for "top-performing categories," use a bar chart sorted by value
905
+ • Prioritize highlighting patterns, outliers, or comparisons relevant to the question
906
+
907
+ - Never include the dataset or styling index in the output.
908
+
909
+ - If there are no relevant columns for the requested visualization, respond with:
910
+ "No relevant columns found to generate this visualization."
911
+
912
+ - Use only one number format consistently: either 'K', 'M', or comma-separated values like 1,000/1,000,000. Do not mix formats.
913
+
914
+ - Only include trendlines in scatter plots if the user explicitly asks for them.
915
+
916
+ - Output only the code and a concise bullet-point summary of what the visualization reveals.
917
+
918
+ - Always end each visualization with:
919
+ fig.to_html(full_html=False)
920
+
921
+ Example Summary:
922
+ • Created an interactive scatter plot of sales vs. marketing spend with color-coded product categories
923
+ • Included a trend line showing positive correlation (r=0.72)
924
+ • Highlighted outliers where high marketing spend resulted in low sales
925
+ • Generated a time series chart of monthly revenue from 2020-2023
926
+ • Added annotations for key business events
927
+ • Visualization reveals 35% YoY growth with seasonal peaks in Q4
928
+ """
929
+ goal = dspy.InputField(desc="user defined goal which includes information about data and chart they want to plot")
930
+ dataset = dspy.InputField(desc=" Provides information about the data in the data frame. Only use column names and dataframe_name as in this context")
931
+ styling_index = dspy.InputField(desc='Provides instructions on how to style your Plotly plots')
932
+ code= dspy.OutputField(desc="Plotly code that visualizes what the user needs according to the query & dataframe_index & styling_context")
933
+ summary = dspy.OutputField(desc="A concise bullet-point summary of the visualization created and key insights revealed")
934
+
935
+
936
+
937
+ class code_fix(dspy.Signature):
938
+ """
939
+ You are an expert AI developer and data analyst assistant, skilled at identifying and resolving issues in Python code related to data analytics. Another agent has attempted to generate Python code for a data analytics task but produced code that is broken or throws an error.
940
+
941
+ Your task is to:
942
+ 1. Carefully examine the provided **faulty_code** and the corresponding **error** message.
943
+ 2. Identify the **exact cause** of the failure based on the error and surrounding context.
944
+ 3. Modify only the necessary portion(s) of the code to fix the issue, utilizing the **dataset_context** to inform your corrections.
945
+ 4. Ensure the **intended behavior** of the original code is preserved (e.g., if the code is meant to filter, group, or visualize data, that functionality must be preserved).
946
+ 5. Ensure the final output is **runnable**, **error-free**, and **logically consistent**.
947
+
948
+ Strict instructions:
949
+ - Assume the dataset is already loaded and available in the code context; do not include any code to read, load, or create data.
950
+ - Do **not** modify any working parts of the code unnecessarily.
951
+ - Do **not** change variable names, structure, or logic unless it directly contributes to resolving the issue.
952
+ - Do **not** output anything besides the corrected, full version of the code (i.e., no explanations, comments, or logs).
953
+ - Avoid introducing new dependencies or libraries unless absolutely required to fix the problem.
954
+ - The output must be complete and executable as-is.
955
+
956
+ Be precise, minimal, and reliable. Prioritize functional correctness.
957
+ """
958
+ dataset_context = dspy.InputField(desc="The dataset context to be used for the code fix")
959
+ faulty_code = dspy.InputField(desc="The faulty Python code used for data analytics")
960
+ # prior_fixes = dspy.InputField(desc="If a fix for this code exists in our error retriever", default="use the error message")
961
+ error = dspy.InputField(desc="The error message thrown when running the code")
962
+ fixed_code = dspy.OutputField(desc="The corrected and executable version of the code")
963
+ class code_edit(dspy.Signature):
964
+ """
965
+ You are an expert AI code editor that specializes in modifying existing data analytics code based on user requests. The user provides a working or partially working code snippet, a natural language prompt describing the desired change, and dataset context information.
966
+
967
+ Your job is to:
968
+ 1. Analyze the provided original_code, user_prompt, and dataset_context.
969
+ 2. Modify only the part(s) of the code that are relevant to the user's request, using the dataset context to inform your edits.
970
+ 3. Leave all unrelated parts of the code unchanged, unless the user explicitly requests a full rewrite or broader changes.
971
+ 4. Ensure that your changes maintain or improve the functionality and correctness of the code.
972
+
973
+ Strict requirements:
974
+ - Assume the dataset is already loaded and available in the code context; do not include any code to read, load, or create data.
975
+ - Do not change variable names, function structures, or logic outside the scope of the user's request.
976
+ - Do not refactor, optimize, or rewrite unless explicitly instructed.
977
+ - Ensure the edited code remains complete and executable.
978
+ - Output only the modified code, without any additional explanation, comments, or extra formatting.
979
+
980
+ Make your edits precise, minimal, and faithful to the user's instructions, using the dataset context to guide your modifications.
981
+ """
982
+ dataset_context = dspy.InputField(desc="The dataset context to be used for the code edit, including information about the dataset's shape, columns, types, and null values")
983
+ original_code = dspy.InputField(desc="The original code the user wants modified")
984
+ user_prompt = dspy.InputField(desc="The user instruction describing how the code should be changed")
985
+ edited_code = dspy.OutputField(desc="The updated version of the code reflecting the user's request, incorporating changes informed by the dataset context")
986
+
987
+ # The ind module is called when agent_name is
988
+ # explicitly mentioned in the query
989
+ class auto_analyst_ind(dspy.Module):
990
+ """Handles individual agent execution when explicitly specified in query"""
991
+
992
+ def __init__(self, agents, retrievers):
993
+ # Initialize agent modules and retrievers
994
+ self.agents = {}
995
+ self.agent_inputs = {}
996
+ self.agent_desc = []
997
+
998
+ # Create modules from agent signatures
999
+ for i, a in enumerate(agents):
1000
+ name = a.__pydantic_core_schema__['schema']['model_name']
1001
+ self.agents[name] = dspy.ChainOfThoughtWithHint(a)
1002
+ self.agent_inputs[name] = {x.strip() for x in str(agents[i].__pydantic_core_schema__['cls']).split('->')[0].split('(')[1].split(',')}
1003
+ self.agent_desc.append(get_agent_description(name))
1004
+
1005
+ # Initialize components
1006
+ self.memory_summarize_agent = dspy.ChainOfThought(m.memory_summarize_agent)
1007
+ self.dataset = retrievers['dataframe_index'].as_retriever(k=1)
1008
+ self.styling_index = retrievers['style_index'].as_retriever(similarity_top_k=1)
1009
+ self.code_combiner_agent = dspy.ChainOfThought(code_combiner_agent)
1010
+
1011
+ # Initialize thread pool
1012
+ self.executor = ThreadPoolExecutor(max_workers=min(4, os.cpu_count() * 2))
1013
+
1014
+ def execute_agent(self, specified_agent, inputs):
1015
+ """Execute agent and generate memory summary in parallel"""
1016
+ try:
1017
+ # Execute main agent
1018
+ agent_result = self.agents[specified_agent.strip()](**inputs)
1019
+ return specified_agent.strip(), dict(agent_result)
1020
+
1021
+ except Exception as e:
1022
+ return specified_agent.strip(), {"error": str(e)}
1023
+
1024
+ def execute_agent_with_memory(self, specified_agent, inputs, query):
1025
+ """Execute agent and generate memory summary in parallel"""
1026
+ try:
1027
+ # Execute main agent
1028
+ agent_result = self.agents[specified_agent.strip()](**inputs)
1029
+ agent_dict = dict(agent_result)
1030
+
1031
+ # Generate memory summary
1032
+ memory_result = self.memory_summarize_agent(
1033
+ agent_response=specified_agent+' '+agent_dict['code']+'\n'+agent_dict['summary'],
1034
+ user_goal=query
1035
+ )
1036
+
1037
+ return {
1038
+ specified_agent.strip(): agent_dict,
1039
+ 'memory_'+specified_agent.strip(): str(memory_result.summary)
1040
+ }
1041
+ except Exception as e:
1042
+ return {"error": str(e)}
1043
+
1044
+ def forward(self, query, specified_agent):
1045
+ try:
1046
+ # If specified_agent contains multiple agents separated by commas
1047
+ # This is for handling multiple @agent mentions in one query
1048
+ if "," in specified_agent:
1049
+ agent_list = [agent.strip() for agent in specified_agent.split(",")]
1050
+ return self.execute_multiple_agents(query, agent_list)
1051
+
1052
+ # Process query with specified agent (single agent case)
1053
+ dict_ = {}
1054
+ dict_['dataset'] = self.dataset.retrieve(query)[0].text
1055
+ dict_['styling_index'] = self.styling_index.retrieve(query)[0].text
1056
+ dict_['hint'] = []
1057
+ dict_['goal'] = query
1058
+ dict_['Agent_desc'] = str(self.agent_desc)
1059
+
1060
+ # Prepare inputs
1061
+ inputs = {x:dict_[x] for x in self.agent_inputs[specified_agent.strip()]}
1062
+ inputs['hint'] = str(dict_['hint']).replace('[','').replace(']','')
1063
+
1064
+ # Execute agent
1065
+ result = self.agents[specified_agent.strip()](**inputs)
1066
+ output_dict = {specified_agent.strip(): dict(result)}
1067
+
1068
+ if "error" in output_dict:
1069
+ return {"response": f"Error executing agent: {output_dict['error']}"}
1070
+
1071
+ return output_dict
1072
+
1073
+ except Exception as e:
1074
+ return {"response": f"This is the error from the system: {str(e)}"}
1075
+
1076
+ def execute_multiple_agents(self, query, agent_list):
1077
+ """Execute multiple agents sequentially on the same query"""
1078
+ try:
1079
+ # Initialize resources
1080
+ dict_ = {}
1081
+ dict_['dataset'] = self.dataset.retrieve(query)[0].text
1082
+ dict_['styling_index'] = self.styling_index.retrieve(query)[0].text
1083
+ dict_['hint'] = []
1084
+ dict_['goal'] = query
1085
+ dict_['Agent_desc'] = str(self.agent_desc)
1086
+
1087
+ results = {}
1088
+ code_list = []
1089
+
1090
+ # Execute each agent sequentially
1091
+ for agent_name in agent_list:
1092
+ if agent_name not in self.agents:
1093
+ results[agent_name] = {"error": f"Agent '{agent_name}' not found"}
1094
+ continue
1095
+
1096
+ # Prepare inputs for this agent
1097
+ inputs = {x:dict_[x] for x in self.agent_inputs[agent_name] if x in dict_}
1098
+ inputs['hint'] = str(dict_['hint']).replace('[','').replace(']','')
1099
+
1100
+ # Execute agent
1101
+ agent_result = self.agents[agent_name](**inputs)
1102
+ agent_dict = dict(agent_result)
1103
+ results[agent_name] = agent_dict
1104
+
1105
+ # Collect code for later combination
1106
+ if 'code' in agent_dict:
1107
+ code_list.append(agent_dict['code'])
1108
+
1109
+ return results
1110
+
1111
+ except Exception as e:
1112
+ return {"response": f"Error executing multiple agents: {str(e)}"}
1113
+
1114
+
1115
+ # This is the auto_analyst with planner
1116
+ class auto_analyst(dspy.Module):
1117
+ """Main analyst module that coordinates multiple agents using a planner"""
1118
+
1119
+ def __init__(self, agents, retrievers):
1120
+ # Initialize agent modules and retrievers
1121
+ self.agents = {}
1122
+ self.agent_inputs = {}
1123
+ self.agent_desc = []
1124
+
1125
+ for i, a in enumerate(agents):
1126
+ name = a.__pydantic_core_schema__['schema']['model_name']
1127
+ self.agents[name] = dspy.ChainOfThought(a)
1128
+ self.agent_inputs[name] = {x.strip() for x in str(agents[i].__pydantic_core_schema__['cls']).split('->')[0].split('(')[1].split(',')}
1129
+ self.agent_desc.append({name: get_agent_description(name)})
1130
+
1131
+ # Initialize coordination agents
1132
+ self.planner = dspy.ChainOfThought(analytical_planner)
1133
+ self.refine_goal = dspy.ChainOfThought(goal_refiner_agent)
1134
+ self.code_combiner_agent = dspy.ChainOfThought(code_combiner_agent)
1135
+ self.story_teller = dspy.ChainOfThought(story_teller_agent)
1136
+ self.memory_summarize_agent = dspy.ChainOfThought(m.memory_summarize_agent)
1137
+
1138
+ # Initialize retrievers
1139
+ self.dataset = retrievers['dataframe_index'].as_retriever(k=1)
1140
+ self.styling_index = retrievers['style_index'].as_retriever(similarity_top_k=1)
1141
+
1142
+ # Initialize thread pool for parallel execution
1143
+ self.executor = ThreadPoolExecutor(max_workers=min(len(agents) + 2, os.cpu_count() * 2))
1144
+
1145
+ def execute_agent(self, agent_name, inputs):
1146
+ """Execute a single agent with given inputs"""
1147
+ try:
1148
+ result = self.agents[agent_name.strip()](**inputs)
1149
+ return agent_name.strip(), dict(result)
1150
+ except Exception as e:
1151
+ return agent_name.strip(), {"error": str(e)}
1152
+
1153
+ def get_plan(self, query):
1154
+ """Get the analysis plan"""
1155
+ dict_ = {}
1156
+ dict_['dataset'] = self.dataset.retrieve(query)[0].text
1157
+ dict_['styling_index'] = self.styling_index.retrieve(query)[0].text
1158
+ dict_['goal'] = query
1159
+ dict_['Agent_desc'] = str(self.agent_desc)
1160
+
1161
+ plan = self.planner(goal=dict_['goal'], dataset=dict_['dataset'], Agent_desc=dict_['Agent_desc'])
1162
+ return dict(plan)
1163
+
1164
+ async def execute_plan(self, query, plan):
1165
+ """Execute the plan and yield results as they complete"""
1166
+ dict_ = {}
1167
+ dict_['dataset'] = self.dataset.retrieve(query)[0].text
1168
+ dict_['styling_index'] = self.styling_index.retrieve(query)[0].text
1169
+ dict_['hint'] = []
1170
+ dict_['goal'] = query
1171
+
1172
+ import json
1173
+
1174
+ # Clean and split the plan string into agent names
1175
+ plan_text = plan.plan.replace("Plan", "").replace(":", "").strip()
1176
+ plan_list = [agent.strip() for agent in plan_text.split("->") if agent.strip()]
1177
+ # logger.log(f"Plan list: {plan_list}")
1178
+ # Parse the attached plan_instructions into a dict
1179
+ raw_instr = plan.plan_instructions
1180
+ # logger.log(f"Raw instructions: {raw_instr}")
1181
+ if isinstance(raw_instr, str):
1182
+ try:
1183
+ plan_instructions = json.loads(raw_instr)
1184
+ except Exception:
1185
+ plan_instructions = {}
1186
+ elif isinstance(raw_instr, dict):
1187
+ plan_instructions = raw_instr
1188
+ else:
1189
+ plan_instructions = {}
1190
+ logger
1191
+ # If no plan was produced, short-circuit
1192
+ if not plan_list:
1193
+ yield "plan_not_found", dict(plan), {"error": "No plan found"}
1194
+ return
1195
+
1196
+ # Launch each agent in parallel, attaching its own instructions
1197
+ futures = []
1198
+ for agent_name in plan_list:
1199
+ key = agent_name.strip()
1200
+ # gather input fields except plan_instructions
1201
+ inputs = {
1202
+ param: dict_[param]
1203
+ for param in self.agent_inputs[key]
1204
+ if param != "plan_instructions"
1205
+ }
1206
+ # attach the specific instructions for this agent (or defaults)
1207
+ if "plan_instructions" in self.agent_inputs[key]:
1208
+ inputs['plan_instructions'] = plan_instructions
1209
+ inputs["your_task"] = str(plan_instructions.get(
1210
+ key, ""
1211
+ ))
1212
+ # logger.log(f"Inputs: {inputs}")
1213
+ future = self.executor.submit(self.execute_agent, agent_name, inputs)
1214
+ futures.append((agent_name, inputs, future))
1215
+ # Yield results as they complete
1216
+ completed_results = []
1217
+ for agent_name, inputs, future in futures:
1218
+ try:
1219
+ name, result = await asyncio.get_event_loop().run_in_executor(None, future.result)
1220
+ completed_results.append((name, result))
1221
+ yield name, inputs, result
1222
+ except Exception as e:
1223
+ yield agent_name, inputs, {"error": str(e)}