| import base64 |
| import io |
| import sys |
| from io import StringIO |
|
|
| |
| _persistent_namespace = {} |
|
|
| |
| _captured_plots = [] |
|
|
|
|
| def run_python_repl(command: str) -> str: |
| """Executes the provided Python command in a persistent environment and returns the output. |
| Variables defined in one execution will be available in subsequent executions. |
| """ |
|
|
| def execute_in_repl(command: str) -> str: |
| """Helper function to execute the command in the persistent environment.""" |
| old_stdout = sys.stdout |
| sys.stdout = mystdout = StringIO() |
|
|
| |
| global _persistent_namespace |
|
|
| try: |
| |
| _apply_matplotlib_patches() |
|
|
| |
| exec(command, _persistent_namespace) |
| output = mystdout.getvalue() |
|
|
| |
| |
|
|
| except Exception as e: |
| output = f"Error: {str(e)}" |
| finally: |
| sys.stdout = old_stdout |
| return output |
|
|
| command = command.strip("```").strip() |
| return execute_in_repl(command) |
|
|
|
|
| def _capture_matplotlib_plots(): |
| """Capture any matplotlib plots that might have been generated during execution.""" |
| global _captured_plots |
| try: |
| import matplotlib.pyplot as plt |
|
|
| |
| if plt.get_fignums(): |
| for fig_num in plt.get_fignums(): |
| fig = plt.figure(fig_num) |
|
|
| |
| buffer = io.BytesIO() |
| fig.savefig(buffer, format="png", dpi=150, bbox_inches="tight") |
| buffer.seek(0) |
|
|
| |
| image_data = base64.b64encode(buffer.getvalue()).decode("utf-8") |
| plot_data = f"data:image/png;base64,{image_data}" |
|
|
| |
| if plot_data not in _captured_plots: |
| _captured_plots.append(plot_data) |
|
|
| |
| plt.close(fig) |
|
|
| except ImportError: |
| |
| pass |
| except Exception as e: |
| print(f"Warning: Could not capture matplotlib plots: {e}") |
|
|
|
|
| def _apply_matplotlib_patches(): |
| """Apply simple monkey patches to matplotlib functions to automatically capture plots.""" |
| try: |
| import matplotlib.pyplot as plt |
|
|
| |
| if hasattr(plt, "_biomni_patched"): |
| return |
|
|
| |
| original_show = plt.show |
| original_savefig = plt.savefig |
|
|
| def show_with_capture(*args, **kwargs): |
| """Enhanced show function that captures plots before displaying them.""" |
| |
| _capture_matplotlib_plots() |
| |
| print("Plot generated and displayed") |
| |
| return original_show(*args, **kwargs) |
|
|
| def savefig_with_capture(*args, **kwargs): |
| """Enhanced savefig function that captures plots after saving them.""" |
| |
| filename = args[0] if args else kwargs.get("fname", "unknown") |
| |
| result = original_savefig(*args, **kwargs) |
| |
| _capture_matplotlib_plots() |
| |
| print(f"Plot saved to: {filename}") |
| return result |
|
|
| |
| plt.show = show_with_capture |
| plt.savefig = savefig_with_capture |
|
|
| |
| plt._biomni_patched = True |
|
|
| except ImportError: |
| |
| pass |
| except Exception as e: |
| print(f"Warning: Could not apply matplotlib patches: {e}") |
|
|
|
|
| def get_captured_plots(): |
| """Get all captured matplotlib plots.""" |
| global _captured_plots |
| return _captured_plots.copy() |
|
|
|
|
| def clear_captured_plots(): |
| """Clear all captured matplotlib plots.""" |
| global _captured_plots |
| _captured_plots = [] |
|
|
|
|
| def read_function_source_code(function_name: str) -> str: |
| """Read the source code of a function from any module path. |
| |
| Parameters |
| ---------- |
| function_name (str): Fully qualified function name (e.g., 'bioagentos.tool.support_tools.write_python_code') |
| |
| Returns |
| ------- |
| str: The source code of the function |
| |
| """ |
| import importlib |
| import inspect |
|
|
| |
| parts = function_name.split(".") |
| module_path = ".".join(parts[:-1]) |
| func_name = parts[-1] |
|
|
| try: |
| |
| module = importlib.import_module(module_path) |
|
|
| |
| function = getattr(module, func_name) |
|
|
| |
| source_code = inspect.getsource(function) |
|
|
| return source_code |
| except (ImportError, AttributeError) as e: |
| return f"Error: Could not find function '{function_name}'. Details: {str(e)}" |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
|
|
| def download_synapse_data( |
| entity_ids: str | list[str], |
| download_location: str = ".", |
| follow_link: bool = False, |
| recursive: bool = False, |
| timeout: int = 300, |
| entity_type: str = "dataset", |
| ): |
| """Download data from Synapse using entity IDs. |
| |
| Uses the synapse CLI to download files, folders, or projects from Synapse. |
| Requires SYNAPSE_AUTH_TOKEN environment variable for authentication. |
| Automatically installs synapseclient if not available. |
| |
| CRITICAL: Always check entity type from query_synapse() search results or user hints and pass the correct entity_type! |
| The default entity_type="dataset" may not be appropriate for your entity. |
| |
| IMPORTANT: Multiple entity IDs are only supported for entity_type="file". |
| For datasets, folders, and projects, only a single entity_id is supported. |
| |
| Parameters |
| ---------- |
| entity_ids : str or list of str |
| Synapse entity ID(s) to download. |
| - For files: Can be a single ID string or list of ID strings |
| - For datasets/folders/projects: Must be a single ID string only |
| download_location : str, default "." |
| Directory where files will be downloaded (current directory by default) |
| follow_link : bool, default False |
| Whether to follow links to download the linked entity |
| recursive : bool, default False |
| Whether to recursively download folders and their contents |
| ONLY valid for entity_type="folder" - ignored for other types |
| timeout : int, default 300 |
| Timeout in seconds for each download operation |
| entity_type : str, default "dataset" |
| Type of Synapse entity ("dataset", "file", "folder", "project") |
| MUST match the actual entity type from search results or user hints! |
| The default "dataset" should only be used for actual datasets. |
| Check the 'node_type' field in search results to determine correct type. |
| |
| Returns |
| ------- |
| dict |
| Dictionary containing download results and any errors |
| |
| Notes |
| ----- |
| Requires SYNAPSE_AUTH_TOKEN environment variable with your Synapse personal |
| access token for authentication. |
| |
| AGENT USAGE GUIDANCE: |
| 1. Always check the 'node_type' field from query_synapse() search results or user hints |
| 2. Pass the correct entity_type parameter matching the node_type |
| 3. Do NOT rely on the default entity_type="dataset" unless confirmed |
| 4. For multiple downloads, ensure all entities are of type "file" |
| 5. Only use recursive=True with entity_type="folder" |
| |
| Examples |
| -------- |
| # After searching with query_synapse(), check node_type and use appropriate entity_type: |
| |
| # If search result shows 'node_type': 'dataset' |
| download_synapse_data("syn123456", entity_type="dataset") |
| |
| # If search result shows 'node_type': 'file' |
| download_synapse_data("syn654321", entity_type="file") |
| |
| # If search result shows 'node_type': 'folder' |
| download_synapse_data("syn789012", entity_type="folder", recursive=True) |
| |
| # Multiple files (only if all are 'node_type': 'file') |
| download_synapse_data(["syn111", "syn222"], entity_type="file") |
| """ |
| import os |
| import subprocess |
|
|
| |
| synapse_token = os.environ.get("SYNAPSE_AUTH_TOKEN") |
| if not synapse_token: |
| return { |
| "success": False, |
| "error": "SYNAPSE_AUTH_TOKEN environment variable is required for downloading", |
| "suggestion": "Set SYNAPSE_AUTH_TOKEN with your Synapse personal access token", |
| } |
|
|
| |
| try: |
| subprocess.run(["synapse", "--version"], capture_output=True, check=True) |
| except (subprocess.CalledProcessError, FileNotFoundError): |
| try: |
| |
| print("Installing synapseclient...") |
| subprocess.run(["pip", "install", "synapseclient"], check=True) |
| print("✓ synapseclient installed successfully") |
| except subprocess.CalledProcessError as e: |
| return { |
| "success": False, |
| "error": f"Failed to install synapseclient: {e}", |
| "suggestion": "Please install manually: pip install synapseclient", |
| } |
|
|
| |
| if isinstance(entity_ids, str): |
| entity_ids = [entity_ids] |
|
|
| |
| if len(entity_ids) > 1 and entity_type != "file": |
| return { |
| "success": False, |
| "error": f"Multiple entity IDs are only supported for entity_type='file'. " |
| f"For entity_type='{entity_type}', only a single entity_id is supported.", |
| "suggestion": "Use a single entity_id string instead of a list, or change entity_type to 'file'", |
| } |
|
|
| |
| if recursive and entity_type != "folder": |
| return { |
| "success": False, |
| "error": f"recursive=True is only valid for entity_type='folder'. " |
| f"For entity_type='{entity_type}', recursive should be False.", |
| "suggestion": "Set recursive=False, or change entity_type to 'folder' if appropriate", |
| } |
|
|
| |
| os.makedirs(download_location, exist_ok=True) |
|
|
| results = [] |
| errors = [] |
|
|
| for entity_id in entity_ids: |
| try: |
| |
| if entity_type == "dataset": |
| |
| cmd = [ |
| "synapse", |
| "-p", |
| synapse_token, |
| "get", |
| "-q", |
| f"select * from {entity_id}", |
| "--downloadLocation", |
| download_location, |
| ] |
| else: |
| |
| cmd = ["synapse", "-p", synapse_token, "get", entity_id, "--downloadLocation", download_location] |
|
|
| |
| if entity_type == "folder" and recursive: |
| cmd.append("-r") |
|
|
| if follow_link: |
| cmd.append("--followLink") |
|
|
| |
| result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=timeout) |
|
|
| results.append( |
| { |
| "entity_id": entity_id, |
| "success": True, |
| "stdout": result.stdout, |
| "download_location": download_location, |
| } |
| ) |
|
|
| except subprocess.CalledProcessError as e: |
| error_msg = f"Failed to download {entity_id}: {e.stderr if e.stderr else str(e)}" |
| errors.append(error_msg) |
| results.append({"entity_id": entity_id, "success": False, "error": error_msg}) |
| except subprocess.TimeoutExpired: |
| error_msg = f"Download timeout for {entity_id} (>{timeout} seconds)" |
| errors.append(error_msg) |
| results.append({"entity_id": entity_id, "success": False, "error": error_msg}) |
|
|
| |
| successful_downloads = [r for r in results if r["success"]] |
| failed_downloads = [r for r in results if not r["success"]] |
|
|
| return { |
| "success": len(failed_downloads) == 0, |
| "total_requested": len(entity_ids), |
| "successful": len(successful_downloads), |
| "failed": len(failed_downloads), |
| "download_location": download_location, |
| "results": results, |
| "errors": errors if errors else None, |
| } |
|
|