| 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=logging.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 |
| |
| |
| SENSITIVE_MODULES = re.compile(r"(os|sys|subprocess|dotenv|requests|http|socket|smtplib|ftplib|telnetlib|paramiko)") |
| IMPORT_PATTERN = re.compile(r"^\s*import\s+(" + SENSITIVE_MODULES.pattern + r").*?(\n|$)", re.MULTILINE) |
| FROM_IMPORT_PATTERN = re.compile(r"^\s*from\s+(" + SENSITIVE_MODULES.pattern + r").*?(\n|$)", re.MULTILINE) |
| DYNAMIC_IMPORT_PATTERN = re.compile(r"__import__\s*\(\s*['\"](" + SENSITIVE_MODULES.pattern + r")['\"].*?\)") |
| ENV_ACCESS_PATTERN = re.compile(r"(os\.getenv|os\.environ|load_dotenv|\.__import__\s*\(\s*['\"]os['\"].*?\.environ)") |
| FILE_ACCESS_PATTERN = re.compile(r"(open\(|read\(|write\(|file\(|with\s+open)") |
|
|
| |
| API_KEY_PATTERNS = [ |
| |
| re.compile(r"(?i)(api_?key|access_?token|secret_?key|auth_?token|password|credential|secret)s?\s*=\s*[\"\'][\w\-\+\/\=]{8,}[\"\']"), |
| |
| re.compile(r"(?i)\.set_api_key\(\s*[\"\'][\w\-\+\/\=]{8,}[\"\']"), |
| |
| re.compile(r"(?i)['\"](?:api_?key|access_?token|secret_?key|auth_?token|password|credential|secret)['\"](?:\s*:\s*)[\"\'][\w\-\+\/\=]{8,}[\"\']"), |
| |
| re.compile(r"[\"\'](?:[A-Za-z0-9\+\/\=]{32,}|[0-9a-fA-F]{32,})[\"\']"), |
| |
| re.compile(r"[\"\'](Bearer\s+[\w\-\+\/\=]{8,})[\"\']"), |
| |
| re.compile(r"https?:\/\/[\w\-\+\/\=]{8,}@") |
| ] |
|
|
| |
| NETWORK_REQUEST_PATTERNS = re.compile(r"(requests\.|urllib\.|http\.client|httpx\.|socket\.connect\()") |
|
|
| |
|
|
| def check_security_concerns(code_str, dataset_names): |
| """Check code for security concerns and return info about what was found""" |
| security_concerns = { |
| "has_concern": False, |
| "blocked_imports": False, |
| "blocked_dynamic_imports": False, |
| "blocked_env_access": False, |
| "blocked_file_access": False, |
| "blocked_api_keys": False, |
| "blocked_network": False, |
| "blocked_dataframe_invention": False, |
| "messages": [] |
| } |
|
|
| dataset_names_pattern = "|".join(re.escape(name) for name in dataset_names) |
| DATAFRAME_INVENTION_PATTERN = re.compile( |
| rf"({dataset_names_pattern})\s*=\s*pd\.DataFrame\s*\(\s*\{{\s*[^}}]*\}}", |
| re.MULTILINE |
| ) |
| if DATAFRAME_INVENTION_PATTERN.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_dataframe_invention"] = True |
| security_concerns["messages"].append(f"DataFrame creation blocked for dataset variables: {', '.join(dataset_names)}") |
| |
| |
| if IMPORT_PATTERN.search(code_str) or FROM_IMPORT_PATTERN.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_imports"] = True |
| security_concerns["messages"].append("Sensitive module imports blocked") |
| |
| |
| if DYNAMIC_IMPORT_PATTERN.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_dynamic_imports"] = True |
| security_concerns["messages"].append("Dynamic import of sensitive modules blocked") |
| |
| |
| if ENV_ACCESS_PATTERN.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_env_access"] = True |
| security_concerns["messages"].append("Environment variables access blocked") |
| |
| |
| if FILE_ACCESS_PATTERN.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_file_access"] = True |
| security_concerns["messages"].append("File operations blocked") |
| |
| |
| for pattern in API_KEY_PATTERNS: |
| if pattern.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_api_keys"] = True |
| security_concerns["messages"].append("API key/token usage blocked") |
| break |
| |
| |
| if NETWORK_REQUEST_PATTERNS.search(code_str): |
| security_concerns["has_concern"] = True |
| security_concerns["blocked_network"] = True |
| security_concerns["messages"].append("Network requests blocked") |
| |
| |
| |
| return security_concerns |
|
|
| def clean_code_for_security(code_str, security_concerns, dataset_names): |
| """Apply security modifications to the code based on detected concerns""" |
|
|
| modified_code = code_str |
| dataset_names_pattern = "|".join(re.escape(name) for name in dataset_names) |
| DATAFRAME_INVENTION_PATTERN = re.compile( |
| rf"({dataset_names_pattern})\s*=\s*pd\.DataFrame\s*\(\s*\{{\s*[^}}]*\}}", |
| re.MULTILINE |
| ) |
| |
| |
| if security_concerns["blocked_imports"]: |
| modified_code = IMPORT_PATTERN.sub(r'# BLOCKED: import \1\n', modified_code) |
| modified_code = FROM_IMPORT_PATTERN.sub(r'# BLOCKED: from \1\n', modified_code) |
| |
| |
| if security_concerns["blocked_dynamic_imports"]: |
| modified_code = DYNAMIC_IMPORT_PATTERN.sub(r'"BLOCKED_DYNAMIC_IMPORT"', modified_code) |
| |
| |
| if security_concerns["blocked_env_access"]: |
| modified_code = ENV_ACCESS_PATTERN.sub(r'"BLOCKED_ENV_ACCESS"', modified_code) |
| |
| |
| if security_concerns["blocked_file_access"]: |
| modified_code = FILE_ACCESS_PATTERN.sub(r'"BLOCKED_FILE_ACCESS"', modified_code) |
| |
| |
| if security_concerns["blocked_api_keys"]: |
| for pattern in API_KEY_PATTERNS: |
| modified_code = pattern.sub(r'"BLOCKED_API_KEY"', modified_code) |
| |
| |
| if security_concerns["blocked_network"]: |
| modified_code = NETWORK_REQUEST_PATTERNS.sub(r'"BLOCKED_NETWORK_REQUEST"', modified_code) |
| |
| |
| if security_concerns["blocked_dataframe_invention"]: |
| modified_code = DATAFRAME_INVENTION_PATTERN.sub( |
| r"# BLOCKED_DATAFRAME_INVENTION: \g<0>", |
| modified_code |
| ) |
| |
| |
| if security_concerns["has_concern"]: |
| security_message = "⚠️ SECURITY WARNING: " + ". ".join(security_concerns["messages"]) + "." |
| modified_code = f"print('{security_message}')\n\n" + modified_code |
| |
| return modified_code |
| |
| def format_correlation_output(text): |
| """Format correlation matrix output for better readability""" |
| lines = text.split('\n') |
| formatted_lines = [] |
| |
| for line in lines: |
| |
| if not line.strip() and not formatted_lines: |
| continue |
| |
| if not line.strip(): |
| formatted_lines.append(line) |
| continue |
| |
| |
| stripped_line = line.strip() |
| parts = stripped_line.split() |
| |
| if len(parts) > 1: |
| |
| if all(part.replace('_', '').replace('-', '').isalpha() for part in parts): |
| |
| formatted_header = f"{'':12}" |
| for part in parts: |
| formatted_header += f"{part:>12}" |
| formatted_lines.append(formatted_header) |
| elif any(char.isdigit() for char in stripped_line) and ('.' in stripped_line or '-' in stripped_line): |
| |
| row_name = parts[0] if parts else "" |
| values = parts[1:] if len(parts) > 1 else [] |
| |
| formatted_row = f"{row_name:<12}" |
| for value in values: |
| try: |
| val = float(value) |
| formatted_row += f"{val:>12.3f}" |
| except ValueError: |
| formatted_row += f"{value:>12}" |
| |
| formatted_lines.append(formatted_row) |
| else: |
| |
| formatted_lines.append(line) |
| else: |
| formatted_lines.append(line) |
| |
| return '\n'.join(formatted_lines) |
|
|
| def format_summary_stats(text): |
| """Format summary statistics for better readability""" |
| lines = text.split('\n') |
| formatted_lines = [] |
| |
| for line in lines: |
| if not line.strip(): |
| formatted_lines.append(line) |
| continue |
| |
| |
| stripped_line = line.strip() |
| if any(stat in stripped_line.lower() for stat in ['count', 'mean', 'median', 'std', 'min', 'max', '25%', '50%', '75%']): |
| parts = stripped_line.split() |
| |
| if parts and parts[0].lower() in ['count', 'mean', 'median', 'std', 'min', 'max', '25%', '50%', '75%']: |
| |
| formatted_header = f"{'':12}" |
| for part in parts: |
| formatted_header += f"{part:>15}" |
| formatted_lines.append(formatted_header) |
| else: |
| |
| row_name = parts[0] if parts else "" |
| values = parts[1:] if len(parts) > 1 else [] |
| |
| formatted_row = f"{row_name:<12}" |
| for value in values: |
| try: |
| if '.' in value or 'e' in value.lower(): |
| val = float(value) |
| if abs(val) >= 1000000: |
| formatted_row += f"{val:>15.2e}" |
| elif abs(val) >= 1: |
| formatted_row += f"{val:>15.2f}" |
| else: |
| formatted_row += f"{val:>15.6f}" |
| else: |
| val = int(value) |
| formatted_row += f"{val:>15}" |
| except ValueError: |
| formatted_row += f"{value:>15}" |
| |
| formatted_lines.append(formatted_row) |
| else: |
| |
| formatted_lines.append(line) |
| |
| return '\n'.join(formatted_lines) |
| |
| 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): |
| |
| if code_str is None: |
| return "```python\n# No code available\n```" |
| |
| |
| if not isinstance(code_str, str): |
| return f"```python\n# Invalid code type: {type(code_str)}\n```" |
| |
| 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'^(\s*)(df\s*=.*)$', r'\1# \2', code_clean, flags=re.MULTILINE) |
| |
| |
| |
| 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, datasets=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, BytesIO |
| import base64 |
|
|
| context_names = list(datasets.keys()) |
| |
| security_concerns = check_security_concerns(code_str, context_names) |
| |
| |
| modified_code = clean_code_for_security(code_str, security_concerns, context_names) |
| |
| |
| captured_outputs = [] |
| original_print = print |
| |
| |
| pd.set_option('display.max_columns', None) |
| pd.set_option('display.max_rows', 20) |
| pd.set_option('display.width', None) |
| pd.set_option('display.max_colwidth', 50) |
| pd.set_option('display.expand_frame_repr', False) |
| |
|
|
| |
| def enhanced_print(*args, **kwargs): |
| |
| str_args = [str(arg) for arg in args] |
| output_text = kwargs.get('sep', ' ').join(str_args) |
| |
| |
| if isinstance(args[0], pd.DataFrame) and len(args) == 1: |
| |
| df = args[0] |
| |
| |
| from io import StringIO |
| csv_buffer = StringIO() |
| |
| |
| df.to_csv(csv_buffer, sep='|', index=True, float_format='%.6g') |
| csv_output = csv_buffer.getvalue() |
| |
| |
| lines = csv_output.strip().split('\n') |
| cleaned_lines = [] |
| |
| for line in lines: |
| |
| clean_line = line.replace('"', '') |
| |
| parts = [part.strip() for part in clean_line.split('|')] |
| cleaned_lines.append(' | '.join(parts)) |
| |
| output_text = '\n'.join(cleaned_lines) |
| captured_outputs.append(f"<TABLE_START>\n{output_text}\n<TABLE_END>") |
| original_print(output_text) |
| return |
| |
| |
| is_table = False |
| |
| |
| |
| lines = output_text.split('\n') |
| if len(lines) > 2: |
| |
| multi_column_lines = sum(1 for line in lines if len(line.split()) > 1 and ' ' in line) |
| if multi_column_lines >= 2: |
| is_table = True |
| |
| |
| if any(re.search(r'^\s*\d+\s+', line) for line in lines): |
| |
| is_table = True |
| |
| |
| if len(lines) >= 3: |
| |
| sample_lines = [lines[i] for i in range(min(len(lines), 5)) if i < len(lines) and lines[i].strip()] |
| |
| |
| if len(sample_lines) >= 2: |
| |
| whitespace_positions = [] |
| for i, line in enumerate(sample_lines): |
| if not line.strip(): |
| continue |
| positions = [m.start() for m in re.finditer(r'\s{2,}', line)] |
| if i == 0: |
| whitespace_positions = positions |
| elif len(positions) == len(whitespace_positions): |
| |
| is_similar = all(abs(pos - whitespace_positions[j]) <= 3 |
| for j, pos in enumerate(positions) |
| if j < len(whitespace_positions)) |
| if is_similar: |
| is_table = True |
| |
| |
| if any(indicator in output_text.lower() for indicator in [ |
| 'count', 'mean', 'std', 'min', 'max', '25%', '50%', '75%', |
| 'correlation', 'corr', |
| 'coefficient', 'r-squared', 'p-value', |
| ]): |
| is_table = True |
| |
| |
| if output_text.count('.') > 5 and len(lines) > 2: |
| is_table = True |
| |
| |
| if is_table: |
| |
| formatted_lines = [] |
| for line in lines: |
| if not line.strip(): |
| formatted_lines.append(line) |
| continue |
| |
| |
| parts = re.split(r'\s{2,}', line.strip()) |
| if parts: |
| formatted_lines.append(" | ".join(parts)) |
| else: |
| formatted_lines.append(line) |
| |
| |
| output_text = "\n".join(formatted_lines) |
| |
| |
| captured_outputs.append(f"<TABLE_START>\n{output_text}\n<TABLE_END>") |
| else: |
| captured_outputs.append(output_text) |
| |
| |
| original_print(*args, **kwargs) |
|
|
| |
| def capture_matplotlib_chart(): |
| """Capture current matplotlib figure as base64 encoded image""" |
| try: |
| fig = plt.gcf() |
| if fig.get_axes(): |
| buffer = BytesIO() |
| fig.savefig(buffer, format='png', dpi=150, bbox_inches='tight', |
| facecolor='white', edgecolor='none') |
| buffer.seek(0) |
| img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') |
| buffer.close() |
| plt.close(fig) |
| return img_base64 |
| return None |
| except Exception: |
| return None |
|
|
| |
| original_plt_show = plt.show |
| |
| def custom_plt_show(*args, **kwargs): |
| """Custom plt.show that captures the chart instead of displaying it""" |
| img_base64 = capture_matplotlib_chart() |
| if img_base64: |
| matplotlib_outputs.append(img_base64) |
| |
| |
| context = { |
| 'pd': pd, |
| 'px': px, |
| 'go': go, |
| 'plt': plt, |
| 'plotly': plotly, |
| '__builtins__': __builtins__, |
| '__import__': __import__, |
| 'sns': sns, |
| 'np': np, |
| 'json_outputs': [], |
| 'matplotlib_outputs': [], |
| 'print': enhanced_print |
| } |
| |
| |
| matplotlib_outputs = context['matplotlib_outputs'] |
| |
| |
| plt.show = custom_plt_show |
| |
| |
|
|
| |
| modified_code = re.sub( |
| r'(\w*_?)fig(\w*)\.show\(\)', |
| r'json_outputs.append(plotly.io.to_json(\1fig\2, pretty=True))', |
| modified_code |
| ) |
|
|
| 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 |
| ) |
| |
|
|
| |
| original_repr = pd.DataFrame.__repr__ |
| |
| def custom_df_repr(self): |
| if len(self) > 15: |
| |
| head_part = self.head(10) |
| tail_part = self.tail(5) |
| |
| head_str = head_part.__repr__() |
| tail_str = tail_part.__repr__() |
| |
| |
| tail_lines = tail_str.split('\n') |
| tail_data = '\n'.join(tail_lines[1:]) |
| |
| return f"{head_str}\n...\n{tail_data}" |
| else: |
| return original_repr(self) |
| |
| |
| pd.DataFrame.__repr__ = custom_df_repr |
|
|
| |
| for dataset_name, dataset_df in datasets.items(): |
| if dataset_df is not None: |
| context[dataset_name] = dataset_df |
| logger.log_message(f"Added dataset '{dataset_name}' to execution context", level=logging.DEBUG) |
|
|
|
|
| |
| 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\.savefig\([^)]*\)', 'plt.show()', modified_code) |
| |
| |
| |
| |
| seaborn_plot_functions = [ |
| 'sns.scatterplot', 'sns.lineplot', 'sns.barplot', 'sns.boxplot', 'sns.violinplot', |
| 'sns.stripplot', 'sns.swarmplot', 'sns.pointplot', 'sns.catplot', 'sns.relplot', |
| 'sns.displot', 'sns.histplot', 'sns.kdeplot', 'sns.ecdfplot', 'sns.rugplot', |
| 'sns.distplot', 'sns.jointplot', 'sns.pairplot', 'sns.FacetGrid', 'sns.PairGrid', |
| 'sns.heatmap', 'sns.clustermap', 'sns.regplot', 'sns.lmplot', 'sns.residplot' |
| ] |
| |
| |
| for func in seaborn_plot_functions: |
| pattern = rf'({re.escape(func)}\([^)]*\)(?:\.[^(]*\([^)]*\))*)' |
| def add_show(match): |
| plot_call = match.group(1) |
| |
| return f'{plot_call}\nplt.show()' |
| |
| modified_code = re.sub(pattern, add_show, 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: |
| |
| captured_outputs.clear() |
| |
| |
| try: |
| block_code = textwrap.dedent(block_code) |
| except Exception as dedent_error: |
| logger.log_message(f"Failed to dedent code block '{block_name}': {str(dedent_error)}", level=logging.WARNING) |
| |
| with stdoutIO() as s: |
| exec(block_code, context) |
| |
| |
| stdout_output = s.getvalue() |
| |
| |
| if captured_outputs: |
| combined_output = '\n'.join(captured_outputs) |
| else: |
| combined_output = stdout_output |
| |
| all_outputs.append((block_name, combined_output, None)) |
| except Exception as e: |
| |
| pd.reset_option('display.max_columns') |
| pd.reset_option('display.max_rows') |
| pd.reset_option('display.width') |
| pd.reset_option('display.max_colwidth') |
| pd.reset_option('display.expand_frame_repr') |
| |
| |
| pd.DataFrame.__repr__ = original_repr |
| |
| |
| plt.show = original_plt_show |
| |
| 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)) |
| |
| |
| pd.reset_option('display.max_columns') |
| pd.reset_option('display.max_rows') |
| pd.reset_option('display.width') |
| pd.reset_option('display.max_colwidth') |
| pd.reset_option('display.expand_frame_repr') |
| |
| |
| pd.DataFrame.__repr__ = original_repr |
| |
| |
| plt.show = original_plt_show |
| |
| |
| output_text = "" |
| json_outputs = context.get('json_outputs', []) |
| matplotlib_outputs = context.get('matplotlib_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, matplotlib_outputs |
| |
| |
| def format_plan_instructions(plan_instructions): |
| """ |
| Format any plan instructions (JSON string or dict) into markdown sections per agent. |
| """ |
| |
|
|
| if "basic_qa_agent" in str(plan_instructions): |
| return "**Non-Data Request**: Please ask a data related query, don't waste credits!" |
|
|
|
|
| try: |
| if isinstance(plan_instructions, str): |
| try: |
| instructions = json.loads(plan_instructions) |
| except json.JSONDecodeError as e: |
| |
| cleaned_str = plan_instructions.strip() |
| if cleaned_str.startswith("'") and cleaned_str.endswith("'"): |
| cleaned_str = cleaned_str[1:-1] |
| try: |
| instructions = json.loads(cleaned_str) |
| except json.JSONDecodeError: |
| raise ValueError(f"Invalid JSON format in plan instructions: {str(e)}") |
| elif isinstance(plan_instructions, dict): |
| instructions = plan_instructions |
| else: |
| raise TypeError(f"Unsupported plan instructions type: {type(plan_instructions)}") |
| except Exception as e: |
| raise ValueError(f"Error processing plan instructions: {str(e)} + {dspy.settings.lm} ") |
| |
|
|
|
|
|
|
| markdown_lines = [] |
| for agent, content in instructions.items(): |
| if agent != 'basic_qa_agent': |
| 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("") |
| else: |
| markdown_lines.append(f"**Non-Data Request**: {content.get('instruction')}") |
|
|
| return "\n".join(markdown_lines).strip() |
| |
| |
| return "" |
|
|
| def format_complexity(instructions): |
| markdown_lines = [] |
| complexity = None |
| |
| |
| if isinstance(instructions, dict): |
| |
| if 'complexity' in instructions: |
| complexity = instructions['complexity'] |
| |
| elif 'plan' in instructions and isinstance(instructions['plan'], dict): |
| if 'complexity' in instructions['plan']: |
| complexity = instructions['plan']['complexity'] |
| else: |
| complexity = "unrelated" |
| |
| |
| if 'plan' in instructions and isinstance(instructions['plan'], str) and "basic_qa_agent" in instructions['plan']: |
| complexity = "unrelated" |
| else: |
| |
| complexity = "unrelated" |
| |
| if complexity: |
| |
| color_map = { |
| "unrelated": "#FFB6B6", |
| "basic": "#FF9E9E", |
| "intermediate": "#FF7F7F", |
| "advanced": "#FF5F5F" |
| } |
| |
| indicator_map = { |
| "unrelated": "○", |
| "basic": "●", |
| "intermediate": "●●", |
| "advanced": "●●●" |
| } |
| |
| color = color_map.get(complexity.lower(), "#FFB6B6") |
| indicator = indicator_map.get(complexity.lower(), "○") |
| |
| |
| 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") |
|
|
| return "\n".join(markdown_lines).strip() |
| |
| |
| return "" |
|
|
| def format_response_to_markdown(api_response, agent_name = None, datasets=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 |
| |
| if "complexity" in content: |
| complexity_result = format_complexity(content) |
| if complexity_result: |
| markdown.append(f"{complexity_result}") |
| |
| markdown.append(f"\n## {agent.replace('_', ' ').title()}\n") |
| |
| if agent == "analytical_planner": |
| logger.log_message(f"Analytical planner content: {content}", level=logging.INFO) |
| if 'plan_desc' in content and content['plan_desc']: |
| markdown.append(f"### Reasoning {content['plan_desc']}") |
| if 'plan_instructions' in content: |
| plan_result = format_plan_instructions(content['plan_instructions']) |
| if plan_result: |
| markdown.append(f"{plan_result}") |
| else: |
| if content.get('rationale'): |
| markdown.append(f"### Reasoning {content['rationale']}") |
| else: |
| if "rationale" in content: |
| if content.get('rationale'): |
| markdown.append(f"### Reasoning{content['rationale']}") |
|
|
| if 'code' in content and content['code'] is not None: |
| formatted_code = format_code_backticked_block(content['code']) |
| if formatted_code: |
| markdown.append(f"### Code Implementation\n{formatted_code}\n") |
| if 'answer' in content and content['answer']: |
| markdown.append(f"### Answer{content['answer']} Please ask a query about the data") |
| if 'summary' in content: |
| import re |
| summary_text = content['summary'] |
| summary_text = re.sub(r'```python\n(.*?)\n```', '', summary_text, flags=re.DOTALL) |
|
|
| markdown.append("### Summary\n") |
|
|
| |
| intro_match = re.split(r'\(\d+\)', summary_text, maxsplit=1) |
| if len(intro_match) > 1: |
| intro_text = intro_match[0].strip() |
| rest_text = "(1)" + intro_match[1] |
| else: |
| intro_text = summary_text.strip() |
| rest_text = "" |
|
|
| if intro_text: |
| markdown.append(f"{intro_text}\n") |
|
|
| |
| bullets = re.split(r'\(\d+\)', rest_text) |
| bullets = [b.strip(" ,.\n") for b in bullets if b.strip()] |
|
|
| |
| for i, bullet in enumerate(bullets): |
| markdown.append(f"* {bullet}\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, matplotlib_outputs = execute_code_from_markdown(clean_code, datasets) |
| elif "```python" in content['summary']: |
| clean_code = format_code_block(content['summary']) |
| markdown_code = format_code_backticked_block(content['summary']) |
| output, json_outputs, matplotlib_outputs = execute_code_from_markdown(clean_code, datasets) |
| 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)}" |
| output = None |
| json_outputs = [] |
| matplotlib_outputs = [] |
| |
| |
| 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") |
| |
| if matplotlib_outputs: |
| markdown.append("### Matplotlib/Seaborn Charts\n") |
| for idx, img_base64 in enumerate(matplotlib_outputs): |
| markdown.append(f"```matplotlib\n{img_base64}\n```\n") |
| |
| |
| |
|
|
| except Exception as e: |
| logger.log_message(f"Error in format_response_to_markdown: {str(e)}", level=logging.ERROR) |
| return f"error formating markdown {str(e)}" |
| |
| |
| |
| if not markdown or len(markdown) <= 1: |
| logger.log_message( |
| f"Invalid markdown content for agent '{agent_name}' at {time.strftime('%Y-%m-%d %H:%M:%S')}: " |
| f"Content: '{markdown}', Type: {type(markdown)}, Length: {len(markdown) if markdown else 0}, " |
| f"API Response: {api_response}", |
| level=logging.ERROR |
| ) |
| return "" |
| |
| return '\n'.join(markdown) |
|
|
|
|
|
|