Update src/agents/agents.py
#5
by FireBird-Tech - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- .dockerignore +0 -2
- .env-template +6 -5
- .gitignore +3 -23
- .huggingface.yaml +0 -1
- Dockerfile +1 -22
- Procfile +0 -1
- agents_config.json +0 -147
- app.py +426 -2451
- chat_database.db +3 -0
- docs/README.md +0 -251
- docs/api/routes/analytics.md +0 -562
- docs/api/routes/deep_analysis.md +0 -348
- docs/api/routes/feedback.md +0 -153
- docs/api/routes/session.md +0 -273
- docs/api/routes/templates.md +0 -363
- docs/architecture/architecture.md +0 -427
- docs/development/development_workflow.md +0 -506
- docs/{api/README.md → endpoints.md} +4 -16
- docs/getting_started.md +0 -273
- docs/routes/analytics.md +200 -0
- docs/{api/routes → routes}/chats.md +109 -33
- docs/{api/routes → routes}/code.md +14 -51
- docs/routes/core.md +242 -0
- docs/{system/shared_dataframe.md → shared_dataframe.md} +0 -0
- docs/system/database-schema.md +0 -289
- docs/troubleshooting/troubleshooting.md +0 -537
- entrypoint_local.sh +0 -175
- images/AI snapshot-chat.png +3 -0
- images/Auto-Analyst Banner.png +3 -0
- images/Auto-analyst-poster.png +3 -0
- images/Auto-analysts icon small.png +3 -0
- images/auto-analyst logo.png +3 -0
- requirements.txt +24 -29
- scripts/create_test_user.py +45 -0
- scripts/format_response.py +126 -588
- scripts/generate_test_data.py +90 -0
- scripts/init_production_db.py +0 -191
- scripts/populate_agent_templates.py +0 -508
- scripts/setup_analytics_data.py +114 -0
- scripts/test_agent_parsing.py +42 -0
- scripts/test_model_usage.py +114 -0
- scripts/test_token_counting.py +176 -0
- scripts/tier_maker.py +6 -27
- scripts/verify_session_state.py +76 -0
- src/agents/agents.py +0 -0
- src/agents/deep_agents.py +0 -1110
- src/agents/retrievers/retrievers.py +89 -3
- src/agents/test_agent.py +1223 -0
- src/db/schemas/models.py +11 -123
- src/managers/ai_manager.py +4 -2
.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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 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"
|
|
|
|
|
|
|
|
|
.gitignore
CHANGED
|
@@ -11,12 +11,14 @@ venv/
|
|
| 11 |
|
| 12 |
try*
|
| 13 |
|
|
|
|
|
|
|
| 14 |
logs/
|
| 15 |
|
| 16 |
updated_code.py
|
| 17 |
sample_code.py
|
| 18 |
|
| 19 |
-
|
| 20 |
*.dump
|
| 21 |
|
| 22 |
migrations/
|
|
@@ -28,25 +30,3 @@ alembic.ini
|
|
| 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/
|
|
|
|
| 30 |
*.db
|
| 31 |
|
| 32 |
schema*.md
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.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,863 @@
|
|
| 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 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 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-
|
| 296 |
"api_key": os.getenv("OPENAI_API_KEY"),
|
| 297 |
-
"temperature":
|
| 298 |
-
"max_tokens": int(os.getenv("MAX_TOKENS", 6000))
|
| 299 |
-
|
| 300 |
}
|
| 301 |
|
| 302 |
-
|
| 303 |
-
|
| 304 |
# Create default LM config but don't set it globally
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 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 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 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 |
-
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
|
| 1445 |
-
|
| 1446 |
-
|
| 1447 |
-
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
-
|
| 1458 |
-
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
| 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 |
-
"
|
| 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 |
-
"
|
| 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 |
-
|
| 1738 |
-
|
| 1739 |
-
|
| 1740 |
-
|
| 1741 |
-
|
| 1742 |
-
|
| 1743 |
-
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
| 1747 |
-
|
| 1748 |
-
|
| 1749 |
-
|
| 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 |
-
"
|
| 1850 |
-
"anthropic": "claude-sonnet-4-6",
|
| 1851 |
"gemini": "gemini-2.5-pro-preview-03-25"
|
| 1852 |
}
|
| 1853 |
-
return provider_model_map.get(provider, "
|
| 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 |
-
|
| 1876 |
-
|
| 1877 |
-
|
| 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 |
-
|
| 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 |
-
|
| 2040 |
-
|
| 2041 |
-
|
| 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.feedback_routes import router as feedback_router
|
|
|
|
| 41 |
from src.routes.session_routes import router as session_router, get_session_id_dependency
|
| 42 |
+
from src.schemas.query_schemas import QueryRequest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
from src.utils.logger import Logger
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
logger = Logger("app", see_time=True, console_log=False)
|
| 47 |
load_dotenv()
|
| 48 |
|
| 49 |
+
styling_instructions = [
|
| 50 |
+
"""
|
| 51 |
+
Dont ignore any of these instructions.
|
| 52 |
+
For a line chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
|
| 53 |
+
Always give a title and make bold using html tag axis label and try to use multiple colors if more than one line
|
| 54 |
+
Annotate the min and max of the line
|
| 55 |
+
Display numbers in thousand(K) or Million(M) if larger than 1000/100000
|
| 56 |
+
Show percentages in 2 decimal points with '%' sign
|
| 57 |
+
Default size of chart should be height =1200 and width =1000
|
| 58 |
+
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
, """
|
| 62 |
+
Dont ignore any of these instructions.
|
| 63 |
+
For a bar chart always use plotly_white template, reduce x axes & y axes line to 0.2 & x & y grid width to 1.
|
| 64 |
+
Always give a title and make bold using html tag axis label
|
| 65 |
+
Always display numbers in thousand(K) or Million(M) if larger than 1000/100000.
|
| 66 |
+
Annotate the values of the bar chart
|
| 67 |
+
If variable is a percentage show in 2 decimal points with '%' sign.
|
| 68 |
+
Default size of chart should be height =1200 and width =1000
|
| 69 |
+
"""
|
| 70 |
+
,
|
| 71 |
+
|
| 72 |
+
"""
|
| 73 |
+
For a histogram chart choose a bin_size of 50
|
| 74 |
+
Do not ignore any of these instructions
|
| 75 |
+
always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
|
| 76 |
+
Always give a title and make bold using html tag axis label
|
| 77 |
+
Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
|
| 78 |
+
If variable is a percentage show in 2 decimal points with '%'
|
| 79 |
+
Default size of chart should be height =1200 and width =1000
|
| 80 |
+
""",
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
"""
|
| 84 |
+
For a pie chart only show top 10 categories, bundle rest as others
|
| 85 |
+
Do not ignore any of these instructions
|
| 86 |
+
always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
|
| 87 |
+
Always give a title and make bold using html tag axis label
|
| 88 |
+
Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
|
| 89 |
+
If variable is a percentage show in 2 decimal points with '%'
|
| 90 |
+
Default size of chart should be height =1200 and width =1000
|
| 91 |
+
""",
|
| 92 |
+
|
| 93 |
+
"""
|
| 94 |
+
Do not ignore any of these instructions
|
| 95 |
+
always use plotly_white template, reduce x & y axes line to 0.2 & x & y grid width to 1.
|
| 96 |
+
Always give a title and make bold using html tag axis label
|
| 97 |
+
Always display numbers in thousand(K) or Million(M) if larger than 1000/100000. Add annotations x values
|
| 98 |
+
Don't add K/M if number already in , or value is not a number
|
| 99 |
+
If variable is a percentage show in 2 decimal points with '%'
|
| 100 |
+
Default size of chart should be height =1200 and width =1000
|
| 101 |
+
""",
|
| 102 |
+
"""
|
| 103 |
+
For a heat map
|
| 104 |
+
Use the 'plotly_white' template for a clean, white background.
|
| 105 |
+
Set a chart title
|
| 106 |
+
Style the X-axis with a black line color, 0.2 line width, 1 grid width, format 1000/1000000 as K/M
|
| 107 |
+
Do not format non-numerical numbers
|
| 108 |
+
.style the Y-axis with a black line color, 0.2 line width, 1 grid width format 1000/1000000 as K/M
|
| 109 |
+
Do not format non-numerical numbers
|
| 110 |
+
|
| 111 |
+
. Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
|
| 112 |
+
""",
|
| 113 |
+
"""
|
| 114 |
+
For a Histogram, used for returns/distribution plotting
|
| 115 |
+
Use the 'plotly_white' template for a clean, white background.
|
| 116 |
+
Set a chart title
|
| 117 |
+
Style the X-axis 1 grid width, format 1000/1000000 as K/M
|
| 118 |
+
Do not format non-numerical numbers
|
| 119 |
+
.style the Y-axis, 1 grid width format 1000/1000000 as K/M
|
| 120 |
+
Do not format non-numerical numbers
|
| 121 |
+
|
| 122 |
+
Use an opacity of 0.75
|
| 123 |
+
|
| 124 |
+
Set the figure dimensions to a height of 1200 pixels and a width of 1000 pixels.
|
| 125 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
]
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
# Add near the top of the file, after imports
|
|
|
|
| 129 |
DEFAULT_MODEL_CONFIG = {
|
| 130 |
"provider": os.getenv("MODEL_PROVIDER", "openai"),
|
| 131 |
+
"model": os.getenv("MODEL_NAME", "gpt-4o-mini"),
|
| 132 |
"api_key": os.getenv("OPENAI_API_KEY"),
|
| 133 |
+
"temperature": float(os.getenv("TEMPERATURE", 1.0)),
|
| 134 |
+
"max_tokens": int(os.getenv("MAX_TOKENS", 6000))
|
|
|
|
| 135 |
}
|
| 136 |
|
|
|
|
|
|
|
| 137 |
# Create default LM config but don't set it globally
|
| 138 |
+
if DEFAULT_MODEL_CONFIG["provider"].lower() == "groq":
|
| 139 |
+
default_lm = dspy.GROQ(
|
| 140 |
+
model=DEFAULT_MODEL_CONFIG["model"],
|
| 141 |
+
api_key=DEFAULT_MODEL_CONFIG["api_key"],
|
| 142 |
+
temperature=DEFAULT_MODEL_CONFIG["temperature"],
|
| 143 |
+
max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
|
| 144 |
+
)
|
| 145 |
+
elif DEFAULT_MODEL_CONFIG["provider"].lower() == "gemini":
|
| 146 |
+
default_lm = dspy.LM(
|
| 147 |
+
model=f"gemini/{DEFAULT_MODEL_CONFIG['model']}",
|
| 148 |
+
api_key=DEFAULT_MODEL_CONFIG["api_key"],
|
| 149 |
+
temperature=DEFAULT_MODEL_CONFIG["temperature"],
|
| 150 |
+
max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
default_lm = dspy.LM(
|
| 154 |
+
model=DEFAULT_MODEL_CONFIG["model"],
|
| 155 |
+
api_key=DEFAULT_MODEL_CONFIG["api_key"],
|
| 156 |
+
temperature=DEFAULT_MODEL_CONFIG["temperature"],
|
| 157 |
+
max_tokens=DEFAULT_MODEL_CONFIG["max_tokens"]
|
| 158 |
+
)
|
| 159 |
|
| 160 |
# Function to get model config from session or use default
|
|
|
|
| 161 |
def get_session_lm(session_state):
|
|
|
|
| 162 |
"""Get the appropriate LM instance for a session, or default if not configured"""
|
|
|
|
| 163 |
# First check if we have a valid session-specific model config
|
|
|
|
| 164 |
if session_state and isinstance(session_state, dict) and "model_config" in session_state:
|
|
|
|
| 165 |
model_config = session_state["model_config"]
|
|
|
|
| 166 |
if model_config and isinstance(model_config, dict) and "model" in model_config:
|
|
|
|
| 167 |
# Found valid session-specific model config, use it
|
|
|
|
| 168 |
provider = model_config.get("provider", "openai").lower()
|
| 169 |
+
if provider == "groq":
|
| 170 |
+
return dspy.GROQ(
|
| 171 |
+
model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
|
| 172 |
+
api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
|
| 173 |
+
temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
|
| 174 |
+
max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
|
| 175 |
+
)
|
| 176 |
+
elif provider == "anthropic":
|
| 177 |
+
return dspy.LM(
|
| 178 |
+
model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
|
| 179 |
+
api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
|
| 180 |
+
temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
|
| 181 |
+
max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
|
| 182 |
+
)
|
| 183 |
+
elif provider == "gemini":
|
| 184 |
+
return dspy.LM(
|
| 185 |
+
model=f"gemini/{model_config.get('model', DEFAULT_MODEL_CONFIG['model'])}",
|
| 186 |
+
api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
|
| 187 |
+
temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
|
| 188 |
+
max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
|
| 189 |
+
)
|
| 190 |
+
else: # OpenAI is the default
|
| 191 |
+
return dspy.LM(
|
| 192 |
+
model=model_config.get("model", DEFAULT_MODEL_CONFIG["model"]),
|
| 193 |
+
api_key=model_config.get("api_key", DEFAULT_MODEL_CONFIG["api_key"]),
|
| 194 |
+
temperature=model_config.get("temperature", DEFAULT_MODEL_CONFIG["temperature"]),
|
| 195 |
+
max_tokens=model_config.get("max_tokens", DEFAULT_MODEL_CONFIG["max_tokens"])
|
| 196 |
+
)
|
| 197 |
|
|
|
|
| 198 |
# If no valid session config, use default
|
| 199 |
+
return default_lm
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
# Initialize retrievers with empty data first
|
| 202 |
+
def initialize_retrievers(styling_instructions: List[str], doc: List[str]):
|
| 203 |
+
try:
|
| 204 |
+
style_index = VectorStoreIndex.from_documents([Document(text=x) for x in styling_instructions])
|
| 205 |
+
data_index = VectorStoreIndex.from_documents([Document(text=x) for x in doc])
|
| 206 |
+
return {"style_index": style_index, "dataframe_index": data_index}
|
| 207 |
+
except Exception as e:
|
| 208 |
+
logger.log_message(f"Error initializing retrievers: {str(e)}", level=logging.ERROR)
|
| 209 |
+
raise e
|
| 210 |
|
| 211 |
# clear console
|
|
|
|
| 212 |
def clear_console():
|
|
|
|
| 213 |
os.system('cls' if os.name == 'nt' else 'clear')
|
| 214 |
|
| 215 |
|
|
|
|
|
|
|
|
|
|
| 216 |
# Check for Housing.csv
|
|
|
|
| 217 |
housing_csv_path = "Housing.csv"
|
|
|
|
| 218 |
if not os.path.exists(housing_csv_path):
|
|
|
|
| 219 |
logger.log_message(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}", level=logging.ERROR)
|
|
|
|
| 220 |
raise FileNotFoundError(f"Housing.csv not found at {os.path.abspath(housing_csv_path)}")
|
| 221 |
|
| 222 |
+
AVAILABLE_AGENTS = {
|
| 223 |
+
"data_viz_agent": data_viz_agent,
|
| 224 |
+
"sk_learn_agent": sk_learn_agent,
|
| 225 |
+
"statistical_analytics_agent": statistical_analytics_agent,
|
| 226 |
+
"preprocessing_agent": preprocessing_agent,
|
| 227 |
+
}
|
| 228 |
|
| 229 |
+
PLANNER_AGENTS = {
|
| 230 |
+
"planner_preprocessing_agent": planner_preprocessing_agent,
|
| 231 |
+
"planner_sk_learn_agent": planner_sk_learn_agent,
|
| 232 |
+
"planner_statistical_analytics_agent": planner_statistical_analytics_agent,
|
| 233 |
+
"planner_data_viz_agent": planner_data_viz_agent,
|
| 234 |
+
}
|
| 235 |
|
| 236 |
# Add session header
|
|
|
|
| 237 |
X_SESSION_ID = APIKeyHeader(name="X-Session-ID", auto_error=False)
|
| 238 |
|
|
|
|
|
|
|
| 239 |
# Update AppState class to use SessionManager
|
| 240 |
+
class AppState:
|
| 241 |
+
def __init__(self):
|
| 242 |
+
self._session_manager = SessionManager(styling_instructions, PLANNER_AGENTS)
|
| 243 |
+
self.model_config = DEFAULT_MODEL_CONFIG.copy()
|
| 244 |
+
# Update the SessionManager with the current model_config
|
| 245 |
+
self._session_manager._app_model_config = self.model_config
|
| 246 |
+
self.ai_manager = AI_Manager()
|
| 247 |
+
self.chat_name_agent = chat_history_name_agent
|
| 248 |
+
|
| 249 |
+
def get_session_state(self, session_id: str):
|
| 250 |
+
"""Get or create session-specific state using the SessionManager"""
|
| 251 |
+
return self._session_manager.get_session_state(session_id)
|
| 252 |
|
| 253 |
+
def clear_session_state(self, session_id: str):
|
| 254 |
+
"""Clear session-specific state using the SessionManager"""
|
| 255 |
+
self._session_manager.clear_session_state(session_id)
|
| 256 |
|
| 257 |
+
def update_session_dataset(self, session_id: str, df, name, desc):
|
| 258 |
+
"""Update dataset for a specific session using the SessionManager"""
|
| 259 |
+
self._session_manager.update_session_dataset(session_id, df, name, desc)
|
| 260 |
|
| 261 |
+
def reset_session_to_default(self, session_id: str):
|
| 262 |
+
"""Reset a session to use the default dataset using the SessionManager"""
|
| 263 |
+
self._session_manager.reset_session_to_default(session_id)
|
| 264 |
+
|
| 265 |
+
def set_session_user(self, session_id: str, user_id: int, chat_id: int = None):
|
| 266 |
+
"""Associate a user with a session using the SessionManager"""
|
| 267 |
+
return self._session_manager.set_session_user(session_id, user_id, chat_id)
|
| 268 |
+
|
| 269 |
+
def get_ai_manager(self):
|
| 270 |
+
"""Get the AI Manager instance"""
|
| 271 |
+
return self.ai_manager
|
| 272 |
+
|
| 273 |
+
def get_provider_for_model(self, model_name):
|
| 274 |
+
return self.ai_manager.get_provider_for_model(model_name)
|
| 275 |
+
|
| 276 |
+
def calculate_cost(self, model_name, input_tokens, output_tokens):
|
| 277 |
+
return self.ai_manager.calculate_cost(model_name, input_tokens, output_tokens)
|
| 278 |
+
|
| 279 |
+
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):
|
| 280 |
+
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)
|
| 281 |
+
|
| 282 |
+
def get_tokenizer(self):
|
| 283 |
+
return self.ai_manager.tokenizer
|
| 284 |
+
|
| 285 |
+
def get_chat_history_name_agent(self):
|
| 286 |
+
return dspy.Predict(self.chat_name_agent)
|
| 287 |
|
| 288 |
# Initialize FastAPI app with state
|
|
|
|
| 289 |
app = FastAPI(title="AI Analytics API", version="1.0")
|
| 290 |
+
app.state = AppState()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
# Configure middleware
|
|
|
|
| 293 |
# Use a wildcard for local development or read from environment
|
|
|
|
| 294 |
is_development = os.getenv("ENVIRONMENT", "development").lower() == "development"
|
| 295 |
|
|
|
|
|
|
|
| 296 |
allowed_origins = []
|
|
|
|
| 297 |
frontend_url = os.getenv("FRONTEND_URL", "").strip()
|
|
|
|
| 298 |
print(f"FRONTEND_URL: {frontend_url}")
|
|
|
|
| 299 |
if is_development:
|
|
|
|
| 300 |
allowed_origins = ["*"]
|
|
|
|
| 301 |
elif frontend_url:
|
|
|
|
| 302 |
allowed_origins = [frontend_url]
|
|
|
|
| 303 |
else:
|
|
|
|
| 304 |
logger.log_message("CORS misconfigured: FRONTEND_URL not set", level=logging.ERROR)
|
|
|
|
| 305 |
allowed_origins = [] # or set a default safe origin
|
| 306 |
|
|
|
|
|
|
|
| 307 |
# Add a strict origin verification middleware
|
|
|
|
| 308 |
@app.middleware("http")
|
|
|
|
| 309 |
async def verify_origin_middleware(request: Request, call_next):
|
|
|
|
| 310 |
# Skip origin check in development mode
|
|
|
|
| 311 |
if is_development:
|
|
|
|
| 312 |
return await call_next(request)
|
|
|
|
| 313 |
|
|
|
|
| 314 |
# Get the origin from the request headers
|
|
|
|
| 315 |
origin = request.headers.get("origin")
|
|
|
|
| 316 |
|
|
|
|
| 317 |
# Log the origin for debugging
|
|
|
|
| 318 |
if origin:
|
|
|
|
| 319 |
print(f"Request from origin: {origin}")
|
|
|
|
| 320 |
|
|
|
|
| 321 |
# If no origin header or origin not in allowed list, reject the request
|
|
|
|
| 322 |
if origin and frontend_url and origin != frontend_url:
|
|
|
|
| 323 |
print(f"Blocked request from unauthorized origin: {origin}")
|
|
|
|
| 324 |
return JSONResponse(
|
|
|
|
| 325 |
status_code=403,
|
|
|
|
| 326 |
content={"detail": "Not authorized"}
|
|
|
|
| 327 |
)
|
|
|
|
| 328 |
|
|
|
|
| 329 |
# Continue processing the request if origin is allowed
|
|
|
|
| 330 |
return await call_next(request)
|
| 331 |
|
|
|
|
|
|
|
| 332 |
# CORS middleware (still needed for browser preflight)
|
|
|
|
| 333 |
app.add_middleware(
|
|
|
|
| 334 |
CORSMiddleware,
|
|
|
|
| 335 |
allow_origins=allowed_origins,
|
|
|
|
| 336 |
allow_origin_regex=None,
|
|
|
|
| 337 |
allow_credentials=True,
|
|
|
|
| 338 |
allow_methods=["*"],
|
|
|
|
| 339 |
allow_headers=["*"],
|
|
|
|
| 340 |
expose_headers=["*"],
|
|
|
|
| 341 |
max_age=600 # Cache preflight requests for 10 minutes (for performance)
|
|
|
|
| 342 |
)
|
| 343 |
|
|
|
|
|
|
|
| 344 |
# Add these constants at the top of the file with other imports/constants
|
|
|
|
| 345 |
RESPONSE_ERROR_INVALID_QUERY = "Please provide a valid query..."
|
|
|
|
| 346 |
RESPONSE_ERROR_NO_DATASET = "No dataset is currently loaded. Please link a dataset before proceeding with your analysis."
|
|
|
|
| 347 |
DEFAULT_TOKEN_RATIO = 1.5
|
| 348 |
+
REQUEST_TIMEOUT_SECONDS = 60 # Timeout for LLM requests
|
| 349 |
+
MAX_RECENT_MESSAGES = 3
|
|
|
|
|
|
|
|
|
|
| 350 |
DB_BATCH_SIZE = 10 # For future batch DB operations
|
| 351 |
|
| 352 |
+
# Replace the existing chat_with_agent function
|
|
|
|
| 353 |
@app.post("/chat/{agent_name}", response_model=dict)
|
|
|
|
| 354 |
async def chat_with_agent(
|
|
|
|
| 355 |
agent_name: str,
|
|
|
|
| 356 |
request: QueryRequest,
|
|
|
|
| 357 |
request_obj: Request,
|
|
|
|
| 358 |
session_id: str = Depends(get_session_id_dependency)
|
|
|
|
| 359 |
):
|
|
|
|
| 360 |
session_state = app.state.get_session_state(session_id)
|
|
|
|
|
|
|
|
|
|
| 361 |
|
|
|
|
| 362 |
try:
|
|
|
|
| 363 |
# Extract and validate query parameters
|
|
|
|
|
|
|
|
|
|
| 364 |
_update_session_from_query_params(request_obj, session_state)
|
|
|
|
|
|
|
|
|
|
| 365 |
|
|
|
|
| 366 |
# Validate dataset and agent name
|
| 367 |
+
if session_state["current_df"] is None:
|
|
|
|
|
|
|
|
|
|
| 368 |
raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
|
| 369 |
|
| 370 |
+
_validate_agent_name(agent_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
|
|
|
| 372 |
# Record start time for timing
|
|
|
|
| 373 |
start_time = time.time()
|
|
|
|
| 374 |
|
|
|
|
| 375 |
# Get chat context and prepare query
|
|
|
|
|
|
|
|
|
|
| 376 |
enhanced_query = _prepare_query_with_context(request.query, session_state)
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
+
# Initialize agent
|
|
|
|
|
|
|
| 379 |
if "," in agent_name:
|
| 380 |
+
agent_list = [AVAILABLE_AGENTS[agent.strip()] for agent in agent_name.split(",")]
|
| 381 |
+
agent = auto_analyst_ind(agents=agent_list, retrievers=session_state["retrievers"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
else:
|
| 383 |
+
agent = auto_analyst_ind(agents=[AVAILABLE_AGENTS[agent_name]], retrievers=session_state["retrievers"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
|
| 385 |
+
# Execute agent with timeout
|
| 386 |
+
try:
|
| 387 |
+
# Get session-specific model
|
| 388 |
+
session_lm = get_session_lm(session_state)
|
| 389 |
+
|
| 390 |
+
# Use session-specific model for this request
|
| 391 |
+
with dspy.context(lm=session_lm):
|
| 392 |
+
response = await asyncio.wait_for(
|
| 393 |
+
asyncio.to_thread(agent, enhanced_query, agent_name),
|
| 394 |
+
timeout=REQUEST_TIMEOUT_SECONDS
|
| 395 |
+
)
|
| 396 |
+
except asyncio.TimeoutError:
|
| 397 |
+
logger.log_message(f"Agent execution timed out for {agent_name}", level=logging.WARNING)
|
| 398 |
+
raise HTTPException(status_code=504, detail="Request timed out. Please try a simpler query.")
|
| 399 |
+
except Exception as agent_error:
|
| 400 |
+
logger.log_message(f"Agent execution failed: {str(agent_error)}", level=logging.ERROR)
|
| 401 |
+
raise HTTPException(status_code=500, detail="Failed to process query. Please try again.")
|
| 402 |
+
|
| 403 |
+
formatted_response = format_response_to_markdown(response, agent_name, session_state["current_df"])
|
| 404 |
|
|
|
|
| 405 |
if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
|
|
|
|
|
|
|
|
|
|
| 406 |
return {
|
|
|
|
| 407 |
"agent_name": agent_name,
|
|
|
|
| 408 |
"query": request.query,
|
|
|
|
| 409 |
"response": formatted_response,
|
|
|
|
| 410 |
"session_id": session_id
|
|
|
|
| 411 |
}
|
|
|
|
| 412 |
|
|
|
|
| 413 |
# Track usage statistics
|
|
|
|
| 414 |
if session_state.get("user_id"):
|
|
|
|
|
|
|
|
|
|
| 415 |
_track_model_usage(
|
|
|
|
| 416 |
session_state=session_state,
|
|
|
|
| 417 |
enhanced_query=enhanced_query,
|
|
|
|
| 418 |
response=response,
|
|
|
|
| 419 |
processing_time_ms=int((time.time() - start_time) * 1000)
|
|
|
|
| 420 |
)
|
|
|
|
| 421 |
|
|
|
|
|
|
|
|
|
|
| 422 |
return {
|
|
|
|
| 423 |
"agent_name": agent_name,
|
|
|
|
| 424 |
"query": request.query, # Return original query without context
|
|
|
|
| 425 |
"response": formatted_response,
|
|
|
|
| 426 |
"session_id": session_id
|
|
|
|
| 427 |
}
|
|
|
|
| 428 |
except HTTPException:
|
|
|
|
| 429 |
# Re-raise HTTP exceptions to preserve status codes
|
|
|
|
|
|
|
|
|
|
| 430 |
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
except Exception as e:
|
| 432 |
+
logger.log_message(f"Unexpected error in chat_with_agent: {str(e)}", level=logging.ERROR)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
|
| 434 |
+
|
| 435 |
+
|
|
|
|
|
|
|
|
|
|
| 436 |
@app.post("/chat", response_model=dict)
|
|
|
|
| 437 |
async def chat_with_all(
|
|
|
|
| 438 |
request: QueryRequest,
|
|
|
|
| 439 |
request_obj: Request,
|
|
|
|
| 440 |
session_id: str = Depends(get_session_id_dependency)
|
|
|
|
| 441 |
):
|
|
|
|
| 442 |
session_state = app.state.get_session_state(session_id)
|
| 443 |
|
|
|
|
|
|
|
| 444 |
try:
|
|
|
|
| 445 |
# Extract and validate query parameters
|
|
|
|
| 446 |
_update_session_from_query_params(request_obj, session_state)
|
|
|
|
| 447 |
|
|
|
|
| 448 |
# Validate dataset
|
| 449 |
+
if session_state["current_df"] is None:
|
|
|
|
| 450 |
raise HTTPException(status_code=400, detail=RESPONSE_ERROR_NO_DATASET)
|
|
|
|
| 451 |
|
|
|
|
| 452 |
if session_state["ai_system"] is None:
|
|
|
|
| 453 |
raise HTTPException(status_code=500, detail="AI system not properly initialized.")
|
| 454 |
|
|
|
|
|
|
|
| 455 |
# Get session-specific model
|
|
|
|
| 456 |
session_lm = get_session_lm(session_state)
|
| 457 |
|
|
|
|
|
|
|
| 458 |
# Create streaming response
|
|
|
|
| 459 |
return StreamingResponse(
|
|
|
|
| 460 |
_generate_streaming_responses(session_state, request.query, session_lm),
|
|
|
|
| 461 |
media_type='text/event-stream',
|
|
|
|
| 462 |
headers={
|
|
|
|
| 463 |
'Cache-Control': 'no-cache',
|
|
|
|
| 464 |
'Connection': 'keep-alive',
|
|
|
|
| 465 |
'Content-Type': 'text/event-stream',
|
|
|
|
| 466 |
'Access-Control-Allow-Origin': '*',
|
|
|
|
| 467 |
'X-Accel-Buffering': 'no'
|
|
|
|
| 468 |
}
|
|
|
|
| 469 |
)
|
|
|
|
| 470 |
except HTTPException:
|
|
|
|
| 471 |
# Re-raise HTTP exceptions to preserve status codes
|
|
|
|
| 472 |
raise
|
|
|
|
| 473 |
except Exception as e:
|
| 474 |
+
logger.log_message(f"Unexpected error in chat_with_all: {str(e)}", level=logging.ERROR)
|
| 475 |
raise HTTPException(status_code=500, detail="An unexpected error occurred. Please try again later.")
|
| 476 |
|
| 477 |
|
|
|
|
|
|
|
|
|
|
| 478 |
# Helper functions to reduce duplication and improve modularity
|
|
|
|
| 479 |
def _update_session_from_query_params(request_obj: Request, session_state: dict):
|
|
|
|
| 480 |
"""Extract and validate chat_id and user_id from query parameters"""
|
|
|
|
| 481 |
# Check for chat_id in query parameters
|
|
|
|
| 482 |
if "chat_id" in request_obj.query_params:
|
|
|
|
| 483 |
try:
|
|
|
|
| 484 |
chat_id_param = int(request_obj.query_params.get("chat_id"))
|
|
|
|
| 485 |
# Update session state with this chat ID
|
|
|
|
| 486 |
session_state["chat_id"] = chat_id_param
|
|
|
|
| 487 |
except (ValueError, TypeError):
|
|
|
|
| 488 |
logger.log_message("Invalid chat_id parameter", level=logging.WARNING)
|
|
|
|
| 489 |
# Continue without updating chat_id
|
| 490 |
|
|
|
|
|
|
|
| 491 |
# Check for user_id in query parameters
|
|
|
|
| 492 |
if "user_id" in request_obj.query_params:
|
|
|
|
| 493 |
try:
|
|
|
|
| 494 |
user_id = int(request_obj.query_params["user_id"])
|
|
|
|
| 495 |
session_state["user_id"] = user_id
|
|
|
|
| 496 |
except (ValueError, TypeError):
|
|
|
|
| 497 |
raise HTTPException(
|
|
|
|
| 498 |
status_code=400,
|
|
|
|
| 499 |
detail="Invalid user_id in query params. Please provide a valid integer."
|
|
|
|
| 500 |
)
|
| 501 |
|
| 502 |
|
| 503 |
+
def _validate_agent_name(agent_name: str):
|
| 504 |
+
"""Validate that the requested agent(s) exist"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
if "," in agent_name:
|
|
|
|
|
|
|
|
|
|
| 506 |
agent_list = [agent.strip() for agent in agent_name.split(",")]
|
|
|
|
|
|
|
|
|
|
| 507 |
for agent in agent_list:
|
| 508 |
+
if agent not in AVAILABLE_AGENTS:
|
| 509 |
+
available = list(AVAILABLE_AGENTS.keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
raise HTTPException(
|
| 511 |
+
status_code=404,
|
| 512 |
+
detail=f"Agent '{agent}' not found. Available agents: {available}"
|
| 513 |
+
)
|
| 514 |
+
elif agent_name not in AVAILABLE_AGENTS:
|
| 515 |
+
available = list(AVAILABLE_AGENTS.keys())
|
| 516 |
+
raise HTTPException(
|
| 517 |
+
status_code=404,
|
| 518 |
+
detail=f"Agent '{agent_name}' not found. Available agents: {available}"
|
| 519 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 520 |
|
| 521 |
|
| 522 |
+
def _prepare_query_with_context(query: str, session_state: dict) -> str:
|
| 523 |
+
"""Prepare the query with chat context from previous messages"""
|
| 524 |
+
chat_id = session_state.get("chat_id")
|
| 525 |
+
if not chat_id:
|
| 526 |
+
return query
|
| 527 |
+
|
| 528 |
+
# Get chat manager from app state
|
| 529 |
+
chat_manager = app.state._session_manager.chat_manager
|
| 530 |
+
# Get recent messages
|
| 531 |
+
recent_messages = chat_manager.get_recent_chat_history(chat_id, limit=MAX_RECENT_MESSAGES)
|
| 532 |
+
# Extract response history
|
| 533 |
+
chat_context = chat_manager.extract_response_history(recent_messages)
|
| 534 |
+
|
| 535 |
+
# Append context to the query if available
|
| 536 |
+
if chat_context:
|
| 537 |
+
return f"### Current Query:\n{query}\n\n{chat_context}"
|
| 538 |
+
return query
|
| 539 |
|
| 540 |
|
| 541 |
def _track_model_usage(session_state: dict, enhanced_query: str, response, processing_time_ms: int):
|
|
|
|
| 542 |
"""Track model usage statistics in the database"""
|
|
|
|
| 543 |
try:
|
|
|
|
| 544 |
ai_manager = app.state.get_ai_manager()
|
|
|
|
| 545 |
|
|
|
|
| 546 |
# Get model configuration
|
|
|
|
| 547 |
model_config = session_state.get("model_config", DEFAULT_MODEL_CONFIG)
|
|
|
|
| 548 |
model_name = model_config.get("model", DEFAULT_MODEL_CONFIG["model"])
|
|
|
|
| 549 |
provider = ai_manager.get_provider_for_model(model_name)
|
|
|
|
| 550 |
|
|
|
|
| 551 |
# Calculate token usage
|
|
|
|
| 552 |
try:
|
|
|
|
| 553 |
# Try exact tokenization
|
|
|
|
| 554 |
prompt_tokens = len(ai_manager.tokenizer.encode(enhanced_query))
|
|
|
|
| 555 |
completion_tokens = len(ai_manager.tokenizer.encode(str(response)))
|
|
|
|
| 556 |
total_tokens = prompt_tokens + completion_tokens
|
|
|
|
| 557 |
except Exception as token_error:
|
|
|
|
| 558 |
# Fall back to estimation
|
|
|
|
| 559 |
logger.log_message(f"Tokenization error: {str(token_error)}", level=logging.WARNING)
|
|
|
|
| 560 |
prompt_words = len(enhanced_query.split())
|
|
|
|
| 561 |
completion_words = len(str(response).split())
|
|
|
|
| 562 |
prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
|
|
|
|
| 563 |
completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
|
|
|
|
| 564 |
total_tokens = prompt_tokens + completion_tokens
|
|
|
|
| 565 |
|
|
|
|
| 566 |
# Calculate cost
|
|
|
|
| 567 |
cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
|
|
|
|
| 568 |
|
|
|
|
| 569 |
# Save usage to database
|
|
|
|
| 570 |
ai_manager.save_usage_to_db(
|
|
|
|
| 571 |
user_id=session_state.get("user_id"),
|
|
|
|
| 572 |
chat_id=session_state.get("chat_id"),
|
|
|
|
| 573 |
model_name=model_name,
|
|
|
|
| 574 |
provider=provider,
|
|
|
|
| 575 |
prompt_tokens=int(prompt_tokens),
|
|
|
|
| 576 |
completion_tokens=int(completion_tokens),
|
|
|
|
| 577 |
total_tokens=int(total_tokens),
|
|
|
|
| 578 |
query_size=len(enhanced_query),
|
|
|
|
| 579 |
response_size=len(str(response)),
|
|
|
|
| 580 |
cost=round(cost, 7),
|
|
|
|
| 581 |
request_time_ms=processing_time_ms,
|
|
|
|
| 582 |
is_streaming=False
|
|
|
|
| 583 |
)
|
|
|
|
| 584 |
except Exception as e:
|
|
|
|
| 585 |
# Log but don't fail the request if usage tracking fails
|
|
|
|
| 586 |
logger.log_message(f"Failed to track model usage: {str(e)}", level=logging.ERROR)
|
| 587 |
|
| 588 |
|
|
|
|
|
|
|
|
|
|
| 589 |
async def _generate_streaming_responses(session_state: dict, query: str, session_lm):
|
|
|
|
| 590 |
"""Generate streaming responses for chat_with_all endpoint"""
|
|
|
|
| 591 |
overall_start_time = time.time()
|
|
|
|
| 592 |
total_response = ""
|
|
|
|
| 593 |
total_inputs = ""
|
|
|
|
| 594 |
usage_records = []
|
| 595 |
|
| 596 |
+
try:
|
| 597 |
+
# Add chat context from previous messages
|
| 598 |
+
enhanced_query = _prepare_query_with_context(query, session_state)
|
| 599 |
+
|
| 600 |
+
# Use the session model for this specific request
|
| 601 |
+
with dspy.context(lm=session_lm):
|
| 602 |
+
try:
|
| 603 |
+
# Get the plan
|
| 604 |
+
plan_response = await asyncio.wait_for(
|
| 605 |
+
asyncio.to_thread(session_state["ai_system"].get_plan, enhanced_query),
|
| 606 |
+
timeout=REQUEST_TIMEOUT_SECONDS
|
| 607 |
+
)
|
| 608 |
+
|
| 609 |
+
plan_description = format_response_to_markdown(
|
| 610 |
+
{"analytical_planner": plan_response},
|
| 611 |
+
dataframe=session_state["current_df"]
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
# Check if plan is valid
|
| 615 |
+
if plan_description == RESPONSE_ERROR_INVALID_QUERY:
|
| 616 |
+
yield json.dumps({
|
| 617 |
+
"agent": "Analytical Planner",
|
| 618 |
+
"content": plan_description,
|
| 619 |
+
"status": "error"
|
| 620 |
+
}) + "\n"
|
| 621 |
+
return
|
| 622 |
+
|
| 623 |
+
yield json.dumps({
|
| 624 |
+
"agent": "Analytical Planner",
|
| 625 |
+
"content": plan_description,
|
| 626 |
+
"status": "success" if plan_description else "error"
|
| 627 |
+
}) + "\n"
|
| 628 |
+
|
| 629 |
+
# Track planner usage
|
| 630 |
+
if session_state.get("user_id"):
|
| 631 |
+
planner_tokens = _estimate_tokens(ai_manager=app.state.ai_manager,
|
| 632 |
+
input_text=enhanced_query,
|
| 633 |
+
output_text=plan_description)
|
| 634 |
+
|
| 635 |
+
usage_records.append(_create_usage_record(
|
| 636 |
+
session_state=session_state,
|
| 637 |
+
model_name=session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"],
|
| 638 |
+
prompt_tokens=planner_tokens["prompt"],
|
| 639 |
+
completion_tokens=planner_tokens["completion"],
|
| 640 |
+
query_size=len(enhanced_query),
|
| 641 |
+
response_size=len(plan_description),
|
| 642 |
+
processing_time_ms=int((time.time() - overall_start_time) * 1000),
|
| 643 |
+
is_streaming=False
|
| 644 |
+
))
|
| 645 |
+
|
| 646 |
+
# Execute the plan with well-managed concurrency
|
| 647 |
+
async for agent_name, inputs, response in _execute_plan_with_timeout(
|
| 648 |
+
session_state["ai_system"], enhanced_query, plan_response):
|
| 649 |
+
|
| 650 |
+
if agent_name == "plan_not_found":
|
| 651 |
+
yield json.dumps({
|
| 652 |
+
"agent": "Analytical Planner",
|
| 653 |
+
"content": "**No plan found**\n\nPlease try again with a different query or try using a different model.",
|
| 654 |
+
"status": "error"
|
| 655 |
+
}) + "\n"
|
| 656 |
+
return
|
| 657 |
+
|
| 658 |
+
formatted_response = format_response_to_markdown(
|
| 659 |
+
{agent_name: response},
|
| 660 |
+
dataframe=session_state["current_df"]
|
| 661 |
+
) or "No response generated"
|
| 662 |
|
| 663 |
+
if formatted_response == RESPONSE_ERROR_INVALID_QUERY:
|
| 664 |
+
yield json.dumps({
|
| 665 |
+
"agent": agent_name,
|
| 666 |
+
"content": formatted_response,
|
| 667 |
+
"status": "error"
|
| 668 |
+
}) + "\n"
|
| 669 |
+
return
|
| 670 |
+
|
| 671 |
+
# if "code_combiner_agent" in agent_name:
|
| 672 |
+
# # logger.log_message(f"[>] Code combiner response: {response}", level=logging.INFO)
|
| 673 |
+
# total_response += str(response) if response else ""
|
| 674 |
+
# total_inputs += str(inputs) if inputs else ""
|
| 675 |
+
|
| 676 |
+
# Send response chunk
|
| 677 |
+
yield json.dumps({
|
| 678 |
+
"agent": agent_name.split("__")[0] if "__" in agent_name else agent_name,
|
| 679 |
+
"content": formatted_response,
|
| 680 |
+
"status": "success" if response else "error"
|
| 681 |
+
}) + "\n"
|
| 682 |
+
|
| 683 |
+
# Track agent usage for future batch DB write
|
| 684 |
+
if session_state.get("user_id"):
|
| 685 |
+
agent_tokens = _estimate_tokens(
|
| 686 |
+
ai_manager=app.state.ai_manager,
|
| 687 |
+
input_text=str(inputs),
|
| 688 |
+
output_text=str(response)
|
| 689 |
+
)
|
| 690 |
+
|
| 691 |
+
# Get appropriate model name for code combiner
|
| 692 |
+
if "code_combiner_agent" in agent_name and "__" in agent_name:
|
| 693 |
+
provider = agent_name.split("__")[1]
|
| 694 |
+
model_name = _get_model_name_for_provider(provider)
|
| 695 |
+
else:
|
| 696 |
+
model_name = session_state.get("model_config", DEFAULT_MODEL_CONFIG)["model"]
|
| 697 |
+
|
| 698 |
+
usage_records.append(_create_usage_record(
|
| 699 |
+
session_state=session_state,
|
| 700 |
+
model_name=model_name,
|
| 701 |
+
prompt_tokens=agent_tokens["prompt"],
|
| 702 |
+
completion_tokens=agent_tokens["completion"],
|
| 703 |
+
query_size=len(str(inputs)),
|
| 704 |
+
response_size=len(str(response)),
|
| 705 |
+
processing_time_ms=int((time.time() - overall_start_time) * 1000),
|
| 706 |
+
is_streaming=True
|
| 707 |
+
))
|
| 708 |
+
|
| 709 |
+
except asyncio.TimeoutError:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 710 |
yield json.dumps({
|
| 711 |
+
"agent": "planner",
|
| 712 |
+
"content": "The request timed out. Please try a simpler query.",
|
|
|
|
|
|
|
|
|
|
| 713 |
"status": "error"
|
|
|
|
| 714 |
}) + "\n"
|
|
|
|
| 715 |
return
|
| 716 |
+
except Exception as e:
|
| 717 |
+
logger.log_message(f"Error in streaming response: {str(e)}", level=logging.ERROR)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
yield json.dumps({
|
| 719 |
+
"agent": "planner",
|
| 720 |
+
"content": "An error occurred while generating responses. Please try again!",
|
|
|
|
|
|
|
|
|
|
| 721 |
"status": "error"
|
|
|
|
| 722 |
}) + "\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
+
# Batch write usage records to DB
|
| 725 |
+
if usage_records and session_state.get("user_id"):
|
| 726 |
+
try:
|
| 727 |
+
# In a real implementation, you would batch these writes
|
| 728 |
+
# For now, we're writing them one by one but could be optimized
|
| 729 |
+
ai_manager = app.state.get_ai_manager()
|
| 730 |
+
for record in usage_records:
|
| 731 |
+
ai_manager.save_usage_to_db(**record)
|
| 732 |
+
except Exception as db_error:
|
| 733 |
+
logger.log_message(f"Failed to save usage records: {str(db_error)}", level=logging.ERROR)
|
| 734 |
+
|
| 735 |
+
except Exception as e:
|
| 736 |
+
logger.log_message(f"Streaming response generation failed: {str(e)}", level=logging.ERROR)
|
| 737 |
+
yield json.dumps({
|
| 738 |
+
"agent": "planner",
|
| 739 |
+
"content": "An error occurred while generating responses. Please try again!",
|
| 740 |
+
"status": "error"
|
| 741 |
+
}) + "\n"
|
| 742 |
|
| 743 |
|
| 744 |
def _estimate_tokens(ai_manager, input_text: str, output_text: str) -> dict:
|
|
|
|
| 745 |
"""Estimate token counts, with fallback for tokenization errors"""
|
|
|
|
| 746 |
try:
|
|
|
|
| 747 |
# Try exact tokenization
|
|
|
|
| 748 |
prompt_tokens = len(ai_manager.tokenizer.encode(input_text))
|
|
|
|
| 749 |
completion_tokens = len(ai_manager.tokenizer.encode(output_text))
|
|
|
|
| 750 |
except Exception:
|
|
|
|
| 751 |
# Fall back to estimation
|
|
|
|
| 752 |
prompt_words = len(input_text.split())
|
|
|
|
| 753 |
completion_words = len(output_text.split())
|
|
|
|
| 754 |
prompt_tokens = int(prompt_words * DEFAULT_TOKEN_RATIO)
|
|
|
|
| 755 |
completion_tokens = int(completion_words * DEFAULT_TOKEN_RATIO)
|
|
|
|
| 756 |
|
|
|
|
| 757 |
return {
|
|
|
|
| 758 |
"prompt": prompt_tokens,
|
|
|
|
| 759 |
"completion": completion_tokens,
|
|
|
|
| 760 |
"total": prompt_tokens + completion_tokens
|
|
|
|
| 761 |
}
|
| 762 |
|
| 763 |
|
|
|
|
|
|
|
|
|
|
| 764 |
def _create_usage_record(session_state: dict, model_name: str, prompt_tokens: int,
|
|
|
|
| 765 |
completion_tokens: int, query_size: int, response_size: int,
|
|
|
|
| 766 |
processing_time_ms: int, is_streaming: bool) -> dict:
|
|
|
|
| 767 |
"""Create a usage record for the database"""
|
|
|
|
| 768 |
ai_manager = app.state.get_ai_manager()
|
|
|
|
| 769 |
provider = ai_manager.get_provider_for_model(model_name)
|
|
|
|
| 770 |
cost = ai_manager.calculate_cost(model_name, prompt_tokens, completion_tokens)
|
|
|
|
| 771 |
|
|
|
|
| 772 |
return {
|
|
|
|
| 773 |
"user_id": session_state.get("user_id"),
|
|
|
|
| 774 |
"chat_id": session_state.get("chat_id"),
|
|
|
|
| 775 |
"model_name": model_name,
|
|
|
|
| 776 |
"provider": provider,
|
|
|
|
| 777 |
"prompt_tokens": int(prompt_tokens),
|
|
|
|
| 778 |
"completion_tokens": int(completion_tokens),
|
|
|
|
| 779 |
"total_tokens": int(prompt_tokens + completion_tokens),
|
|
|
|
| 780 |
"query_size": query_size,
|
|
|
|
| 781 |
"response_size": response_size,
|
|
|
|
| 782 |
"cost": round(cost, 7),
|
|
|
|
| 783 |
"request_time_ms": processing_time_ms,
|
|
|
|
| 784 |
"is_streaming": is_streaming
|
|
|
|
| 785 |
}
|
| 786 |
|
| 787 |
|
|
|
|
|
|
|
|
|
|
| 788 |
def _get_model_name_for_provider(provider: str) -> str:
|
|
|
|
| 789 |
"""Get the model name for a provider"""
|
|
|
|
| 790 |
provider_model_map = {
|
| 791 |
+
"openai": "o3-mini",
|
| 792 |
+
"anthropic": "claude-3-7-sonnet-latest",
|
|
|
|
| 793 |
"gemini": "gemini-2.5-pro-preview-03-25"
|
| 794 |
}
|
| 795 |
+
return provider_model_map.get(provider, "o3-mini")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 796 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 797 |
|
| 798 |
+
async def _execute_plan_with_timeout(ai_system, enhanced_query, plan_response):
|
| 799 |
+
"""Execute the plan with timeout handling for each step"""
|
| 800 |
try:
|
| 801 |
+
# Use asyncio.create_task to run the execute_plan coroutine
|
| 802 |
+
async for agent_name, inputs, response in ai_system.execute_plan(enhanced_query, plan_response):
|
| 803 |
+
# Yield results as they come
|
| 804 |
+
yield agent_name, inputs, response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
except Exception as e:
|
| 806 |
+
logger.log_message(f"Error executing plan: {str(e)}", level=logging.ERROR)
|
| 807 |
+
yield "error", None, {"error": "An error occurred during plan execution"}
|
|
|
|
|
|
|
| 808 |
|
| 809 |
|
| 810 |
+
# Add an endpoint to list available agents
|
| 811 |
+
@app.get("/agents", response_model=dict)
|
| 812 |
+
async def list_agents():
|
| 813 |
+
return {
|
| 814 |
+
"available_agents": list(AVAILABLE_AGENTS.keys()),
|
| 815 |
+
"description": "List of available specialized agents that can be called using @agent_name"
|
| 816 |
+
}
|
| 817 |
|
| 818 |
@app.get("/health", response_model=dict)
|
|
|
|
| 819 |
async def health():
|
|
|
|
| 820 |
return {"message": "API is healthy and running"}
|
| 821 |
|
|
|
|
|
|
|
| 822 |
@app.get("/")
|
|
|
|
| 823 |
async def index():
|
|
|
|
| 824 |
return {
|
|
|
|
| 825 |
"title": "Welcome to the AI Analytics API",
|
|
|
|
| 826 |
"message": "Explore our API for advanced analytics and visualization tools designed to empower your data-driven decisions.",
|
|
|
|
| 827 |
"description": "Utilize our powerful agents and models to gain insights from your data effortlessly.",
|
|
|
|
| 828 |
"colors": {
|
|
|
|
| 829 |
"primary": "#007bff",
|
|
|
|
| 830 |
"secondary": "#6c757d",
|
|
|
|
| 831 |
"success": "#28a745",
|
|
|
|
| 832 |
"danger": "#dc3545",
|
|
|
|
| 833 |
},
|
|
|
|
| 834 |
"features": [
|
|
|
|
| 835 |
"Real-time data processing",
|
|
|
|
| 836 |
"Customizable visualizations",
|
|
|
|
| 837 |
"Seamless integration with various data sources",
|
|
|
|
| 838 |
"User-friendly interface for easy navigation",
|
|
|
|
| 839 |
"Custom Analytics",
|
|
|
|
| 840 |
],
|
|
|
|
| 841 |
}
|
| 842 |
|
|
|
|
|
|
|
| 843 |
@app.post("/chat_history_name")
|
|
|
|
| 844 |
async def chat_history_name(request: dict, session_id: str = Depends(get_session_id_dependency)):
|
|
|
|
| 845 |
query = request.get("query")
|
|
|
|
| 846 |
name = None
|
|
|
|
| 847 |
|
| 848 |
+
lm = dspy.LM(model="gpt-4o-mini", max_tokens=300, temperature=0.5)
|
|
|
|
|
|
|
| 849 |
|
|
|
|
| 850 |
with dspy.context(lm=lm):
|
|
|
|
| 851 |
name = app.state.get_chat_history_name_agent()(query=str(query))
|
|
|
|
| 852 |
|
|
|
|
| 853 |
return {"name": name.name if name else "New Chat"}
|
| 854 |
|
| 855 |
+
# In the section where routers are included, add the session_router
|
| 856 |
+
app.include_router(chat_router)
|
| 857 |
+
app.include_router(analytics_router)
|
| 858 |
+
app.include_router(code_router)
|
| 859 |
+
app.include_router(session_router)
|
| 860 |
+
app.include_router(feedback_router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
|
| 862 |
if __name__ == "__main__":
|
| 863 |
+
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:e6403c5e1c61ed80d24f1b33d27a5a734121e1bad55e311bab41cf7349e7af23
|
| 3 |
+
size 36864
|
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/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/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,7 @@ 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 |
-
|
| 10 |
-
|
| 11 |
-
-
|
| 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.
|
| 11 |
+
- [Code Endpoints](./code.md):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
| 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
|
| 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
|
| 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 |
-
#### **
|
| 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 |
-
|
| 137 |
-
|
| 138 |
-
**
|
|
|
|
|
|
|
| 139 |
**Path Parameter:** `chat_id` (ID of the chat)
|
| 140 |
-
**Query Parameter:** `user_id` (Optional for access control)
|
| 141 |
**Request Body:**
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 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 chat’s 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/{api/routes → routes}/code.md
RENAMED
|
@@ -16,17 +16,15 @@ Executes Python code against the current session's dataframe.
|
|
| 16 |
**Request Body:**
|
| 17 |
```json
|
| 18 |
{
|
| 19 |
-
"code": "string"
|
| 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",
|
| 29 |
-
"plotly_outputs": [
|
| 30 |
"string"
|
| 31 |
]
|
| 32 |
}
|
|
@@ -44,15 +42,15 @@ Uses AI to edit code based on user instructions.
|
|
| 44 |
**Request Body:**
|
| 45 |
```json
|
| 46 |
{
|
| 47 |
-
"original_code": "string",
|
| 48 |
-
"user_prompt": "string"
|
| 49 |
}
|
| 50 |
```
|
| 51 |
|
| 52 |
**Response:**
|
| 53 |
```json
|
| 54 |
{
|
| 55 |
-
"edited_code": "string"
|
| 56 |
}
|
| 57 |
```
|
| 58 |
|
|
@@ -61,22 +59,22 @@ Uses AI to edit code based on user 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
|
| 65 |
|
| 66 |
**Endpoint:** `POST /code/fix`
|
| 67 |
|
| 68 |
**Request Body:**
|
| 69 |
```json
|
| 70 |
{
|
| 71 |
-
"code": "string",
|
| 72 |
-
"error": "string"
|
| 73 |
}
|
| 74 |
```
|
| 75 |
|
| 76 |
**Response:**
|
| 77 |
```json
|
| 78 |
{
|
| 79 |
-
"fixed_code": "string"
|
| 80 |
}
|
| 81 |
```
|
| 82 |
|
|
@@ -92,14 +90,14 @@ Cleans and formats code by organizing imports and ensuring proper code block for
|
|
| 92 |
**Request Body:**
|
| 93 |
```json
|
| 94 |
{
|
| 95 |
-
"code": "string"
|
| 96 |
}
|
| 97 |
```
|
| 98 |
|
| 99 |
**Response:**
|
| 100 |
```json
|
| 101 |
{
|
| 102 |
-
"cleaned_code": "string"
|
| 103 |
}
|
| 104 |
```
|
| 105 |
|
|
@@ -107,30 +105,6 @@ Cleans and formats code by organizing imports and ensuring proper code block for
|
|
| 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
|
|
@@ -144,12 +118,10 @@ 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
|
| 148 |
-
When fixing code, the system
|
| 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 |
|
|
@@ -160,23 +132,14 @@ When editing or fixing code, the system provides context about the current datas
|
|
| 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
|
|
|
|
| 16 |
**Request Body:**
|
| 17 |
```json
|
| 18 |
{
|
| 19 |
+
"code": "string" // Python code to execute
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
```
|
| 22 |
|
| 23 |
**Response:**
|
| 24 |
```json
|
| 25 |
{
|
| 26 |
+
"output": "string", // Execution output
|
| 27 |
+
"plotly_outputs": [ // Optional array of plotly outputs
|
| 28 |
"string"
|
| 29 |
]
|
| 30 |
}
|
|
|
|
| 42 |
**Request Body:**
|
| 43 |
```json
|
| 44 |
{
|
| 45 |
+
"original_code": "string", // Code to be edited
|
| 46 |
+
"user_prompt": "string" // Instructions for editing
|
| 47 |
}
|
| 48 |
```
|
| 49 |
|
| 50 |
**Response:**
|
| 51 |
```json
|
| 52 |
{
|
| 53 |
+
"edited_code": "string" // The edited code
|
| 54 |
}
|
| 55 |
```
|
| 56 |
|
|
|
|
| 59 |
- `500 Internal Server Error`: Editing error
|
| 60 |
|
| 61 |
### Fix Code
|
| 62 |
+
Uses AI to fix code with errors, employing a block-by-block approach.
|
| 63 |
|
| 64 |
**Endpoint:** `POST /code/fix`
|
| 65 |
|
| 66 |
**Request Body:**
|
| 67 |
```json
|
| 68 |
{
|
| 69 |
+
"code": "string", // Code containing errors
|
| 70 |
+
"error": "string" // Error message to fix
|
| 71 |
}
|
| 72 |
```
|
| 73 |
|
| 74 |
**Response:**
|
| 75 |
```json
|
| 76 |
{
|
| 77 |
+
"fixed_code": "string" // The fixed code
|
| 78 |
}
|
| 79 |
```
|
| 80 |
|
|
|
|
| 90 |
**Request Body:**
|
| 91 |
```json
|
| 92 |
{
|
| 93 |
+
"code": "string" // Code to clean
|
| 94 |
}
|
| 95 |
```
|
| 96 |
|
| 97 |
**Response:**
|
| 98 |
```json
|
| 99 |
{
|
| 100 |
+
"cleaned_code": "string" // The cleaned code
|
| 101 |
}
|
| 102 |
```
|
| 103 |
|
|
|
|
| 105 |
- `400 Bad Request`: No code provided
|
| 106 |
- `500 Internal Server Error`: Cleaning error
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
## Code Processing Features
|
| 109 |
|
| 110 |
### Import Organization
|
|
|
|
| 118 |
- Start marker: `# agent_name code start`
|
| 119 |
- End marker: `# agent_name code end`
|
| 120 |
|
| 121 |
+
### Error Handling
|
| 122 |
+
When fixing code, the system:
|
| 123 |
- Identifies specific code blocks with errors
|
| 124 |
- Processes error messages to extract relevant information
|
|
|
|
|
|
|
| 125 |
- Fixes each block individually while maintaining the overall structure
|
| 126 |
- Preserves code block markers and relationships
|
| 127 |
|
|
|
|
| 132 |
- Null value counts
|
| 133 |
- Sample values for each column
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
## Session Management
|
| 136 |
All endpoints require a valid session ID, which is used to:
|
| 137 |
- Access the current dataset
|
| 138 |
- Maintain state between requests
|
| 139 |
- Track code execution history
|
|
|
|
| 140 |
|
| 141 |
## Error Handling
|
| 142 |
The system provides detailed error messages while maintaining security by:
|
| 143 |
- Logging errors for debugging
|
| 144 |
- Returning user-friendly error messages
|
| 145 |
- Preserving original code in case of processing failures
|
|
|
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/{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/AI snapshot-chat.png
ADDED
|
Git LFS Details
|
images/Auto-Analyst Banner.png
ADDED
|
Git LFS Details
|
images/Auto-analyst-poster.png
ADDED
|
Git LFS Details
|
images/Auto-analysts icon small.png
ADDED
|
|
Git LFS Details
|
images/auto-analyst logo.png
ADDED
|
Git LFS Details
|
requirements.txt
CHANGED
|
@@ -1,31 +1,32 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
dspy==3.2.1
|
| 5 |
-
litellm==1.82.3
|
| 6 |
email_validator==2.2.0
|
| 7 |
-
fastapi==0.
|
| 8 |
-
fastapi-cli==0.0.7
|
| 9 |
FastAPI-SQLAlchemy==0.2.1
|
| 10 |
-
|
| 11 |
-
groq==0.18.0
|
| 12 |
-
gunicorn==23.0.0
|
| 13 |
-
huggingface-hub==0.30.2
|
| 14 |
joblib==1.4.2
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
matplotlib==3.10.0
|
| 17 |
-
|
| 18 |
numpy==2.2.2
|
| 19 |
openpyxl==3.1.2
|
| 20 |
xlrd==2.0.1
|
| 21 |
-
openai==
|
| 22 |
pandas==2.2.3
|
| 23 |
-
polars==1.31.0
|
| 24 |
pillow==11.1.0
|
| 25 |
plotly==5.24.1
|
| 26 |
-
|
| 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 +38,10 @@ tabulate==0.9.0
|
|
| 37 |
threadpoolctl==3.5.0
|
| 38 |
tiktoken==0.8.0
|
| 39 |
tokenizers==0.21.0
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
websockets>=13.1.0
|
| 44 |
wheel==0.45.1
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 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 |
openpyxl==3.1.2
|
| 22 |
xlrd==2.0.1
|
| 23 |
+
openai==1.60.1
|
| 24 |
pandas==2.2.3
|
|
|
|
| 25 |
pillow==11.1.0
|
| 26 |
plotly==5.24.1
|
| 27 |
+
pypdf==5.2.0
|
|
|
|
| 28 |
python-dotenv==1.0.1
|
| 29 |
+
regex==2024.11.6
|
| 30 |
requests==2.32.3
|
| 31 |
scikit-learn==1.6.1
|
| 32 |
scipy==1.15.1
|
|
|
|
| 38 |
threadpoolctl==3.5.0
|
| 39 |
tiktoken==0.8.0
|
| 40 |
tokenizers==0.21.0
|
| 41 |
+
uvicorn==0.22.0
|
| 42 |
+
websockets==14.2
|
| 43 |
+
Werkzeug==3.1.3
|
|
|
|
| 44 |
wheel==0.45.1
|
| 45 |
+
gunicorn
|
| 46 |
+
psycopg2
|
| 47 |
+
chardet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=
|
| 12 |
|
| 13 |
@contextlib.contextmanager
|
| 14 |
def stdoutIO(stdout=None):
|
|
@@ -44,33 +44,20 @@ API_KEY_PATTERNS = [
|
|
| 44 |
]
|
| 45 |
|
| 46 |
# Network request patterns
|
| 47 |
-
NETWORK_REQUEST_PATTERNS = re.compile(r"(requests\.|urllib\.|http\.
|
| 48 |
|
| 49 |
-
|
| 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):
|
|
@@ -110,19 +97,11 @@ def check_security_concerns(code_str, dataset_names):
|
|
| 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
|
| 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"]:
|
|
@@ -150,13 +129,6 @@ def clean_code_for_security(code_str, security_concerns, dataset_names):
|
|
| 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"]) + "."
|
|
@@ -164,104 +136,6 @@ def clean_code_for_security(code_str, security_concerns, dataset_names):
|
|
| 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 |
"""
|
| 267 |
This function cleans up any `print()` statements that might contain unwanted `\n` characters.
|
|
@@ -304,14 +178,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 +186,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 +209,8 @@ def format_code_backticked_block(code_str):
|
|
| 344 |
return f'```python\n{code_clean}\n```'
|
| 345 |
|
| 346 |
|
| 347 |
-
|
|
|
|
| 348 |
import pandas as pd
|
| 349 |
import plotly.express as px
|
| 350 |
import plotly
|
|
@@ -355,169 +221,13 @@ def execute_code_from_markdown(code_str, datasets=None):
|
|
| 355 |
import re
|
| 356 |
import traceback
|
| 357 |
import sys
|
| 358 |
-
from io import StringIO
|
| 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
|
| 364 |
|
| 365 |
# Apply security modifications to the code
|
| 366 |
-
modified_code = clean_code_for_security(code_str, security_concerns
|
| 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,
|
|
@@ -529,18 +239,8 @@ def execute_code_from_markdown(code_str, datasets=None):
|
|
| 529 |
'__import__': __import__,
|
| 530 |
'sns': sns,
|
| 531 |
'np': np,
|
| 532 |
-
'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(
|
|
@@ -553,7 +253,8 @@ def execute_code_from_markdown(code_str, datasets=None):
|
|
| 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 +266,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 |
-
|
| 595 |
-
|
| 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 |
-
|
|
|
|
| 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 +314,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 |
-
|
| 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 +465,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 +478,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
|
| 871 |
|
| 872 |
|
| 873 |
def format_plan_instructions(plan_instructions):
|
|
@@ -875,127 +488,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 |
-
|
| 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 |
-
|
| 900 |
-
except
|
| 901 |
-
|
| 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 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 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 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 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 |
-
|
| 963 |
-
|
| 964 |
-
#
|
| 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 |
-
|
| 994 |
|
| 995 |
-
|
| 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 +560,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
|
| 1033 |
-
markdown.append(f"### Reasoning
|
| 1034 |
if 'plan_instructions' in content:
|
| 1035 |
-
|
| 1036 |
-
if plan_result: # Only append if not empty/None
|
| 1037 |
-
markdown.append(f"{plan_result}")
|
| 1038 |
else:
|
| 1039 |
-
|
| 1040 |
-
markdown.append(f"### Reasoning {content['rationale']}")
|
| 1041 |
else:
|
| 1042 |
if "rationale" in content:
|
| 1043 |
-
|
| 1044 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
markdown.append("### Summary\n")
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 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
|
| 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
|
| 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 +631,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"
|
| 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 |
-
|
| 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):
|
|
|
|
| 44 |
]
|
| 45 |
|
| 46 |
# Network request patterns
|
| 47 |
+
NETWORK_REQUEST_PATTERNS = re.compile(r"(requests\.|urllib\.|http\.|\.post\(|\.get\(|\.connect\()")
|
| 48 |
|
| 49 |
+
def check_security_concerns(code_str):
|
|
|
|
|
|
|
| 50 |
"""Check code for security concerns and return info about what was found"""
|
| 51 |
security_concerns = {
|
| 52 |
"has_concern": False,
|
| 53 |
+
"messages": [],
|
| 54 |
"blocked_imports": False,
|
| 55 |
"blocked_dynamic_imports": False,
|
| 56 |
"blocked_env_access": False,
|
| 57 |
"blocked_file_access": False,
|
| 58 |
"blocked_api_keys": False,
|
| 59 |
+
"blocked_network": False
|
|
|
|
|
|
|
| 60 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
# Check for sensitive imports
|
| 63 |
if IMPORT_PATTERN.search(code_str) or FROM_IMPORT_PATTERN.search(code_str):
|
|
|
|
| 97 |
security_concerns["blocked_network"] = True
|
| 98 |
security_concerns["messages"].append("Network requests blocked")
|
| 99 |
|
|
|
|
|
|
|
| 100 |
return security_concerns
|
| 101 |
|
| 102 |
+
def clean_code_for_security(code_str, security_concerns):
|
| 103 |
"""Apply security modifications to the code based on detected concerns"""
|
|
|
|
| 104 |
modified_code = code_str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
# Block sensitive imports if needed
|
| 107 |
if security_concerns["blocked_imports"]:
|
|
|
|
| 129 |
if security_concerns["blocked_network"]:
|
| 130 |
modified_code = NETWORK_REQUEST_PATTERNS.sub(r'"BLOCKED_NETWORK_REQUEST"', modified_code)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
# Add warning banner if needed
|
| 133 |
if security_concerns["has_concern"]:
|
| 134 |
security_message = "⚠️ SECURITY WARNING: " + ". ".join(security_concerns["messages"]) + "."
|
|
|
|
| 136 |
|
| 137 |
return modified_code
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
def clean_print_statements(code_block):
|
| 140 |
"""
|
| 141 |
This function cleans up any `print()` statements that might contain unwanted `\n` characters.
|
|
|
|
| 178 |
return f'\n{code_clean}\n'
|
| 179 |
|
| 180 |
def format_code_backticked_block(code_str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
code_clean = re.sub(r'^```python\n?', '', code_str, flags=re.MULTILINE)
|
| 182 |
code_clean = re.sub(r'\n```$', '', code_clean)
|
| 183 |
# Only match assignments at top level (not indented)
|
|
|
|
| 186 |
|
| 187 |
# Remove reading the csv file if it's already in the context
|
| 188 |
modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', code_clean)
|
|
|
|
| 189 |
|
| 190 |
# Only match assignments at top level (not indented)
|
| 191 |
# 1. Remove 'df = pd.DataFrame()' if it's at the top level
|
|
|
|
| 209 |
return f'```python\n{code_clean}\n```'
|
| 210 |
|
| 211 |
|
| 212 |
+
# In format_response.py, modify the execute_code function:
|
| 213 |
+
def execute_code_from_markdown(code_str, dataframe=None):
|
| 214 |
import pandas as pd
|
| 215 |
import plotly.express as px
|
| 216 |
import plotly
|
|
|
|
| 221 |
import re
|
| 222 |
import traceback
|
| 223 |
import sys
|
| 224 |
+
from io import StringIO
|
|
|
|
| 225 |
|
|
|
|
| 226 |
# Check for security concerns in the code
|
| 227 |
+
security_concerns = check_security_concerns(code_str)
|
| 228 |
|
| 229 |
# Apply security modifications to the code
|
| 230 |
+
modified_code = clean_code_for_security(code_str, security_concerns)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
context = {
|
| 233 |
'pd': pd,
|
|
|
|
| 239 |
'__import__': __import__,
|
| 240 |
'sns': sns,
|
| 241 |
'np': np,
|
| 242 |
+
'json_outputs': [] # List to store multiple Plotly JSON outputs
|
|
|
|
|
|
|
| 243 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
# Modify code to store multiple JSON outputs
|
| 246 |
modified_code = re.sub(
|
|
|
|
| 253 |
r'(\w*_?)fig(\w*)\.to_html\(.*?\)',
|
| 254 |
r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))',
|
| 255 |
modified_code
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
# Remove reading the csv file if it's already in the context
|
| 259 |
modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', modified_code)
|
| 260 |
|
|
|
|
| 266 |
modified_code,
|
| 267 |
flags=re.MULTILINE
|
| 268 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
# If a dataframe is provided, add it to the context
|
| 271 |
+
if dataframe is not None:
|
| 272 |
+
context['df'] = dataframe
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
# remove pd.read_csv() if it's already in the context
|
| 275 |
modified_code = re.sub(r"pd\.read_csv\(\s*[\"\'].*?[\"\']\s*\)", '', modified_code)
|
| 276 |
|
| 277 |
# Remove sample dataframe lines with multiple array values
|
| 278 |
modified_code = re.sub(r"^# Sample DataFrames?.*?(\n|$)", '', modified_code, flags=re.MULTILINE | re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
+
# Remove plt.show() statements
|
| 281 |
+
modified_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', modified_code)
|
| 282 |
|
| 283 |
# Only add df = pd.read_csv() if no dataframe was provided and the code contains pd.read_csv
|
| 284 |
+
if dataframe is None and 'pd.read_csv' not in modified_code:
|
| 285 |
+
modified_code = re.sub(
|
| 286 |
+
r'import pandas as pd',
|
| 287 |
+
r'import pandas as pd\n\n# Read Housing.csv\ndf = pd.read_csv("Housing.csv")',
|
| 288 |
+
modified_code
|
| 289 |
+
)
|
| 290 |
|
| 291 |
# Identify code blocks by comments
|
| 292 |
code_blocks = []
|
|
|
|
| 314 |
all_outputs = []
|
| 315 |
for block_name, block_code in code_blocks:
|
| 316 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
with stdoutIO() as s:
|
| 318 |
exec(block_code, context) # Execute the block
|
| 319 |
+
output = s.getvalue()
|
| 320 |
+
all_outputs.append((block_name, output, None)) # None means no error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
error_traceback = traceback.format_exc()
|
| 323 |
|
| 324 |
# Extract error message and error type
|
|
|
|
| 465 |
|
| 466 |
all_outputs.append((block_name, None, formatted_error))
|
| 467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
# Compile all outputs and errors
|
| 469 |
output_text = ""
|
| 470 |
json_outputs = context.get('json_outputs', [])
|
|
|
|
| 471 |
error_found = False
|
| 472 |
|
| 473 |
for block_name, output, error in all_outputs:
|
|
|
|
| 478 |
output_text += f"\n\n=== OUTPUT FROM {block_name.upper()}_AGENT ===\n{output}\n"
|
| 479 |
|
| 480 |
if error_found:
|
| 481 |
+
return output_text, []
|
| 482 |
else:
|
| 483 |
+
return output_text, json_outputs
|
| 484 |
|
| 485 |
|
| 486 |
def format_plan_instructions(plan_instructions):
|
|
|
|
| 488 |
Format any plan instructions (JSON string or dict) into markdown sections per agent.
|
| 489 |
"""
|
| 490 |
# Parse input into a dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
try:
|
| 492 |
if isinstance(plan_instructions, str):
|
| 493 |
+
instructions = json.loads(plan_instructions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
elif isinstance(plan_instructions, dict):
|
| 495 |
instructions = plan_instructions
|
| 496 |
else:
|
| 497 |
+
return f"Unsupported plan instructions type: {type(plan_instructions)}"
|
| 498 |
+
except json.JSONDecodeError:
|
| 499 |
+
return f"Invalid plan instructions: {plan_instructions}"
|
| 500 |
# logger.log_message(f"Plan instructions: {instructions}", level=logging.INFO)
|
| 501 |
|
|
|
|
|
|
|
| 502 |
markdown_lines = []
|
| 503 |
for agent, content in instructions.items():
|
| 504 |
+
agent_title = agent.replace('_', ' ').title()
|
| 505 |
+
markdown_lines.append(f"#### {agent_title}")
|
| 506 |
+
if isinstance(content, dict):
|
| 507 |
+
# Handle 'create' key
|
| 508 |
+
create_vals = content.get('create', [])
|
| 509 |
+
if create_vals:
|
| 510 |
+
markdown_lines.append(f"- **Create**:")
|
| 511 |
+
for item in create_vals:
|
| 512 |
+
markdown_lines.append(f" - {item}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
else:
|
| 514 |
+
markdown_lines.append(f"- **Create**: None")
|
| 515 |
+
|
| 516 |
+
# Handle 'use' key
|
| 517 |
+
use_vals = content.get('use', [])
|
| 518 |
+
if use_vals:
|
| 519 |
+
markdown_lines.append(f"- **Use**:")
|
| 520 |
+
for item in use_vals:
|
| 521 |
+
markdown_lines.append(f" - {item}")
|
| 522 |
+
else:
|
| 523 |
+
markdown_lines.append(f"- **Use**: None")
|
| 524 |
|
| 525 |
+
# Handle 'instruction' key
|
| 526 |
+
instr = content.get('instruction')
|
| 527 |
+
if isinstance(instr, str) and instr:
|
| 528 |
+
markdown_lines.append(f"- **Instruction**: {instr}")
|
| 529 |
+
else:
|
| 530 |
+
markdown_lines.append(f"- **Instruction**: None")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
else:
|
| 532 |
+
# Fallback for non-dict content
|
| 533 |
+
markdown_lines.append(f"- {content}")
|
| 534 |
+
markdown_lines.append("") # blank line between agents
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
|
| 536 |
+
return "\n".join(markdown_lines).strip()
|
| 537 |
|
| 538 |
+
def format_response_to_markdown(api_response, agent_name = None, dataframe=None):
|
|
|
|
|
|
|
|
|
|
| 539 |
try:
|
| 540 |
markdown = []
|
| 541 |
# logger.log_message(f"API response for {agent_name} at {time.strftime('%Y-%m-%d %H:%M:%S')}: {api_response}", level=logging.INFO)
|
|
|
|
| 560 |
if "memory" in agent or not content:
|
| 561 |
continue
|
| 562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
markdown.append(f"\n## {agent.replace('_', ' ').title()}\n")
|
| 564 |
|
| 565 |
if agent == "analytical_planner":
|
| 566 |
+
# logger.log_message(f"Analytical planner content: {content}", level=logging.INFO)
|
| 567 |
+
if 'plan_desc' in content:
|
| 568 |
+
markdown.append(f"### Reasoning\n{content['plan_desc']}\n")
|
| 569 |
if 'plan_instructions' in content:
|
| 570 |
+
markdown.append(f"### Plan Instructions\n{format_plan_instructions(content['plan_instructions'])}\n")
|
|
|
|
|
|
|
| 571 |
else:
|
| 572 |
+
markdown.append(f"### Reasoning\n{content['rationale']}\n")
|
|
|
|
| 573 |
else:
|
| 574 |
if "rationale" in content:
|
| 575 |
+
markdown.append(f"### Reasoning\n{content['rationale']}\n")
|
| 576 |
+
|
| 577 |
+
if 'code' in content:
|
| 578 |
+
markdown.append(f"### Code Implementation\n{format_code_backticked_block(content['code'])}\n")
|
| 579 |
+
# if agent_name is not None:
|
| 580 |
+
# # execute the code
|
| 581 |
+
# clean_code = format_code_block(content['code'])
|
| 582 |
+
# output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
|
| 583 |
+
# if output:
|
| 584 |
+
# markdown.append("### Execution Output\n")
|
| 585 |
+
# markdown.append(f"```output\n{output}\n```\n")
|
| 586 |
+
|
| 587 |
+
# if json_outputs:
|
| 588 |
+
# markdown.append("### Plotly JSON Outputs\n")
|
| 589 |
+
# for idx, json_output in enumerate(json_outputs):
|
| 590 |
+
# if len(json_output) > 1000000: # If JSON is larger than 1MB
|
| 591 |
+
# logger.log_message(f"Large JSON output detected: {len(json_output)} bytes", level=logging.WARNING)
|
| 592 |
+
# markdown.append(f"```plotly\n{json_output}\n```\n")
|
| 593 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
if 'summary' in content:
|
| 595 |
+
# make the summary a bullet-point list
|
| 596 |
+
summary_lines = remove_code_block_from_summary(content['summary'])
|
| 597 |
+
summary_lines = content['summary'].split('\n')
|
| 598 |
+
# remove code block from summary
|
| 599 |
markdown.append("### Summary\n")
|
| 600 |
+
for line in summary_lines:
|
| 601 |
+
if line != "":
|
| 602 |
+
if line.strip().startswith('•') or line.strip().startswith('-') or line.strip().startswith('*'):
|
| 603 |
+
line = line.strip().replace('•', '').replace('-', '').replace('*', '')
|
| 604 |
+
markdown.append(f"* {line.strip()}\n")
|
| 605 |
+
else:
|
| 606 |
+
markdown.append(f"{line.strip()}\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
|
| 608 |
if 'refined_complete_code' in content and 'summary' in content:
|
| 609 |
try:
|
| 610 |
if content['refined_complete_code'] is not None and content['refined_complete_code'] != "":
|
| 611 |
clean_code = format_code_block(content['refined_complete_code'])
|
| 612 |
markdown_code = format_code_backticked_block(content['refined_complete_code'])
|
| 613 |
+
output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
|
| 614 |
elif "```python" in content['summary']:
|
| 615 |
clean_code = format_code_block(content['summary'])
|
| 616 |
markdown_code = format_code_backticked_block(content['summary'])
|
| 617 |
+
output, json_outputs = execute_code_from_markdown(clean_code, dataframe)
|
| 618 |
except Exception as e:
|
| 619 |
logger.log_message(f"Error in execute_code_from_markdown: {str(e)}", level=logging.ERROR)
|
| 620 |
markdown_code = f"**Error**: {str(e)}"
|
|
|
|
|
|
|
|
|
|
| 621 |
# continue
|
| 622 |
|
| 623 |
if markdown_code is not None:
|
|
|
|
| 631 |
markdown.append("### Plotly JSON Outputs\n")
|
| 632 |
for idx, json_output in enumerate(json_outputs):
|
| 633 |
markdown.append(f"```plotly\n{json_output}\n```\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
# if agent_name is not None:
|
| 635 |
# if f"memory_{agent_name}" in api_response:
|
| 636 |
# markdown.append(f"### Memory\n{api_response[f'memory_{agent_name}']}\n")
|
| 637 |
|
| 638 |
except Exception as e:
|
| 639 |
logger.log_message(f"Error in format_response_to_markdown: {str(e)}", level=logging.ERROR)
|
| 640 |
+
return f"{str(e)}"
|
| 641 |
|
| 642 |
# 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)
|
| 643 |
|
| 644 |
if not markdown or len(markdown) <= 1:
|
| 645 |
+
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)
|
| 646 |
+
return "Please provide a valid query..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
|
| 648 |
return '\n'.join(markdown)
|
| 649 |
|
| 650 |
+
# Example usage with dummy data
|
| 651 |
+
if __name__ == "__main__":
|
| 652 |
+
sample_response = {
|
| 653 |
+
"code_combiner_agent": {
|
| 654 |
+
"reasoning": "Sample reasoning for multiple charts.",
|
| 655 |
+
"refined_complete_code": """
|
| 656 |
+
```python
|
| 657 |
+
import plotly.express as px
|
| 658 |
+
import pandas as pd
|
| 659 |
+
|
| 660 |
+
# Sample Data
|
| 661 |
+
df = pd.DataFrame({'Category': ['A', 'B', 'C'], 'Values': [10, 20, 30]})
|
| 662 |
+
|
| 663 |
+
# First Chart
|
| 664 |
+
fig = px.bar(df, x='Category', y='Values', title='Bar Chart')
|
| 665 |
+
fig.show()
|
| 666 |
+
|
| 667 |
+
# Second Chart
|
| 668 |
+
fig2 = px.pie(df, values='Values', names='Category', title='Pie Chart')
|
| 669 |
+
fig2.show()
|
| 670 |
+
```
|
| 671 |
+
"""
|
| 672 |
+
}
|
| 673 |
+
}
|
| 674 |
|
| 675 |
+
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:
|
| 6 |
-
# tier 3: > $0.
|
| 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"] <
|
| 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"] >=
|
| 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"] >=
|
| 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 |
-
|
| 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_['
|
| 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)}
|
src/db/schemas/models.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, DateTime, Text, Float, Boolean, JSON
|
| 2 |
from sqlalchemy.ext.declarative import declarative_base
|
| 3 |
from sqlalchemy.orm import sessionmaker, relationship
|
| 4 |
-
from datetime import datetime
|
| 5 |
|
| 6 |
# Define the base class for declarative models
|
| 7 |
Base = declarative_base()
|
|
@@ -11,14 +11,12 @@ class User(Base):
|
|
| 11 |
__tablename__ = 'users'
|
| 12 |
|
| 13 |
user_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 14 |
-
username = Column(String, nullable=False)
|
| 15 |
email = Column(String, unique=True, nullable=False)
|
| 16 |
-
created_at = Column(DateTime, default=
|
| 17 |
# Add relationship for cascade options
|
| 18 |
chats = relationship("Chat", back_populates="user", cascade="all, delete-orphan")
|
| 19 |
usage_records = relationship("ModelUsage", back_populates="user")
|
| 20 |
-
deep_analysis_reports = relationship("DeepAnalysisReport", back_populates="user", cascade="all, delete-orphan")
|
| 21 |
-
template_preferences = relationship("UserTemplatePreference", back_populates="user", cascade="all, delete-orphan")
|
| 22 |
|
| 23 |
# Define the Chats table
|
| 24 |
class Chat(Base):
|
|
@@ -27,7 +25,7 @@ class Chat(Base):
|
|
| 27 |
chat_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 28 |
user_id = Column(Integer, ForeignKey('users.user_id', ondelete="CASCADE"), nullable=True)
|
| 29 |
title = Column(String, default='New Chat')
|
| 30 |
-
created_at = Column(DateTime, default=
|
| 31 |
# Add relationships for cascade options
|
| 32 |
user = relationship("User", back_populates="chats")
|
| 33 |
messages = relationship("Message", back_populates="chat", cascade="all, delete-orphan")
|
|
@@ -41,7 +39,7 @@ class Message(Base):
|
|
| 41 |
chat_id = Column(Integer, ForeignKey('chats.chat_id', ondelete="CASCADE"), nullable=False)
|
| 42 |
sender = Column(String, nullable=False) # 'user' or 'ai'
|
| 43 |
content = Column(Text, nullable=False)
|
| 44 |
-
timestamp = Column(DateTime, default=
|
| 45 |
# Add relationship for cascade options
|
| 46 |
chat = relationship("Chat", back_populates="messages")
|
| 47 |
feedback = relationship("MessageFeedback", back_populates="message", uselist=False, cascade="all, delete-orphan")
|
|
@@ -62,7 +60,7 @@ class ModelUsage(Base):
|
|
| 62 |
query_size = Column(Integer, default=0) # Size in characters
|
| 63 |
response_size = Column(Integer, default=0) # Size in characters
|
| 64 |
cost = Column(Float, default=0.0) # Cost in USD
|
| 65 |
-
timestamp = Column(DateTime, default=
|
| 66 |
is_streaming = Column(Boolean, default=False)
|
| 67 |
request_time_ms = Column(Integer, default=0) # Request processing time in milliseconds
|
| 68 |
# Add relationships
|
|
@@ -98,8 +96,8 @@ class CodeExecution(Base):
|
|
| 98 |
error_messages = Column(Text, nullable=True) # JSON map of error messages by agent
|
| 99 |
|
| 100 |
# Metadata
|
| 101 |
-
created_at = Column(DateTime, default=
|
| 102 |
-
updated_at = Column(DateTime, default=
|
| 103 |
|
| 104 |
class MessageFeedback(Base):
|
| 105 |
"""Tracks user feedback and model settings for each message."""
|
|
@@ -118,120 +116,10 @@ class MessageFeedback(Base):
|
|
| 118 |
max_tokens = Column(Integer, nullable=True)
|
| 119 |
|
| 120 |
# Metadata
|
| 121 |
-
created_at = Column(DateTime, default=
|
| 122 |
-
updated_at = Column(DateTime, default=
|
| 123 |
|
| 124 |
# Relationship
|
| 125 |
message = relationship("Message", back_populates="feedback")
|
| 126 |
-
|
| 127 |
-
class DeepAnalysisReport(Base):
|
| 128 |
-
"""Stores deep analysis reports with comprehensive analysis data and metadata."""
|
| 129 |
-
__tablename__ = 'deep_analysis_reports'
|
| 130 |
-
|
| 131 |
-
report_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 132 |
-
report_uuid = Column(String(100), unique=True, nullable=False) # Frontend generated ID
|
| 133 |
-
user_id = Column(Integer, ForeignKey('users.user_id', ondelete="CASCADE"), nullable=True)
|
| 134 |
-
|
| 135 |
-
# Analysis objective and status
|
| 136 |
-
goal = Column(Text, nullable=False) # The analysis objective/question
|
| 137 |
-
status = Column(String(20), nullable=False, default='pending') # 'pending', 'running', 'completed', 'failed'
|
| 138 |
-
|
| 139 |
-
# Timing information
|
| 140 |
-
start_time = Column(DateTime, default=lambda: datetime.now(UTC))
|
| 141 |
-
end_time = Column(DateTime, nullable=True)
|
| 142 |
-
duration_seconds = Column(Integer, nullable=True) # Calculated duration
|
| 143 |
-
|
| 144 |
-
# Analysis components (stored as text/JSON)
|
| 145 |
-
deep_questions = Column(Text, nullable=True) # Generated analytical questions
|
| 146 |
-
deep_plan = Column(Text, nullable=True) # Analysis plan
|
| 147 |
-
summaries = Column(JSON, nullable=True) # Array of analysis summaries
|
| 148 |
-
analysis_code = Column(Text, nullable=True) # Generated Python code
|
| 149 |
-
plotly_figures = Column(JSON, nullable=True) # Array of Plotly figure data
|
| 150 |
-
synthesis = Column(JSON, nullable=True) # Array of synthesis insights
|
| 151 |
-
final_conclusion = Column(Text, nullable=True) # Final analysis conclusion
|
| 152 |
-
|
| 153 |
-
# Report output
|
| 154 |
-
html_report = Column(Text, nullable=True) # Complete HTML report
|
| 155 |
-
report_summary = Column(Text, nullable=True) # Brief summary for listing
|
| 156 |
-
|
| 157 |
-
# Execution tracking
|
| 158 |
-
progress_percentage = Column(Integer, default=0) # Progress 0-100
|
| 159 |
-
steps_completed = Column(JSON, nullable=True) # Array of completed step names
|
| 160 |
-
error_message = Column(Text, nullable=True) # Error details if failed
|
| 161 |
-
|
| 162 |
-
# Model and cost tracking
|
| 163 |
-
model_provider = Column(String(50), nullable=True)
|
| 164 |
-
model_name = Column(String(100), nullable=True)
|
| 165 |
-
total_tokens_used = Column(Integer, default=0)
|
| 166 |
-
estimated_cost = Column(Float, default=0.0) # Cost in USD
|
| 167 |
-
credits_consumed = Column(Integer, default=0) # Credits deducted for this analysis
|
| 168 |
-
|
| 169 |
-
# Metadata
|
| 170 |
-
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
| 171 |
-
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
| 172 |
-
|
| 173 |
-
# Relationships
|
| 174 |
-
user = relationship("User", back_populates="deep_analysis_reports")
|
| 175 |
-
|
| 176 |
-
class AgentTemplate(Base):
|
| 177 |
-
"""Stores predefined agent templates that users can enable/disable."""
|
| 178 |
-
__tablename__ = 'agent_templates'
|
| 179 |
-
|
| 180 |
-
template_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 181 |
-
|
| 182 |
-
# Template definition
|
| 183 |
-
template_name = Column(String(100), nullable=False, unique=True) # e.g., 'pytorch_specialist', 'data_cleaning_expert'
|
| 184 |
-
display_name = Column(String(200), nullable=True) # User-friendly display name
|
| 185 |
-
description = Column(Text, nullable=False) # Short description for template selection
|
| 186 |
-
prompt_template = Column(Text, nullable=False) # Main prompt/instructions for agent behavior
|
| 187 |
-
|
| 188 |
-
# Template appearance
|
| 189 |
-
icon_url = Column(String(500), nullable=True) # URL to template icon (CDN, data URL, or relative path)
|
| 190 |
-
|
| 191 |
-
# Template categorization
|
| 192 |
-
category = Column(String(50), nullable=True) # 'Visualization', 'Modelling', 'Data Manipulation'
|
| 193 |
-
is_premium_only = Column(Boolean, default=False) # True if template requires premium subscription
|
| 194 |
-
|
| 195 |
-
# Agent variant support
|
| 196 |
-
variant_type = Column(String(20), default='individual') # 'planner', 'individual', or 'both'
|
| 197 |
-
base_agent = Column(String(100), nullable=True) # Base agent name for variants (e.g., 'preprocessing_agent')
|
| 198 |
-
|
| 199 |
-
# Status and metadata
|
| 200 |
-
is_active = Column(Boolean, default=True)
|
| 201 |
-
|
| 202 |
-
# Timestamps
|
| 203 |
-
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
| 204 |
-
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
| 205 |
-
|
| 206 |
-
# Relationships
|
| 207 |
-
user_preferences = relationship("UserTemplatePreference", back_populates="template", cascade="all, delete-orphan")
|
| 208 |
-
|
| 209 |
-
class UserTemplatePreference(Base):
|
| 210 |
-
"""Tracks user preferences and usage for agent templates."""
|
| 211 |
-
__tablename__ = 'user_template_preferences'
|
| 212 |
-
|
| 213 |
-
preference_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 214 |
-
user_id = Column(Integer, ForeignKey('users.user_id', ondelete="CASCADE"), nullable=False)
|
| 215 |
-
template_id = Column(Integer, ForeignKey('agent_templates.template_id', ondelete="CASCADE"), nullable=False)
|
| 216 |
-
|
| 217 |
-
# User preferences
|
| 218 |
-
is_enabled = Column(Boolean, default=True) # Whether user has this template enabled
|
| 219 |
-
|
| 220 |
-
# Usage tracking
|
| 221 |
-
usage_count = Column(Integer, default=0) # Track how many times user has used this template
|
| 222 |
-
last_used_at = Column(DateTime, nullable=True) # Last time user used this template
|
| 223 |
-
|
| 224 |
-
# Timestamps
|
| 225 |
-
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
|
| 226 |
-
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
| 227 |
-
|
| 228 |
-
# Relationships
|
| 229 |
-
user = relationship("User", back_populates="template_preferences")
|
| 230 |
-
template = relationship("AgentTemplate", back_populates="user_preferences")
|
| 231 |
-
|
| 232 |
-
# Constraints - user can only have one preference record per template
|
| 233 |
-
__table_args__ = (
|
| 234 |
-
UniqueConstraint('user_id', 'template_id', name='unique_user_template_preference'),
|
| 235 |
-
)
|
| 236 |
|
| 237 |
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, DateTime, Text, Float, Boolean, JSON
|
| 2 |
from sqlalchemy.ext.declarative import declarative_base
|
| 3 |
from sqlalchemy.orm import sessionmaker, relationship
|
| 4 |
+
from datetime import datetime
|
| 5 |
|
| 6 |
# Define the base class for declarative models
|
| 7 |
Base = declarative_base()
|
|
|
|
| 11 |
__tablename__ = 'users'
|
| 12 |
|
| 13 |
user_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 14 |
+
username = Column(String, unique=True, nullable=False)
|
| 15 |
email = Column(String, unique=True, nullable=False)
|
| 16 |
+
created_at = Column(DateTime, default=datetime.utcnow)
|
| 17 |
# Add relationship for cascade options
|
| 18 |
chats = relationship("Chat", back_populates="user", cascade="all, delete-orphan")
|
| 19 |
usage_records = relationship("ModelUsage", back_populates="user")
|
|
|
|
|
|
|
| 20 |
|
| 21 |
# Define the Chats table
|
| 22 |
class Chat(Base):
|
|
|
|
| 25 |
chat_id = Column(Integer, primary_key=True, autoincrement=True)
|
| 26 |
user_id = Column(Integer, ForeignKey('users.user_id', ondelete="CASCADE"), nullable=True)
|
| 27 |
title = Column(String, default='New Chat')
|
| 28 |
+
created_at = Column(DateTime, default=datetime.utcnow)
|
| 29 |
# Add relationships for cascade options
|
| 30 |
user = relationship("User", back_populates="chats")
|
| 31 |
messages = relationship("Message", back_populates="chat", cascade="all, delete-orphan")
|
|
|
|
| 39 |
chat_id = Column(Integer, ForeignKey('chats.chat_id', ondelete="CASCADE"), nullable=False)
|
| 40 |
sender = Column(String, nullable=False) # 'user' or 'ai'
|
| 41 |
content = Column(Text, nullable=False)
|
| 42 |
+
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 43 |
# Add relationship for cascade options
|
| 44 |
chat = relationship("Chat", back_populates="messages")
|
| 45 |
feedback = relationship("MessageFeedback", back_populates="message", uselist=False, cascade="all, delete-orphan")
|
|
|
|
| 60 |
query_size = Column(Integer, default=0) # Size in characters
|
| 61 |
response_size = Column(Integer, default=0) # Size in characters
|
| 62 |
cost = Column(Float, default=0.0) # Cost in USD
|
| 63 |
+
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 64 |
is_streaming = Column(Boolean, default=False)
|
| 65 |
request_time_ms = Column(Integer, default=0) # Request processing time in milliseconds
|
| 66 |
# Add relationships
|
|
|
|
| 96 |
error_messages = Column(Text, nullable=True) # JSON map of error messages by agent
|
| 97 |
|
| 98 |
# Metadata
|
| 99 |
+
created_at = Column(DateTime, default=datetime.utcnow)
|
| 100 |
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 101 |
|
| 102 |
class MessageFeedback(Base):
|
| 103 |
"""Tracks user feedback and model settings for each message."""
|
|
|
|
| 116 |
max_tokens = Column(Integer, nullable=True)
|
| 117 |
|
| 118 |
# Metadata
|
| 119 |
+
created_at = Column(DateTime, default=datetime.utcnow)
|
| 120 |
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 121 |
|
| 122 |
# Relationship
|
| 123 |
message = relationship("Message", back_populates="feedback")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
|
src/managers/ai_manager.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
import logging
|
| 2 |
from typing import Optional, Dict, Any
|
|
|
|
| 3 |
from src.db.schemas.models import ModelUsage
|
| 4 |
from src.db.init_db import session_factory
|
| 5 |
-
from datetime import datetime
|
|
|
|
| 6 |
from src.routes.analytics_routes import handle_new_model_usage
|
| 7 |
import asyncio
|
| 8 |
|
|
@@ -43,7 +45,7 @@ class AI_Manager:
|
|
| 43 |
query_size=query_size,
|
| 44 |
response_size=response_size,
|
| 45 |
cost=cost,
|
| 46 |
-
timestamp=datetime.
|
| 47 |
is_streaming=is_streaming,
|
| 48 |
request_time_ms=request_time_ms
|
| 49 |
)
|
|
|
|
| 1 |
import logging
|
| 2 |
from typing import Optional, Dict, Any
|
| 3 |
+
import time
|
| 4 |
from src.db.schemas.models import ModelUsage
|
| 5 |
from src.db.init_db import session_factory
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
import tiktoken
|
| 8 |
from src.routes.analytics_routes import handle_new_model_usage
|
| 9 |
import asyncio
|
| 10 |
|
|
|
|
| 45 |
query_size=query_size,
|
| 46 |
response_size=response_size,
|
| 47 |
cost=cost,
|
| 48 |
+
timestamp=datetime.utcnow(),
|
| 49 |
is_streaming=is_streaming,
|
| 50 |
request_time_ms=request_time_ms
|
| 51 |
)
|