| import re |
| import json |
| import sys |
| import contextlib |
| from io import StringIO |
| import time |
| import logging |
| from src.utils.logger import Logger |
| import textwrap |
|
|
| logger = Logger(__name__, level="INFO", see_time=False, console_log=False) |
|
|
| @contextlib.contextmanager |
| def stdoutIO(stdout=None): |
| old = sys.stdout |
| if stdout is None: |
| stdout = StringIO() |
| sys.stdout = stdout |
| yield stdout |
| sys.stdout = old |
| |
| |
| def clean_print_statements(code_block): |
| """ |
| This function cleans up any `print()` statements that might contain unwanted `\n` characters. |
| It ensures print statements are properly formatted without unnecessary newlines. |
| """ |
| |
| return re.sub(r'print\((.*?)(\\n.*?)(.*?)\)', r'print(\1\3)', code_block, flags=re.DOTALL) |
|
|
| def remove_code_block_from_summary(summary): |
| |
| summary = re.sub(r'```python\n(.*?)\n```', '', summary) |
| return summary.split("\n") |
|
|
| def remove_main_block(code): |
| |
| pattern = r'(?m)^if\s+__name__\s*==\s*["\']__main__["\']\s*:\s*\n((?:\s+.*\n?)*)' |
| |
| match = re.search(pattern, code) |
| if match: |
| main_block = match.group(1) |
| |
| |
| dedented_block = textwrap.dedent(main_block) |
| |
| |
| dedented_block = clean_print_statements(dedented_block) |
| |
| cleaned_code = re.sub(pattern, dedented_block, code) |
| |
| |
| cleaned_code = cleaned_code.strip() |
| |
| return cleaned_code |
| return code |
|
|
|
|
| def format_code_block(code_str): |
| code_clean = re.sub(r'^```python\n?', '', code_str, flags=re.MULTILINE) |
| code_clean = re.sub(r'\n```$', '', code_clean) |
| return f'\n{code_clean}\n' |
|
|
| def format_code_backticked_block(code_str): |
| code_clean = re.sub(r'^```python\n?', '', code_str, flags=re.MULTILINE) |
| code_clean = re.sub(r'\n```$', '', code_clean) |
| |
| |
| |
| |
| |
| modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', code_clean) |
| |
| |
| |
| modified_code = re.sub( |
| r"^df\s*=\s*pd\.DataFrame\(\s*\)\s*(#.*)?$", |
| '', |
| modified_code, |
| flags=re.MULTILINE |
| ) |
|
|
| |
| modified_code = re.sub(r"^# Sample DataFrames?.*?(\n|$)", '', modified_code, flags=re.MULTILINE | re.IGNORECASE) |
| |
| |
| modified_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', modified_code) |
| |
| |
| |
| code_clean = remove_main_block(modified_code) |
| |
| return f'```python\n{code_clean}\n```' |
|
|
| |
| |
| def execute_code_from_markdown(code_str, dataframe=None): |
| import pandas as pd |
| import plotly.express as px |
| import plotly |
| import plotly.graph_objects as go |
| import matplotlib.pyplot as plt |
| import seaborn as sns |
| import numpy as np |
| import re |
| import traceback |
| import sys |
| from io import StringIO |
|
|
| context = { |
| 'pd': pd, |
| 'px': px, |
| 'go': go, |
| 'plt': plt, |
| 'plotly': plotly, |
| '__builtins__': __builtins__, |
| '__import__': __import__, |
| 'sns': sns, |
| 'np': np, |
| 'json_outputs': [] |
| } |
|
|
| |
| modified_code = re.sub( |
| r'(\w*_?)fig(\w*)\.show\(\)', |
| r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))', |
| code_str |
| ) |
|
|
| modified_code = re.sub( |
| r'(\w*_?)fig(\w*)\.to_html\(.*?\)', |
| r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))', |
| modified_code |
| ) |
| |
| |
| modified_code = re.sub(r"df\s*=\s*pd\.read_csv\([\"\'].*?[\"\']\).*?(\n|$)", '', modified_code) |
| |
| |
| |
| modified_code = re.sub( |
| r"^df\s*=\s*pd\.DataFrame\(\s*\)\s*(#.*)?$", |
| '', |
| modified_code, |
| flags=re.MULTILINE |
| ) |
|
|
| |
| if dataframe is not None: |
| context['df'] = dataframe |
|
|
| |
| modified_code = re.sub(r"pd\.read_csv\(\s*[\"\'].*?[\"\']\s*\)", '', modified_code) |
|
|
| |
| modified_code = re.sub(r"^# Sample DataFrames?.*?(\n|$)", '', modified_code, flags=re.MULTILINE | re.IGNORECASE) |
| |
| |
| modified_code = re.sub(r"plt\.show\(\).*?(\n|$)", '', modified_code) |
| |
| |
| if dataframe is None and 'pd.read_csv' not in modified_code: |
| modified_code = re.sub( |
| r'import pandas as pd', |
| r'import pandas as pd\n\n# Read Housing.csv\ndf = pd.read_csv("Housing.csv")', |
| modified_code |
| ) |
|
|
| |
| code_blocks = [] |
| current_block = [] |
| current_block_name = "unknown" |
| |
| for line in modified_code.splitlines(): |
| |
| block_match = re.match(r'^# ([a-zA-Z_]+)_agent code start', line) |
| if block_match: |
| |
| if current_block: |
| code_blocks.append((current_block_name, '\n'.join(current_block))) |
| |
| current_block_name = block_match.group(1) |
| current_block = [] |
| else: |
| current_block.append(line) |
| |
| |
| if current_block: |
| code_blocks.append((current_block_name, '\n'.join(current_block))) |
| |
| |
| all_outputs = [] |
| for block_name, block_code in code_blocks: |
| try: |
| with stdoutIO() as s: |
| exec(block_code, context) |
| output = s.getvalue() |
| all_outputs.append((block_name, output, None)) |
| except Exception as e: |
| error_traceback = traceback.format_exc() |
| |
| |
| error_message = str(e) |
| error_type = type(e).__name__ |
| error_lines = error_traceback.splitlines() |
| |
| |
| formatted_error = f"Error in {block_name}_agent: {error_message}\n" |
| |
| |
| first_lines = error_lines[:3] |
| formatted_error += "\n".join(first_lines) + "\n" |
| |
| |
| problem_vars = [] |
| |
| |
| if "not in index" in error_message: |
| |
| column_match = re.search(r"\['([^']+)'(?:, '([^']+)')*\] not in index", error_message) |
| if column_match: |
| problem_vars = [g for g in column_match.groups() if g is not None] |
| |
| |
| potential_lines = [] |
| code_lines = block_code.splitlines() |
| |
| |
| df_access_patterns = [] |
| for i, line in enumerate(code_lines): |
| |
| df_matches = re.findall(r'(\w+)(?:\[|\.)(?:loc|iloc|columns|at|iat|\.select)', line) |
| for df_var in df_matches: |
| df_access_patterns.append((i, df_var)) |
| |
| |
| for var in problem_vars: |
| if re.search(r'\b(numeric_columns|categorical_columns|columns|features|cols)\b', line): |
| potential_lines.append(i) |
| |
| |
| if df_access_patterns: |
| for i, df_var in df_access_patterns: |
| if any(re.search(rf'{df_var}\[.*?\]', line) for line in code_lines): |
| potential_lines.append(i) |
| |
| |
| if not potential_lines: |
| for i, line in enumerate(code_lines): |
| if re.search(r'(?:corr|drop|groupby|pivot|merge|join|concat|apply|map|filter|loc|iloc)\(', line): |
| potential_lines.append(i) |
| |
| |
| potential_lines = sorted(set(potential_lines)) |
| elif "name" in error_message and "is not defined" in error_message: |
| |
| var_match = re.search(r"name '([^']+)' is not defined", error_message) |
| if var_match: |
| problem_vars = [var_match.group(1)] |
| elif "object has no attribute" in error_message: |
| |
| attr_match = re.search(r"'([^']+)' object has no attribute '([^']+)'", error_message) |
| if attr_match: |
| problem_vars = [f"{attr_match.group(1)}.{attr_match.group(2)}"] |
| |
| |
| if problem_vars: |
| formatted_error += "\nProblem likely in these lines:\n" |
| code_lines = block_code.splitlines() |
| problem_lines = [] |
| |
| |
| direct_matches = False |
| for i, line in enumerate(code_lines): |
| if any(var in line for var in problem_vars): |
| direct_matches = True |
| |
| start_idx = max(0, i-1) |
| end_idx = min(len(code_lines), i+2) |
| |
| for j in range(start_idx, end_idx): |
| line_prefix = f"{j+1}: " |
| if j == i: |
| problem_lines.append(f"{line_prefix}>>> {code_lines[j]} <<<") |
| else: |
| problem_lines.append(f"{line_prefix}{code_lines[j]}") |
| |
| problem_lines.append("") |
| |
| |
| if not direct_matches and "not in index" in error_message and 'potential_lines' in locals(): |
| for i in potential_lines: |
| start_idx = max(0, i-1) |
| end_idx = min(len(code_lines), i+2) |
| |
| for j in range(start_idx, end_idx): |
| line_prefix = f"{j+1}: " |
| if j == i: |
| problem_lines.append(f"{line_prefix}>>> {code_lines[j]} <<<") |
| else: |
| problem_lines.append(f"{line_prefix}{code_lines[j]}") |
| |
| problem_lines.append("") |
| |
| if problem_lines: |
| formatted_error += "\n".join(problem_lines) |
| else: |
| |
| if "not in index" in error_message: |
| formatted_error += (f"Unable to locate direct reference to columns: {', '.join(problem_vars)}\n" |
| f"Check for variables that might contain these column names (like numeric_columns, " |
| f"categorical_columns, etc.)\n") |
| else: |
| formatted_error += f"Unable to locate lines containing: {', '.join(problem_vars)}\n" |
| else: |
| |
| for line in reversed(error_lines): |
| |
| if ', line ' in line and '<module>' in line: |
| try: |
| line_num = int(re.search(r', line (\d+)', line).group(1)) |
| code_lines = block_code.splitlines() |
| if 0 < line_num <= len(code_lines): |
| line_idx = line_num - 1 |
| start_idx = max(0, line_idx-2) |
| end_idx = min(len(code_lines), line_idx+3) |
| |
| formatted_error += "\nProblem at this location:\n" |
| for i in range(start_idx, end_idx): |
| line_prefix = f"{i+1}: " |
| if i == line_idx: |
| formatted_error += f"{line_prefix}>>> {code_lines[i]} <<<\n" |
| else: |
| formatted_error += f"{line_prefix}{code_lines[i]}\n" |
| break |
| except (ValueError, AttributeError, IndexError): |
| pass |
| |
| |
| formatted_error += "\nFull error details:\n" |
| last_lines = error_lines[-3:] |
| formatted_error += "\n".join(last_lines) |
| |
| all_outputs.append((block_name, None, formatted_error)) |
| |
| |
| output_text = "" |
| json_outputs = context.get('json_outputs', []) |
| error_found = False |
| |
| for block_name, output, error in all_outputs: |
| if error: |
| output_text += f"\n\n=== ERROR IN {block_name.upper()}_AGENT ===\n{error}\n" |
| error_found = True |
| elif output: |
| output_text += f"\n\n=== OUTPUT FROM {block_name.upper()}_AGENT ===\n{output}\n" |
| |
| if error_found: |
| return output_text, [] |
| else: |
| return output_text, json_outputs |
| |
| |
| def format_plan_instructions(plan_instructions): |
| """ |
| Format any plan instructions (JSON string or dict) into markdown sections per agent. |
| """ |
| |
| try: |
| if isinstance(plan_instructions, str): |
| instructions = json.loads(plan_instructions) |
| elif isinstance(plan_instructions, dict): |
| instructions = plan_instructions |
| else: |
| return f"Unsupported plan instructions type: {type(plan_instructions)}" |
| except json.JSONDecodeError: |
| return f"Invalid plan instructions: {plan_instructions}" |
| |
|
|
| markdown_lines = [] |
| for agent, content in instructions.items(): |
| agent_title = agent.replace('_', ' ').title() |
| markdown_lines.append(f"#### {agent_title}") |
| if isinstance(content, dict): |
| |
| create_vals = content.get('create', []) |
| if create_vals: |
| markdown_lines.append(f"- **Create**:") |
| for item in create_vals: |
| markdown_lines.append(f" - {item}") |
| else: |
| markdown_lines.append(f"- **Create**: None") |
|
|
| |
| use_vals = content.get('use', []) |
| if use_vals: |
| markdown_lines.append(f"- **Use**:") |
| for item in use_vals: |
| markdown_lines.append(f" - {item}") |
| else: |
| markdown_lines.append(f"- **Use**: None") |
|
|
| |
| instr = content.get('instruction') |
| if isinstance(instr, str) and instr: |
| markdown_lines.append(f"- **Instruction**: {instr}") |
| else: |
| markdown_lines.append(f"- **Instruction**: None") |
| else: |
| |
| markdown_lines.append(f"- {content}") |
| markdown_lines.append("") |
|
|
| return "\n".join(markdown_lines).strip() |
| |
| def format_response_to_markdown(api_response, agent_name = None, dataframe=None): |
| try: |
| markdown = [] |
| |
|
|
| if isinstance(api_response, dict): |
| for key in api_response: |
| if "error" in api_response[key] and "litellm.RateLimitError" in api_response[key]['error'].lower(): |
| return f"**Error**: Rate limit exceeded. Please try switching models from the settings." |
| |
| |
| |
| if isinstance(api_response, dict) and "error" in api_response: |
| return f"**Error**: {api_response['error']}" |
| if "response" in api_response and isinstance(api_response['response'], str): |
| if any(err in api_response['response'].lower() for err in ["auth", "api", "lm"]): |
| return "**Error**: Authentication failed. Please check your API key in settings and try again." |
| if "model" in api_response['response'].lower(): |
| return "**Error**: Model configuration error. Please verify your model selection in settings." |
|
|
| for agent, content in api_response.items(): |
| agent = agent.split("__")[0] if "__" in agent else agent |
| if "memory" in agent or not content: |
| continue |
| |
| markdown.append(f"\n## {agent.replace('_', ' ').title()}\n") |
| |
| if agent == "analytical_planner": |
| |
| if 'plan_desc' in content: |
| markdown.append(f"### Reasoning\n{content['plan_desc']}\n") |
| if 'plan_instructions' in content: |
| markdown.append(f"### Plan Instructions\n{format_plan_instructions(content['plan_instructions'])}\n") |
| else: |
| markdown.append(f"### Reasoning\n{content['rationale']}\n") |
| else: |
| if "rationale" in content: |
| markdown.append(f"### Reasoning\n{content['rationale']}\n") |
|
|
| if 'code' in content: |
| markdown.append(f"### Code Implementation\n{format_code_backticked_block(content['code'])}\n") |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| if 'summary' in content: |
| |
| summary_lines = remove_code_block_from_summary(content['summary']) |
| summary_lines = content['summary'].split('\n') |
| |
| markdown.append("### Summary\n") |
| for line in summary_lines: |
| if line != "": |
| if line.strip().startswith('•') or line.strip().startswith('-') or line.strip().startswith('*'): |
| line = line.strip().replace('•', '').replace('-', '').replace('*', '') |
| markdown.append(f"* {line.strip()}\n") |
| else: |
| markdown.append(f"{line.strip()}\n") |
|
|
| if 'refined_complete_code' in content and 'summary' in content: |
| try: |
| if content['refined_complete_code'] is not None and content['refined_complete_code'] != "": |
| clean_code = format_code_block(content['refined_complete_code']) |
| markdown_code = format_code_backticked_block(content['refined_complete_code']) |
| output, json_outputs = execute_code_from_markdown(clean_code, dataframe) |
| elif "```python" in content['summary']: |
| clean_code = format_code_block(content['summary']) |
| markdown_code = format_code_backticked_block(content['summary']) |
| output, json_outputs = execute_code_from_markdown(clean_code, dataframe) |
| except Exception as e: |
| logger.log_message(f"Error in execute_code_from_markdown: {str(e)}", level=logging.ERROR) |
| markdown_code = f"**Error**: {str(e)}" |
| |
| |
| if markdown_code is not None: |
| markdown.append(f"### Refined Complete Code\n{markdown_code}\n") |
| |
| if output: |
| markdown.append("### Execution Output\n") |
| markdown.append(f"```output\n{output}\n```\n") |
| |
| if json_outputs: |
| markdown.append("### Plotly JSON Outputs\n") |
| for idx, json_output in enumerate(json_outputs): |
| markdown.append(f"```plotly\n{json_output}\n```\n") |
| |
| |
| |
|
|
| except Exception as e: |
| logger.log_message(f"Error in format_response_to_markdown: {str(e)}", level=logging.ERROR) |
| return f"{str(e)}" |
| |
| |
| |
| if not markdown or len(markdown) <= 1: |
| 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) |
| return "Please provide a valid query..." |
| |
| return '\n'.join(markdown) |
|
|
| |
| if __name__ == "__main__": |
| sample_response = { |
| "code_combiner_agent": { |
| "reasoning": "Sample reasoning for multiple charts.", |
| "refined_complete_code": """ |
| ```python |
| import plotly.express as px |
| import pandas as pd |
| |
| # Sample Data |
| df = pd.DataFrame({'Category': ['A', 'B', 'C'], 'Values': [10, 20, 30]}) |
| |
| # First Chart |
| fig = px.bar(df, x='Category', y='Values', title='Bar Chart') |
| fig.show() |
| |
| # Second Chart |
| fig2 = px.pie(df, values='Values', names='Category', title='Pie Chart') |
| fig2.show() |
| ``` |
| """ |
| } |
| } |
|
|
| formatted_md = format_response_to_markdown(sample_response) |