czty commited on
Commit
ef16689
·
verified ·
1 Parent(s): a9f1600

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .env.example +22 -0
  2. .gitignore +234 -0
  3. .pre-commit-config.yaml +41 -0
  4. CONTRIBUTION.md +129 -0
  5. DETAILS.md +364 -0
  6. LICENSE +201 -0
  7. MANIFEST.in +27 -0
  8. README.md +420 -0
  9. biomni/__init__.py +3 -0
  10. biomni/config.py +99 -0
  11. biomni/env_desc.py +221 -0
  12. biomni/env_desc_cm.py +219 -0
  13. biomni/llm.py +275 -0
  14. biomni/utils.py +2366 -0
  15. biomni/version.py +1 -0
  16. biomni_env/README.md +74 -0
  17. biomni_env/bio_env.yml +70 -0
  18. biomni_env/bio_env_py310.yml +19 -0
  19. biomni_env/cli_tools_config.json +94 -0
  20. biomni_env/environment.yml +39 -0
  21. biomni_env/fixed_env.yml +742 -0
  22. biomni_env/install_cli_tools.sh +912 -0
  23. biomni_env/install_r_packages.R +132 -0
  24. biomni_env/new_software_v008.sh +18 -0
  25. biomni_env/r_packages.yml +7 -0
  26. biomni_env/setup.sh +232 -0
  27. docs/Makefile +28 -0
  28. docs/building_documentation.md +36 -0
  29. docs/configuration.md +147 -0
  30. docs/generate_api_docs.py +124 -0
  31. docs/known_conflicts.md +34 -0
  32. docs/make.bat +35 -0
  33. docs/mcp_integration.md +347 -0
  34. experiments/bioagent_bench/README.md +102 -0
  35. experiments/bioagent_bench/README_RUN.md +208 -0
  36. experiments/bioagent_bench/configs/mcp_scale_100.yaml +0 -0
  37. experiments/bioagent_bench/configs/mcp_scale_manifest.json +1329 -0
  38. experiments/bioagent_bench/evaluate_bioagent_bench.py +1198 -0
  39. experiments/bioagent_bench/gold_tools.json +553 -0
  40. experiments/lab_bench/README_RUN.md +289 -0
  41. experiments/lab_bench/__pycache__/summarize_labbench_repeats.cpython-310.pyc +0 -0
  42. experiments/lab_bench/__pycache__/summarize_labbench_repeats.cpython-313.pyc +0 -0
  43. experiments/lab_bench/run_all_labbench_with_biomni.sh +9 -0
  44. experiments/lab_bench/run_dbqa_biomni_experiment.sh +129 -0
  45. experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh +37 -0
  46. experiments/lab_bench/run_seqqa_biomni_experiment.sh +129 -0
  47. figs/biomni_logo.png +0 -0
  48. license_info.md +52 -0
  49. pyproject.toml +105 -0
  50. tutorials/biomni_101.ipynb +1070 -0
.env.example ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni Environment Configuration
2
+ # Copy this file to .env and fill in your actual API keys
3
+
4
+ # Required: Anthropic API Key for Claude models
5
+ ANTHROPIC_API_KEY=your_anthropic_api_key_here
6
+
7
+ # Optional: OpenAI API Key (if using OpenAI models)
8
+ OPENAI_API_KEY=your_openai_api_key_here
9
+
10
+ # Optional: AWS Bedrock Configuration (if using AWS Bedrock models)
11
+ AWS_BEARER_TOKEN_BEDROCK=your_bedrock_api_key_here
12
+ AWS_REGION=us-east-1
13
+
14
+ # Optional: Custom model serving configuration
15
+ # CUSTOM_MODEL_BASE_URL=http://localhost:8000/v1
16
+ # CUSTOM_MODEL_API_KEY=your_custom_api_key_here
17
+
18
+ # Optional: Biomni data path (defaults to ./data)
19
+ # BIOMNI_DATA_PATH=/path/to/your/data
20
+
21
+ # Optional: Timeout settings
22
+ # BIOMNI_TIMEOUT_SECONDS=600
.gitignore ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .gitignore
2
+ .buildplan
3
+ .env
4
+ debug*
5
+ .*
6
+ # Byte-compiled / optimized / DLL files
7
+ biomni_release/biomni_env/biomni_tools/
8
+ open_source_process.ipynb
9
+ biomni_env/biomni_tools/*
10
+ .gradio/
11
+ *xlsx
12
+ scripts/*.png
13
+ notebooks/figures/*
14
+ notebooks/tmp_dir/*
15
+ notebooks/tmp_directory/*
16
+ notebooks/save_folder/*
17
+ notebooks/cache
18
+ notebooks/\[draft*
19
+ notebooks/\[scratch*
20
+ notebooks/labbench/*
21
+ scripts/save_folder/*
22
+ scripts/\[draft*
23
+ scripts/tmp_dir/*
24
+ scripts/tmp_directory/*
25
+ scripts/figures/*
26
+ */scripts/outputs/*
27
+ */scripts/wandb/*
28
+ */results/*
29
+ bioagentos/agent/popper_agent.py
30
+ bioagentos/scripts/biorxiv_results
31
+ bioagentos/tool/data_tool/old_data_tool/*
32
+ examples/*.png
33
+ examples/*.csv
34
+ examples/*.fa
35
+ scratch/
36
+ packages/
37
+ data
38
+ dataset
39
+ logs
40
+ scripts/gradio
41
+ data_lake
42
+ __pycache__/
43
+ *.py[cod]
44
+ *$py.class
45
+ res/
46
+ examples/tmp*
47
+ bioagentos/task/lab_bench.py
48
+ *database/
49
+ .history/
50
+
51
+ temp.*
52
+
53
+ scripts/analysis/all_tools.txt
54
+
55
+ BIOMNI_GRADIO/biomni/tool/files/*
56
+
57
+
58
+ # C extensions
59
+ *.so
60
+
61
+ # Distribution / packaging
62
+ .Python
63
+ build/
64
+ develop-eggs/
65
+ dist/
66
+ downloads/
67
+ eggs/
68
+ .eggs/
69
+ lib/
70
+ lib64/
71
+ parts/
72
+ sdist/
73
+ var/
74
+ wheels/
75
+ share/python-wheels/
76
+ *.egg-info/
77
+ .installed.cfg
78
+ *.egg
79
+ MANIFEST
80
+
81
+ # PyInstaller
82
+ # Usually these files are written by a python script from a template
83
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
84
+ *.manifest
85
+ *.spec
86
+
87
+ # Installer logs
88
+ pip-log.txt
89
+ pip-delete-this-directory.txt
90
+
91
+ # Unit test / coverage reports
92
+ htmlcov/
93
+ .tox/
94
+ .nox/
95
+ .coverage
96
+ .coverage.*
97
+ .cache
98
+ nosetests.xml
99
+ coverage.xml
100
+ *.cover
101
+ *.py,cover
102
+ .hypothesis/
103
+ .pytest_cache/
104
+ cover/
105
+ test.ipynb
106
+
107
+ # Translations
108
+ *.mo
109
+ *.pot
110
+
111
+ # Django stuff:
112
+ *.log
113
+ local_settings.py
114
+ db.sqlite3
115
+ db.sqlite3-journal
116
+
117
+ # Flask stuff:
118
+ instance/
119
+ .webassets-cache
120
+
121
+ # Scrapy stuff:
122
+ .scrapy
123
+
124
+ # Sphinx documentation
125
+ docs/_build/
126
+
127
+ # PyBuilder
128
+ .pybuilder/
129
+ target/
130
+
131
+ # Jupyter Notebook
132
+ .ipynb_checkpoints
133
+
134
+ # IPython
135
+ profile_default/
136
+ ipython_config.py
137
+
138
+ # pyenv
139
+ # For a library or package, you might want to ignore these files since the code is
140
+ # intended to run in multiple environments; otherwise, check them in:
141
+ # .python-version
142
+
143
+ # pipenv
144
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
145
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
146
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
147
+ # install all needed dependencies.
148
+ #Pipfile.lock
149
+
150
+ # poetry
151
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
152
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
153
+ # commonly ignored for libraries.
154
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
155
+ #poetry.lock
156
+
157
+ # pdm
158
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
159
+ #pdm.lock
160
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
161
+ # in version control.
162
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
163
+ .pdm.toml
164
+ .pdm-python
165
+ .pdm-build/
166
+
167
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
168
+ __pypackages__/
169
+
170
+ # Celery stuff
171
+ celerybeat-schedule
172
+ celerybeat.pid
173
+
174
+ # SageMath parsed files
175
+ *.sage.py
176
+
177
+ # Environments
178
+ .env
179
+ .venv
180
+ env/
181
+ venv/
182
+ ENV/
183
+ env.bak/
184
+ venv.bak/
185
+ .envrc
186
+ uv.lock
187
+
188
+ # Spyder project settings
189
+ .spyderproject
190
+ .spyproject
191
+
192
+ # Rope project settings
193
+ .ropeproject
194
+
195
+ # mkdocs documentation
196
+ /site
197
+
198
+ # mypy
199
+ .mypy_cache/
200
+ .dmypy.json
201
+ dmypy.json
202
+
203
+ *.jsonl
204
+
205
+ # Pyre type checker
206
+ .pyre/
207
+
208
+ # pytype static type analyzer
209
+ .pytype/
210
+
211
+ # Cython debug symbols
212
+ cython_debug/
213
+
214
+ # macOS system files
215
+ .DS_Store
216
+ **/.DS_Store
217
+
218
+ # DDInter data files
219
+ biomni/tool/schema_db/ddinter_*.pkl
220
+ data/ddinter_raw/
221
+
222
+ # PyCharm
223
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
224
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
225
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
226
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
227
+ #.idea/
228
+
229
+ # VSCode
230
+ /.vscode
231
+
232
+ # Sphinx build
233
+ /docs/build/
234
+ /docs/source/api/
.pre-commit-config.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fail_fast: false
2
+ default_language_version:
3
+ python: python3
4
+ default_stages:
5
+ - pre-commit
6
+ - pre-push
7
+ minimum_pre_commit_version: 2.16.0
8
+ repos:
9
+ - repo: https://github.com/biomejs/pre-commit
10
+ rev: v2.3.10
11
+ hooks:
12
+ - id: biome-format
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.14.11
15
+ hooks:
16
+ - id: ruff-check
17
+ args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]
18
+ - id: ruff-format
19
+ - repo: https://github.com/pre-commit/pre-commit-hooks
20
+ rev: v6.0.0
21
+ hooks:
22
+ - id: detect-private-key
23
+ - id: check-ast
24
+ - id: end-of-file-fixer
25
+ - id: mixed-line-ending
26
+ args: [--fix=lf]
27
+ - id: trailing-whitespace
28
+ - id: check-case-conflict
29
+ - id: check-added-large-files
30
+ - id: check-toml
31
+ - id: check-yaml
32
+ - id: check-merge-conflict
33
+ - id: no-commit-to-branch
34
+ args: ["--branch=main"]
35
+ #- repo: https://github.com/pre-commit/mirrors-mypy
36
+ # rev: v1.16.1
37
+ # hooks:
38
+ # - id: mypy
39
+ # args: [--no-strict-optional, --ignore-missing-imports]
40
+ # additional_dependencies:
41
+ # ["types-setuptools", "types-requests", "types-attrs"]
CONTRIBUTION.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to Biomni
2
+
3
+ Thank you for your interest in contributing to Biomni! We're building the infrastructure layer for biomedical AI agents, and we welcome contributions from the community. Contributors with significant contributions will be invited to co-author publications in top-tier journals and conferences.
4
+
5
+ ## Getting Started
6
+
7
+ Before contributing, please ensure you:
8
+ - Have tested your changes locally
9
+ - Follow the existing code style and conventions
10
+ - Include appropriate documentation
11
+
12
+ ## Types of Contributions
13
+
14
+ ### 🛠️ Adding a New Tool
15
+
16
+ Tools are implemented as Python functions in `biomni/tool/XXX.py`, organized by subject area.
17
+
18
+ **Steps:**
19
+ 1. **Implement and test** your function locally. If it requires additional software, create installation script and append it into `biomni_env/new_software_{VERSION}.sh`
20
+
21
+ 2. **Choose the appropriate subject** category (e.g. database, biochemistry, etc.)
22
+
23
+ 3. **Create a tool description** in `biomni/tool/tool_description/XXX.py` following the existing format
24
+
25
+ *Tip: Use this helper to auto-generate descriptions:*
26
+ ```python
27
+ from biomni.utils import function_to_api_schema
28
+ from biomni.llm import get_llm
29
+
30
+ llm = get_llm('claude-sonnet-4-20250514')
31
+ desc = function_to_api_schema(function_code, llm)
32
+ ```
33
+ 4. **Create a test prompt** that uses your tool and verify the agent works correctly
34
+ 5. **Submit a pull request** for review, don't forget to include your test prompt as well
35
+
36
+ ### 📊 Adding New Data
37
+
38
+ If the data source has web API, follow this process:
39
+
40
+ **Steps:**
41
+ 1. **Verify uniqueness** - ensure no overlap with existing data
42
+ 2. **Add a new query_XX function** to `biomni/tool/database.py`, follow the format from the other functions.
43
+ 3. **Create a tool description** in `biomni/tool/tool_description/database.py` following the existing format
44
+
45
+ If the data source has no API access, follow the process below:
46
+
47
+ **Steps:**
48
+ 1. **Verify uniqueness** - ensure no overlap with existing data
49
+ 2. **Prepare download link** with verified redistribution rights
50
+ 3. **Add entry** to `data_lake_dict` in `biomni/env_desc.py`
51
+ 4. **Submit a pull request** with the download link
52
+
53
+ Then, make a PR.
54
+
55
+ ### 💻 Adding New Software
56
+
57
+ **Steps:**
58
+ 1. **Test locally** to ensure no conflicts with existing environments
59
+ 2. **Create installation script** and append it into `biomni_env/new_software_{VERSION}.sh`
60
+ 3. **Add entry** to `library_content_dict` in `biomni/env_desc.py`
61
+ 4. **Submit a pull request** including:
62
+ - Installation bash script
63
+ - Screenshot demonstrating no environment conflicts
64
+
65
+ ### 🎯 Adding a New Benchmark
66
+
67
+ Create benchmarks in the `biomni/task/` folder.
68
+
69
+ **Required implementation:**
70
+ ```python
71
+ class YourBenchmark:
72
+ def __init__(self):
73
+ # Initialize benchmark
74
+ pass
75
+
76
+ def __len__(self):
77
+ # Return dataset size
78
+ pass
79
+
80
+ def get_example(self, index):
81
+ # Return dataset item at index
82
+ pass
83
+
84
+ def evaluate(self):
85
+ # Evaluation logic (flexible input format)
86
+ pass
87
+
88
+ def output_class(self):
89
+ # Return expected agent output format
90
+ pass
91
+ ```
92
+
93
+ **Steps:**
94
+ 1. **Create benchmark file** in `biomni/task/[benchmark_name].py`
95
+ 2. **Implement required methods** as shown above
96
+ 3. **Provide data download link** for associated datasets
97
+ 4. **Submit a pull request**
98
+
99
+ ### 🐛 Bug Fixes & Enhancements
100
+
101
+ We welcome all bug fixes and enhancements to the existing codebase!
102
+
103
+ **Create an issue to discuss with the Biomni team first.**
104
+
105
+ **Guidelines:**
106
+ - Clearly describe the issue or enhancement
107
+ - Include tests when applicable
108
+ - Follow existing code patterns
109
+ - Update documentation if needed
110
+
111
+ ## Submission Process
112
+
113
+ 1. **Fork** the repository
114
+ 2. **Create a feature branch** from `main`
115
+ 3. **Make your changes** following the guidelines above
116
+ 4. **Test thoroughly** in your local environment
117
+ 5. **Submit a pull request** with a clear description
118
+
119
+ ## Review Process
120
+
121
+ The Biomni team will review all pull requests promptly. We may request changes or provide feedback to ensure code quality and consistency.
122
+
123
+ ## Questions?
124
+
125
+ If you have questions about contributing, please open an issue or reach out to the maintainers.
126
+
127
+ ---
128
+
129
+ *Together, let's build the future of biomedical AI agents!*
DETAILS.md ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DETAILS.md
2
+
3
+ 🔍 **Powered by [Detailer](https://detailer.ginylil.com)** - Context-aware codebase analysis
4
+
5
+
6
+
7
+ ---
8
+
9
+ ## 1. Project Overview
10
+
11
+ ### Project Purpose & Domain
12
+
13
+ This project is a comprehensive **biomedical AI toolkit and research platform** designed to facilitate **biomedical data analysis, knowledge extraction, and AI-driven reasoning**. It integrates large language models (LLMs), domain-specific bioinformatics tools, and scientific data processing pipelines to enable:
14
+
15
+ - Automated extraction of biomedical knowledge from literature (e.g., bioRxiv papers)
16
+ - Querying and integration of diverse biomedical databases and APIs
17
+ - Execution of domain-specific computational biology and physiology analyses
18
+ - AI agent orchestration for complex biomedical reasoning and tool invocation
19
+ - Benchmarking and evaluation of biomedical tasks and datasets
20
+
21
+ ### Target Users and Use Cases
22
+
23
+ - **Biomedical researchers and data scientists** seeking to automate literature mining, data retrieval, and analysis workflows.
24
+ - **Bioinformaticians** requiring integrated access to multiple biological databases and computational tools.
25
+ - **AI researchers** interested in applying LLMs and autonomous agents to biomedical problem solving.
26
+ - **Developers and integrators** building domain-specific AI pipelines and scientific workflows.
27
+ - Use cases include:
28
+ - Extracting structured biomedical tasks and entities from scientific papers
29
+ - Querying gene, protein, disease, and pathway databases via natural language prompts
30
+ - Running computational models of biological systems (e.g., metabolic networks, signaling)
31
+ - Performing image analysis and quantitative pathology workflows
32
+ - Orchestrating multi-step AI reasoning with tool use and self-criticism
33
+
34
+ ### Core Business Logic and Domain Models
35
+
36
+ - **Biomedical domain models**: gene IDs, protein structures, pathways, disease ontologies, experimental assays.
37
+ - **Task abstractions**: benchmark tasks with prompt/response evaluation (e.g., humanity last exam, lab bench).
38
+ - **Tool metadata schemas**: declarative descriptions of biomedical tools and APIs for dynamic invocation.
39
+ - **AI agent workflows**: ReAct-style reasoning graphs integrating LLMs, tool calls, retrieval, and self-critique.
40
+ - **Data models**: structured JSON, pandas DataFrames, numpy arrays representing biological data and analysis results.
41
+
42
+ ---
43
+
44
+ ## 2. Architecture and Structure
45
+
46
+ ### High-Level Architecture
47
+
48
+ The system is organized into modular layers and components:
49
+
50
+ - **Core Library (`biomni/`)**: Contains main application logic, including:
51
+ - **Agent framework (`biomni/agent/`)**: Implements autonomous AI agents using LLMs and workflow graphs.
52
+ - **Task definitions (`biomni/task/`)**: Abstract base and concrete biomedical benchmark tasks.
53
+ - **Tool implementations (`biomni/tool/`)**: Domain-specific analysis functions, API clients, and computational biology workflows.
54
+ - **Tool metadata (`biomni/tool/tool_description/`)**: Declarative schemas describing tool APIs and parameters.
55
+ - **Model components (`biomni/model/`)**: AI-driven resource retriever for selecting relevant tools and data.
56
+ - **Utility modules (`biomni/utils.py`, `biomni/llm.py`, `biomni/env_desc.py`)**: Helpers for LLM instantiation, system commands, environment descriptions.
57
+ - **Versioning (`biomni/version.py`)**: Package version management.
58
+
59
+ - **Environment Setup (`biomni_env/`)**: Scripts and configuration files for reproducible environment provisioning, including:
60
+ - Conda environment YAMLs (`environment.yml`, `bio_env.yml`)
61
+ - R package specifications (`r_packages.yml`)
62
+ - CLI tools installer (`install_cli_tools.sh`)
63
+ - Shell scripts for environment setup (`setup.sh`, `setup_path.sh`)
64
+
65
+ - **Scripts (`biomni/biorxiv_scripts/`)**: Data processing pipelines for literature mining and task extraction.
66
+
67
+ - **Documentation and Configuration**:
68
+ - Root-level files: `README.md`, `CONTRIBUTION.md`, `pyproject.toml`, `.pre-commit-config.yaml`.
69
+
70
+ ---
71
+
72
+ ### Complete Repository Structure
73
+
74
+ ```
75
+ .
76
+ ├── biomni/ (90 items)
77
+ │ ├── agent/
78
+ │ │ ├── __init__.py
79
+ │ │ ├── a1.py
80
+ │ │ ├── env_collection.py
81
+ │ │ ├── qa_llm.py
82
+ │ │ └── react.py
83
+ │ ├── biorxiv_scripts/
84
+ │ │ ├── extract_biorxiv_tasks.py
85
+ │ │ ├── generate_function.py
86
+ │ │ └── process_all_subjects.py
87
+ │ ├── model/
88
+ │ │ ├── __init__.py
89
+ │ │ └── retriever.py
90
+ │ ├── task/
91
+ │ │ ├── __init__.py
92
+ │ │ ├── base_task.py
93
+ │ │ ├── hle.py
94
+ │ │ └── lab_bench.py
95
+ │ ├── tool/ (65 items)
96
+ │ │ ├── schema_db/ (25 items)
97
+ │ │ │ ├── cbioportal.pkl
98
+ │ │ │ ├── clinvar.pkl
99
+ │ │ │ ├── dbsnp.pkl
100
+ │ │ │ ├── emdb.pkl
101
+ │ │ │ ├── ensembl.pkl
102
+ │ │ │ ├── geo.pkl
103
+ │ │ │ ├── gnomad.pkl
104
+ │ │ │ ├── gtopdb.pkl
105
+ │ │ │ ├── gwas_catalog.pkl
106
+ │ │ │ ├── interpro.pkl
107
+ │ │ │ └── ... (15 more files)
108
+ │ │ ├── tool_description/ (18 items)
109
+ │ │ │ ├── biochemistry.py
110
+ │ │ │ ├── bioengineering.py
111
+ │ │ │ ├── biophysics.py
112
+ │ │ │ ├── cancer_biology.py
113
+ │ │ │ ├── cell_biology.py
114
+ │ │ │ ├── database.py
115
+ │ │ │ ├── genetics.py
116
+ │ │ │ ├── genomics.py
117
+ │ │ │ ├── immunology.py
118
+ │ │ │ ├── literature.py
119
+ │ │ │ ├── microbiology.py
120
+ │ │ │ ├── molecular_biology.py
121
+ │ │ │ ├── pathology.py
122
+ │ │ │ ├── pharmacology.py
123
+ │ │ │ ├── physiology.py
124
+ │ │ │ ├── support_tools.py
125
+ │ │ │ ├── synthetic_biology.py
126
+ │ │ │ └── systems_biology.py
127
+ │ │ ├── __init__.py
128
+ │ │ ├── biochemistry.py
129
+ │ │ ├── bioengineering.py
130
+ │ │ ├── biophysics.py
131
+ │ │ ├── cancer_biology.py
132
+ │ │ ├── cell_biology.py
133
+ │ │ ├── database.py
134
+ │ │ ├── genetics.py
135
+ │ │ └── ... (12 more files)
136
+ │ ├── __init__.py
137
+ │ ├── env_desc.py
138
+ │ ├── llm.py
139
+ │ ├── utils.py
140
+ │ └── version.py
141
+ ├── biomni_env/ (9 items)
142
+ │ ├── README.md
143
+ │ ├── bio_env.yml
144
+ │ ├── cli_tools_config.json
145
+ │ ├── environment.yml
146
+ │ ├── install_cli_tools.sh
147
+ │ ├── install_r_packages.R
148
+ │ ├── r_packages.yml
149
+ │ ├── setup.sh
150
+ │ └── setup_path.sh
151
+ ├── figs/
152
+ │ └── biomni_logo.png
153
+ ├── tutorials/
154
+ │ ├── examples/
155
+ │ │ └── cloning.ipynb
156
+ │ ├── 101_biomni.ipynb
157
+ │ └── biomni_101.ipynb
158
+ ├── .gitignore
159
+ ├── .pre-commit-config.yaml
160
+ ├── CONTRIBUTION.md
161
+ ├── LICENSE
162
+ ├── README.md
163
+ └── pyproject.toml
164
+ ```
165
+
166
+ ---
167
+
168
+ ## 3. Technical Implementation Details
169
+
170
+ ### Core Modules and Their Roles
171
+
172
+ #### `biomni/agent/`
173
+
174
+ - Implements autonomous AI agents using the **ReAct paradigm**:
175
+ - `react.py`: Main ReAct agent class managing reasoning, tool invocation, retrieval, and self-criticism workflows.
176
+ - `env_collection.py`: Environment and data retrieval utilities.
177
+ - `qa_llm.py`: Question-answering LLM wrappers.
178
+ - `a1.py`: Possibly experimental or auxiliary agent code.
179
+
180
+ - Uses **langgraph** for workflow graph orchestration and **langchain** for LLM integration.
181
+
182
+ #### `biomni/task/`
183
+
184
+ - Defines **benchmark tasks** with a common interface:
185
+ - `base_task.py`: Abstract base class specifying methods like `get_example()`, `evaluate()`, `output_class()`.
186
+ - `hle.py`: "Humanity Last Exam" task implementation.
187
+ - `lab_bench.py`: Lab bench dataset task.
188
+
189
+ - Tasks load data (e.g., parquet files), generate prompts, and evaluate LLM responses.
190
+
191
+ #### `biomni/tool/`
192
+
193
+ - Contains **domain-specific scientific analysis functions** organized by subdomains:
194
+ - `biochemistry.py`, `bioengineering.py`, `biophysics.py`, `cancer_biology.py`, `cell_biology.py`, `genetics.py`, `pathology.py`, `physiology.py`, `systems_biology.py`, etc.
195
+ - Each file implements multiple functions performing analyses, simulations, or data processing workflows.
196
+ - Functions accept input files/parameters and return detailed textual logs and output files.
197
+
198
+ - **API client modules** (e.g., `database.py`) provide facade functions to query external biomedical databases (UniProt, GWAS Catalog, Ensembl, etc.) via REST or GraphQL APIs, often using LLMs to generate query payloads from natural language prompts.
199
+
200
+ - **Tool registry (`tool_registry.py`)** manages metadata about available tools, supporting dynamic registration and lookup.
201
+
202
+ #### `biomni/tool/tool_description/`
203
+
204
+ - Contains **declarative metadata schemas** describing tool APIs:
205
+ - Each file exports a `description` list of dictionaries defining tool names, descriptions, required and optional parameters with types and defaults.
206
+ - Supports **dynamic API generation, validation, and documentation**.
207
+ - Organized by biological domain (e.g., genetics, immunology, pathology).
208
+
209
+ #### `biomni/model/retriever.py`
210
+
211
+ - Implements `ToolRetriever` class for **AI-driven resource selection**:
212
+ - Uses LLMs (OpenAI or Anthropic) to parse user queries and select relevant tools, datasets, and libraries.
213
+ - Encapsulates prompt formatting and response parsing logic.
214
+
215
+ #### `biomni/utils.py` and `biomni/llm.py`
216
+
217
+ - `utils.py`: Utility functions for running system commands (R, Bash), file operations, schema generation, logging, and colorized printing.
218
+ - `llm.py`: Factory functions to instantiate LLMs (OpenAI, Anthropic) with configurable parameters.
219
+
220
+ #### `biomni/env_desc.py`
221
+
222
+ - Contains **environment and dataset descriptions**, acting as a centralized metadata repository for datasets and experimental environments.
223
+
224
+ ---
225
+
226
+ ### Environment Setup (`biomni_env/`)
227
+
228
+ - `setup.sh`: Main shell script to create conda environment, install R packages, and CLI bioinformatics tools.
229
+ - `install_cli_tools.sh`: Automates downloading, compiling, and installing external bioinformatics command-line tools, managing PATH and verification.
230
+ - `r_packages.yml`: Lists R packages required.
231
+ - `environment.yml` and `bio_env.yml`: Conda environment specifications.
232
+ - `setup_path.sh`: Shell script to update environment variables for CLI tools.
233
+
234
+ ---
235
+
236
+ ### Entry Points and Execution Flow
237
+
238
+ - **Agent usage**: Instantiate `react` agent from `biomni.agent.react`, configure with tools and retrieval, then call `go(prompt)` to run reasoning workflows.
239
+ - **Task evaluation**: Use classes in `biomni.task` to load datasets, generate prompts, and evaluate LLM outputs.
240
+ - **Tool invocation**: Call functions in `biomni.tool` modules or use API facades in `database.py` to query external resources.
241
+ - **Metadata-driven tool discovery**: Use `tool_registry.py` and `tool_description` schemas to dynamically discover and validate tools.
242
+ - **Environment setup**: Run `biomni_env/setup.sh` to provision environment and install dependencies.
243
+
244
+ ---
245
+
246
+ ## 4. Development Patterns and Standards
247
+
248
+ ### Code Organization Principles
249
+
250
+ - **Modular design**: Clear separation of concerns by domain and functionality (agent, task, tool, model).
251
+ - **Functional programming style**: Most analysis modules use standalone functions with explicit inputs and outputs.
252
+ - **Declarative metadata**: Tool descriptions and schemas are separated from implementation, enabling dynamic validation and UI generation.
253
+ - **Abstract base classes**: Used in `biomni.task.base_task` to enforce consistent task interfaces.
254
+ - **Factory pattern**: Used in `llm.py` to instantiate LLMs based on configuration.
255
+ - **Strategy pattern**: Task implementations and tool retrieval use interchangeable strategies.
256
+
257
+ ### Testing and Coverage
258
+
259
+ - No explicit test files detected; testing likely manual or via notebooks (`tutorials/`).
260
+ - Tasks and tools return detailed logs suitable for manual verification.
261
+ - Metadata schemas facilitate automated validation of inputs.
262
+
263
+ ### Error Handling and Logging
264
+
265
+ - Use of try-except blocks around external calls and subprocesses.
266
+ - Logging via custom callback handlers (`PromptLogger`, `NodeLogger`) in LLM interactions.
267
+ - Utilities provide colorized printing and error wrappers for robustness.
268
+
269
+ ### Configuration Management
270
+
271
+ - Environment variables for API keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`).
272
+ - YAML and JSON files for environment and tool configuration.
273
+ - Dynamic loading of schemas from pickle files for API request generation.
274
+ - CLI tools and R packages installed via scripted environment setup.
275
+
276
+ ---
277
+
278
+ ## 5. Integration and Dependencies
279
+
280
+ ### External Libraries
281
+
282
+ - **LLM & AI Frameworks**: `langchain_core`, `langchain_openai`, `langchain_anthropic`
283
+ - **Scientific Computing**: `numpy`, `pandas`, `scipy`, `scikit-image`, `matplotlib`, `BioPython`, `cobra`, `sklearn`
284
+ - **Data Processing**: `pickle`, `json`, `requests`, `PyPDF2`
285
+ - **System and OS**: `subprocess`, `os`, `sys`, `tempfile`, `multiprocessing`
286
+ - **Others**: `tqdm` (progress bars), `enum`, `ast` (code introspection)
287
+
288
+ ### External APIs and Data Sources
289
+
290
+ - Biomedical databases: UniProt, GWAS Catalog, Ensembl, ClinVar, dbSNP, EMDB, GEO, GnomAD, InterPro, etc.
291
+ - Bioinformatics tools: PLINK, IQ-TREE, GCTA, MACS2, samtools, LUMPY, installed via CLI tools installer.
292
+ - R packages for statistical and bioinformatics analyses.
293
+
294
+ ### Build and Deployment Dependencies
295
+
296
+ - Python 3 environment managed via Conda (`environment.yml`).
297
+ - R environment with specified packages (`r_packages.yml`).
298
+ - Shell scripts for CLI tool installation and environment setup.
299
+ - Pre-commit hooks for code quality and security.
300
+
301
+ ---
302
+
303
+ ## 6. Usage and Operational Guidance
304
+
305
+ ### Getting Started
306
+
307
+ 1. **Environment Setup**
308
+ - Run `biomni_env/setup.sh` to create the Conda environment, install R packages, and CLI tools.
309
+ - Source `biomni_env/setup_path.sh` or add it to your shell profile to configure PATH.
310
+
311
+ 2. **API Keys**
312
+ - Set environment variables `OPENAI_API_KEY` and/or `ANTHROPIC_API_KEY` for LLM access.
313
+
314
+ 3. **Running Agents**
315
+ - Import and instantiate the `react` agent from `biomni.agent.react`.
316
+ - Configure with desired tools and retrieval options.
317
+ - Call `go(prompt)` to execute reasoning workflows.
318
+
319
+ 4. **Executing Tasks**
320
+ - Use classes in `biomni.task` to load datasets and evaluate LLM responses.
321
+ - Implement new tasks by subclassing `base_task` and following the interface.
322
+
323
+ 5. **Querying Databases**
324
+ - Use `biomni.tool.database` functions (e.g., `query_uniprot`, `query_gwas_catalog`) to retrieve data via natural language or direct parameters.
325
+
326
+ 6. **Extending Tools**
327
+ - Add new tool metadata in `biomni/tool/tool_description/` as structured dictionaries.
328
+ - Implement corresponding analysis functions in `biomni/tool/`.
329
+ - Register tools in `tool_registry.py` for discovery.
330
+
331
+ ### Monitoring and Debugging
332
+
333
+ - Use logging callbacks (`PromptLogger`, `NodeLogger`) to trace LLM interactions.
334
+ - Check output logs returned by analysis functions for detailed execution info.
335
+ - Use pre-commit hooks to maintain code quality.
336
+
337
+ ### Performance and Scalability
338
+
339
+ - Modular design allows parallel execution of tasks and tools.
340
+ - Timeout wrappers in agent tools prevent hanging executions.
341
+ - Use of efficient numerical libraries (`numpy`, `scipy`) for computational tasks.
342
+ - Large data handled via streaming and chunking (e.g., PDF text extraction).
343
+
344
+ ### Security Considerations
345
+
346
+ - API keys managed via environment variables, not hardcoded.
347
+ - Pre-commit hooks include security checks.
348
+ - External tool installations verified via version commands.
349
+
350
+ ### Observability
351
+
352
+ - Progress bars (`tqdm`) used in data processing scripts.
353
+ - Structured logs and JSON outputs facilitate downstream analysis.
354
+ - Agent workflows produce detailed message histories for audit.
355
+
356
+ ---
357
+
358
+ ## Summary
359
+
360
+ This project is a **modular, extensible biomedical AI platform** integrating **LLM-powered agents**, **domain-specific scientific tools**, and **metadata-driven APIs** to automate complex biomedical research workflows. It emphasizes **declarative tool descriptions**, **dynamic resource retrieval**, and **robust environment provisioning** to enable researchers and developers to build, evaluate, and extend AI-driven biomedical applications efficiently.
361
+
362
+ ---
363
+
364
+ # End of DETAILS.md
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
MANIFEST.in ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MANIFEST.in
2
+
3
+ # Include all python files from the biomni package
4
+ recursive-include biomni *.py
5
+
6
+ # Include the .pkl database files
7
+ recursive-include biomni/tool/schema_db *.pkl
8
+
9
+ # Include specific files from biomni_env, but not the biomni_tools subdirectory
10
+ recursive-include biomni_env *.py *.sh *.yml *.yaml *.txt *.md *.json *.R
11
+
12
+ # Exclude specific directories that are not part of the package
13
+ prune tutorials/data
14
+ prune biomni_env/biomni_tools
15
+
16
+ # Exclude build artifacts and caches
17
+ global-exclude *.py[co]
18
+ global-exclude __pycache__
19
+ global-exclude .ruff_cache
20
+ prune build
21
+ prune dist
22
+ prune *.egg-info
23
+
24
+ # Include other necessary files
25
+ include README.md
26
+ include LICENSE
27
+ include pyproject.toml
README.md ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <img src="./figs/biomni_logo.png" alt="Biomni Logo" width="600px" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://join.slack.com/t/biomnigroup/shared_invite/zt-3avks4913-dotMBt8D_apQnJ3mG~ak6Q">
7
+ <img src="https://img.shields.io/badge/Join-Slack-4A154B?style=for-the-badge&logo=slack" alt="Join Slack" />
8
+ </a>
9
+ <a href="https://biomni.stanford.edu">
10
+ <img src="https://img.shields.io/badge/Try-Web%20UI-blue?style=for-the-badge" alt="Web UI" />
11
+ </a>
12
+ <a href="https://x.com/ProjectBiomni">
13
+ <img src="https://img.shields.io/badge/Follow-on%20X-black?style=for-the-badge&logo=x" alt="Follow on X" />
14
+ </a>
15
+ <a href="https://www.linkedin.com/company/project-biomni">
16
+ <img src="https://img.shields.io/badge/Follow-LinkedIn-0077B5?style=for-the-badge&logo=linkedin" alt="Follow on LinkedIn" />
17
+ </a>
18
+ <a href="https://www.biorxiv.org/content/10.1101/2025.05.30.656746v1">
19
+ <img src="https://img.shields.io/badge/Read-Paper-green?style=for-the-badge" alt="Paper" />
20
+ </a>
21
+ </p>
22
+
23
+
24
+
25
+ # Biomni: A General-Purpose Biomedical AI Agent
26
+
27
+ ## Overview
28
+
29
+
30
+ Biomni is a general-purpose biomedical AI agent designed to autonomously execute a wide range of research tasks across diverse biomedical subfields. By integrating cutting-edge large language model (LLM) reasoning with retrieval-augmented planning and code-based execution, Biomni helps scientists dramatically enhance research productivity and generate testable hypotheses.
31
+
32
+
33
+ ## Quick Start
34
+
35
+ ### Installation
36
+
37
+ Our software environment is massive and we provide a single setup.sh script to setup.
38
+ Follow this [file](biomni_env/README.md) to setup the env first.
39
+
40
+ Then activate the environment E1:
41
+
42
+ ```bash
43
+ conda activate biomni_e1
44
+ ```
45
+
46
+ then install the biomni official pip package:
47
+
48
+ ```bash
49
+ pip install biomni --upgrade
50
+ ```
51
+
52
+ For the latest update, install from the github source version, or do:
53
+
54
+ ```bash
55
+ pip install git+https://github.com/snap-stanford/Biomni.git@main
56
+ ```
57
+
58
+ Lastly, configure your API keys using one of the following methods:
59
+
60
+ <details>
61
+ <summary>Click to expand</summary>
62
+
63
+ #### Option 1: Using .env file (Recommended)
64
+
65
+ Create a `.env` file in your project directory:
66
+
67
+ ```bash
68
+ # Copy the example file
69
+ cp .env.example .env
70
+
71
+ # Edit the .env file with your actual API keys
72
+ ```
73
+
74
+ Your `.env` file should look like:
75
+
76
+ ```env
77
+ # Required: Anthropic API Key for Claude models
78
+ ANTHROPIC_API_KEY=your_anthropic_api_key_here
79
+
80
+ # Optional: OpenAI API Key (if using OpenAI models)
81
+ OPENAI_API_KEY=your_openai_api_key_here
82
+
83
+ # Optional: Azure OpenAI API Key (if using Azure OpenAI models)
84
+ OPENAI_API_KEY=your_azure_openai_api_key
85
+ OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/
86
+
87
+ # Optional: AI Studio Gemini API Key (if using Gemini models)
88
+ GEMINI_API_KEY=your_gemini_api_key_here
89
+
90
+ # Optional: groq API Key (if using groq as model provider)
91
+ GROQ_API_KEY=your_groq_api_key_here
92
+
93
+ # Optional: Set the source of your LLM for example:
94
+ #"OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", "Groq", "Custom"
95
+ LLM_SOURCE=your_LLM_source_here
96
+
97
+ # Optional: AWS Bedrock Configuration (if using AWS Bedrock models)
98
+ AWS_BEARER_TOKEN_BEDROCK=your_bedrock_api_key_here
99
+ AWS_REGION=us-east-1
100
+
101
+ # Optional: Custom model serving configuration
102
+ # CUSTOM_MODEL_BASE_URL=http://localhost:8000/v1
103
+ # CUSTOM_MODEL_API_KEY=your_custom_api_key_here
104
+
105
+ # Optional: Biomni data path (defaults to ./data)
106
+ # BIOMNI_DATA_PATH=/path/to/your/data
107
+
108
+ # Optional: Timeout settings (defaults to 600 seconds)
109
+ # BIOMNI_TIMEOUT_SECONDS=600
110
+ ```
111
+
112
+ #### Option 2: Using shell environment variables
113
+
114
+ Alternatively, configure your API keys in bash profile `~/.bashrc`:
115
+
116
+ ```bash
117
+ export ANTHROPIC_API_KEY="YOUR_API_KEY"
118
+ export OPENAI_API_KEY="YOUR_API_KEY" # optional if you just use Claude
119
+ export OPENAI_ENDPOINT="https://your-resource-name.openai.azure.com/" # optional unless you are using Azure
120
+ export AWS_BEARER_TOKEN_BEDROCK="YOUR_BEDROCK_API_KEY" # optional for AWS Bedrock models
121
+ export AWS_REGION="us-east-1" # optional, defaults to us-east-1 for Bedrock
122
+ export GEMINI_API_KEY="YOUR_GEMINI_API_KEY" #optional if you want to use a gemini model
123
+ export GROQ_API_KEY="YOUR_GROQ_API_KEY" # Optional: set this to use models served by Groq
124
+ export LLM_SOURCE="Groq" # Optional: set this to use models served by Groq
125
+
126
+
127
+ ```
128
+ </details>
129
+
130
+
131
+ #### ⚠️ Known Package Conflicts
132
+
133
+ Some Python packages are not installed by default in the Biomni environment due to dependency conflicts. If you need these features, you must install the packages manually and may need to uncomment relevant code in the codebase. See the up-to-date list and details in [docs/known_conflicts.md](./docs/known_conflicts.md).
134
+
135
+ ### Basic Usage
136
+
137
+ Once inside the environment, you can start using Biomni:
138
+
139
+ ```python
140
+ from biomni.agent import A1
141
+
142
+ # Initialize the agent with data path, Data lake will be automatically downloaded on first run (~11GB)
143
+ agent = A1(path='./data', llm='claude-sonnet-4-20250514')
144
+
145
+ # Execute biomedical tasks using natural language
146
+ agent.go("Plan a CRISPR screen to identify genes that regulate T cell exhaustion, generate 32 genes that maximize the perturbation effect.")
147
+ agent.go("Perform scRNA-seq annotation at [PATH] and generate meaningful hypothesis")
148
+ agent.go("Predict ADMET properties for this compound: CC(C)CC1=CC=C(C=C1)C(C)C(=O)O")
149
+ ```
150
+
151
+ #### Controlling Datalake Loading
152
+
153
+ By default, Biomni automatically downloads the datalake files (~11GB) when you create an agent. You can control this behavior:
154
+
155
+ ```python
156
+ # Skip automatic datalake download (faster initialization)
157
+ agent = A1(path='./data', llm='claude-sonnet-4-20250514', expected_data_lake_files = [])
158
+ ```
159
+
160
+ This is useful for:
161
+ - Faster testing and development
162
+ - Environments with limited storage or bandwidth
163
+ - Cases where you only need specific tools that don't require datalake files
164
+ If you plan on using Azure for your model, always prefix the model name with azure- (e.g. llm='azure-gpt-4o').
165
+
166
+ ### Gradio Interface
167
+
168
+ Launch an interactive web UI for Biomni:
169
+
170
+ ```python
171
+ from biomni.agent import A1
172
+
173
+ agent = A1(path='./data', llm='claude-sonnet-4-20250514')
174
+ agent.launch_gradio_demo()
175
+ ```
176
+
177
+ **Installation:**
178
+ ```bash
179
+ pip install "gradio>=5.0,<6.0"
180
+ ```
181
+
182
+ **Note:** Biomni's Gradio interface currently requires Gradio 5.x due to API changes in Gradio 6.0. If you have Gradio 6.x installed, you may need to downgrade.
183
+
184
+ **Options:**
185
+ - `share=True` - Create a public shareable link
186
+ - `server_name="127.0.0.1"` - Localhost only (default: "0.0.0.0")
187
+ - `require_verification=True` - Require access code (default code: "Biomni2025")
188
+
189
+ The interface will be available at `http://localhost:7860`
190
+
191
+ ### Configuration Management
192
+
193
+ Biomni includes a centralized configuration system that provides flexible ways to manage settings. You can configure Biomni through environment variables, runtime modifications, or direct parameters.
194
+
195
+ ```python
196
+ from biomni.config import default_config
197
+ from biomni.agent import A1
198
+
199
+ # RECOMMENDED: Modify global defaults for consistency
200
+ default_config.llm = "gpt-4"
201
+ default_config.timeout_seconds = 1200
202
+
203
+ # All agents AND database queries use these defaults
204
+ agent = A1() # Everything uses gpt-4, 1200s timeout
205
+ ```
206
+
207
+ **Note**: Direct parameters to `A1()` only affect that agent's reasoning, not database queries. For consistent configuration across all operations, use `default_config` or environment variables.
208
+
209
+ For detailed configuration options, see the **[Configuration Guide](docs/configuration.md)**.
210
+
211
+ ### PDF Generation
212
+
213
+ Generate PDF reports of execution traces:
214
+
215
+ ```python
216
+ from biomni.agent import A1
217
+
218
+ # Initialize agent
219
+ agent = A1(path='./data', llm='claude-sonnet-4-20250514')
220
+
221
+ # Run your task
222
+ agent.go("Your biomedical task here")
223
+
224
+ # Save conversation history as PDF
225
+ agent.save_conversation_history("my_analysis_results.pdf")
226
+ ```
227
+
228
+ **PDF Generation Dependencies:**
229
+ <details>
230
+ <summary>Click to expand</summary>
231
+ For optimal PDF generation, install one of these packages:
232
+
233
+ ```bash
234
+ # Option 1: WeasyPrint (recommended for best layout control)
235
+ # Conda environment (recommended)
236
+ conda install weasyprint
237
+
238
+ # System installation
239
+ brew install weasyprint # macOS
240
+ apt install weasyprint # Linux
241
+
242
+ # See [WeasyPrint Installation Guide](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html) for detailed instructions.
243
+
244
+ # Option 2: markdown2pdf (Rust-based, fast and reliable)
245
+ # macOS:
246
+ brew install theiskaa/tap/markdown2pdf
247
+
248
+ # Windows/Linux (using Cargo):
249
+ cargo install markdown2pdf
250
+
251
+ # Or download prebuilt binaries from:
252
+ # https://github.com/theiskaa/markdown2pdf/releases/latest
253
+
254
+ # Option 3: Pandoc (pip installation)
255
+ pip install pandoc
256
+ ```
257
+ </details>
258
+
259
+ ## MCP (Model Context Protocol) Support
260
+
261
+ Biomni supports MCP servers for external tool integration:
262
+
263
+ ```python
264
+ from biomni.agent import A1
265
+
266
+ agent = A1()
267
+ agent.add_mcp(config_path="./mcp_config.yaml")
268
+ agent.go("Find FDA active ingredient information for ibuprofen")
269
+ ```
270
+
271
+ **Built-in MCP Servers:**
272
+ For usage and implementation details, see the [MCP Integration Documentation](docs/mcp_integration.md) and examples in [`tutorials/examples/add_mcp_server/`](tutorials/examples/add_mcp_server/) and [`tutorials/examples/expose_biomni_server/`](tutorials/examples/expose_biomni_server/).
273
+
274
+
275
+ ## Biomni-R0
276
+
277
+ **Biomni-R0** is our first reasoning model for biology, built on Qwen-32B with reinforcement learning from agent interaction data. It's designed to excel at tool use, multi-step reasoning, and complex biological problem-solving through iterative self-correction.
278
+
279
+ - 🤗 Model: [biomni/Biomni-R0-32B-Preview](https://huggingface.co/biomni/Biomni-R0-32B-Preview)
280
+ - 📝 Technical Report: [biomni.stanford.edu/blog/biomni-r0-technical-report](https://biomni.stanford.edu/blog/biomni-r0-technical-report)
281
+
282
+ To use Biomni-R0 for agent reasoning while keeping database queries on your usual provider (recommended), run a local SGLang server and pass the model to `A1()` directly.
283
+
284
+ 1) Launch SGLang with Biomni-R0:
285
+
286
+ ```bash
287
+ python -m sglang.launch_server --model-path RyanLi0802/Biomni-R0-Preview --port 30000 --host 0.0.0.0 --mem-fraction-static 0.8 --tp 2 --trust-remote-code --json-model-override-args '{"rope_scaling":{"rope_type":"yarn","factor":1.0,"original_max_position_embeddings":32768}, "max_position_embeddings": 131072}'
288
+ ```
289
+
290
+ 2) Point the agent to your SGLang endpoint for reasoning:
291
+
292
+ ```python
293
+ from biomni.config import default_config
294
+ from biomni.agent import A1
295
+
296
+ # Database queries (indexes, retrieval, etc.) use default_config
297
+ default_config.llm = "claude-3-5-sonnet-20241022"
298
+ default_config.source = "Anthropic"
299
+
300
+ # Agent reasoning uses Biomni-R0 served via SGLang (OpenAI-compatible API)
301
+ agent = A1(
302
+ llm="biomni/Biomni-R0-32B-Preview",
303
+ source="Custom",
304
+ base_url="http://localhost:30000/v1",
305
+ api_key="EMPTY",
306
+ )
307
+
308
+ agent.go("Plan a CRISPR screen to identify genes regulating T cell exhaustion")
309
+ ```
310
+
311
+ ## Biomni-Eval1
312
+
313
+ **Biomni-Eval1** is a comprehensive evaluation benchmark for assessing biological reasoning capabilities across diverse tasks. It contains **433 instances** spanning **10 biological reasoning tasks**, from gene identification to disease diagnosis.
314
+
315
+ **Tasks Included:**
316
+ - GWAS causal gene identification (3 variants)
317
+ - Lab bench Q&A (2 variants)
318
+ - Patient gene detection
319
+ - Screen gene retrieval
320
+ - GWAS variant prioritization
321
+ - Rare disease diagnosis
322
+ - CRISPR delivery method selection
323
+
324
+ **Resources:**
325
+ - 🤗 Dataset: [biomni/Eval1](https://huggingface.co/datasets/biomni/Eval1)
326
+ - 💻 Quick Start:
327
+ ```python
328
+ from biomni.eval import BiomniEval1
329
+
330
+ evaluator = BiomniEval1()
331
+ score = evaluator.evaluate('gwas_causal_gene_opentargets', 0, 'BRCA1')
332
+ ```
333
+
334
+
335
+ ## 📚 Know-How Library
336
+
337
+ Biomni includes a **Know-How Library** — a curated collection of best practices, protocols, and troubleshooting guides for biomedical techniques. These documents are automatically retrieved by the A1 agent when relevant to provide domain expertise and practical knowledge.
338
+
339
+ **Features:**
340
+ - Automatic retrieval based on query relevance
341
+ - Metadata tracking (authors, affiliations, licensing, commercial use)
342
+ - Compatible with commercial mode (filters non-commercial content)
343
+
344
+ ### 📝 Contributing Know-How Documents
345
+
346
+ We're actively seeking community contributions to expand our Know-How Library! Share your expertise by contributing:
347
+
348
+ - **Lab protocols** (cell culture, flow cytometry, western blotting, etc.)
349
+ - **Analysis best practices** (NGS workflows, microscopy techniques, etc.)
350
+ - **Troubleshooting guides** (common issues and solutions)
351
+ - **Experimental design guidelines** (sample size, controls, validation)
352
+ - **Domain-specific knowledge** (drug formulation, animal models, clinical trials, etc.)
353
+
354
+ Know-how documents should be practical, succinct, and include proper attribution. Use [this know-how](know_how/single_cell_annotation.md) as an example.
355
+
356
+ **To contribute:** Create a markdown file following our template and submit a pull request.
357
+
358
+ ## 🤝 Contributing to Biomni
359
+
360
+ Biomni is an open-science initiative that thrives on community contributions. We welcome:
361
+
362
+ - **🔧 New Tools**: Specialized analysis functions and algorithms
363
+ - **📊 Datasets**: Curated biomedical data and knowledge bases
364
+ - **💻 Software**: Integration of existing biomedical software packages
365
+ - **📋 Benchmarks**: Evaluation datasets and performance metrics
366
+ - **📚 Know-How**: Best practices, protocols, and domain expertise
367
+ - **📚 Misc**: Tutorials, examples, and use cases
368
+ - **🔧 Update existing tools**: many current tools are not optimized - fix and replacements are welcome!
369
+
370
+ Check out this **[Contributing Guide](CONTRIBUTION.md)** on how to contribute to the Biomni ecosystem.
371
+
372
+ If you have particular tool/database/software in mind that you want to add, you can also submit to [this form](https://forms.gle/nu2n1unzAYodTLVj6) and the biomni team will implement them.
373
+
374
+ ## 🔬 Call for Contributors: Help Build Biomni-E2
375
+
376
+ Biomni-E1 only scratches the surface of what’s possible in the biomedical action space.
377
+
378
+ Now, we’re building **Biomni-E2** — a next-generation environment developed **with and for the community**.
379
+
380
+ We believe that by collaboratively defining and curating a shared library of standard biomedical actions, we can accelerate science for everyone.
381
+
382
+ **Join us in shaping the future of biomedical AI agent.**
383
+
384
+ - **Contributors with significant impact** (e.g., 10+ significant & integrated tool contributions or equivalent) will be **invited as co-authors** on our upcoming paper in a top-tier journal or conference.
385
+ - **All contributors** will be acknowledged in our publications.
386
+ - More contributor perks...
387
+
388
+ Let’s build it together.
389
+
390
+
391
+ ## Tutorials and Examples
392
+
393
+ **[Biomni 101](./tutorials/biomni_101.ipynb)** - Basic concepts and first steps
394
+
395
+ More to come!
396
+
397
+ ## 🌐 Web Interface
398
+
399
+ Experience Biomni through our no-code web interface at **[biomni.stanford.edu](https://biomni.stanford.edu)**.
400
+
401
+ [![Watch the video](https://img.youtube.com/vi/E0BRvl23hLs/maxresdefault.jpg)](https://youtu.be/E0BRvl23hLs)
402
+
403
+
404
+ ## Important Note
405
+ - Security warning: Currently, Biomni executes LLM-generated code with full system privileges. If you want to use it in production, please use in isolated/sandboxed environments. The agent can access files, network, and system commands. Be careful with sensitive data or credentials.
406
+ - This release was frozen as of April 15 2025, so it differs from the current web platform.
407
+ - Biomni itself is Apache 2.0-licensed, but certain integrated tools, databases, or software may carry more restrictive commercial licenses. Review each component carefully before any commercial use.
408
+
409
+ ## Cite Us
410
+
411
+ ```
412
+ @article{huang2025biomni,
413
+ title={Biomni: A General-Purpose Biomedical AI Agent},
414
+ author={Huang, Kexin and Zhang, Serena and Wang, Hanchen and Qu, Yuanhao and Lu, Yingzhou and Roohani, Yusuf and Li, Ryan and Qiu, Lin and Zhang, Junze and Di, Yin and others},
415
+ journal={bioRxiv},
416
+ pages={2025--05},
417
+ year={2025},
418
+ publisher={Cold Spring Harbor Laboratory}
419
+ }
420
+ ```
biomni/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .version import __version__
2
+
3
+ __all__ = ["__version__"]
biomni/config.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Biomni Configuration Management
3
+
4
+ Simple configuration class for centralizing common settings.
5
+ Maintains full backward compatibility with existing code.
6
+ """
7
+
8
+ import os
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class BiomniConfig:
14
+ """Central configuration for Biomni agent.
15
+
16
+ All settings are optional and have sensible defaults.
17
+ API keys are still read from environment variables to maintain
18
+ compatibility with existing .env file structure.
19
+
20
+ Usage:
21
+ # Create config with defaults
22
+ config = BiomniConfig()
23
+
24
+ # Override specific settings
25
+ config = BiomniConfig(llm="gpt-4", timeout_seconds=1200)
26
+
27
+ # Modify after creation
28
+ config.path = "./custom_data"
29
+ """
30
+
31
+ # Data and execution settings
32
+ path: str = "./data"
33
+ timeout_seconds: int = 600
34
+
35
+ # LLM settings (API keys still from environment)
36
+ llm: str = "claude-sonnet-4-5"
37
+ temperature: float = 0.7
38
+
39
+ # Tool settings
40
+ use_tool_retriever: bool = True
41
+
42
+ # Data licensing settings
43
+ commercial_mode: bool = False # If True, excludes non-commercial datasets
44
+
45
+ # Custom model settings (for custom LLM serving)
46
+ base_url: str | None = None
47
+ api_key: str | None = None # Only for custom models, not provider API keys
48
+
49
+ # LLM source (auto-detected if None)
50
+ source: str | None = None
51
+
52
+ # Third-party integrations
53
+ protocols_io_access_token: str | None = None
54
+
55
+ def __post_init__(self):
56
+ """Load any environment variable overrides if they exist."""
57
+ # Check for environment variable overrides (optional)
58
+ # Support both old and new names for backwards compatibility
59
+ if os.getenv("BIOMNI_PATH") or os.getenv("BIOMNI_DATA_PATH"):
60
+ self.path = os.getenv("BIOMNI_PATH") or os.getenv("BIOMNI_DATA_PATH")
61
+ if os.getenv("BIOMNI_TIMEOUT_SECONDS"):
62
+ self.timeout_seconds = int(os.getenv("BIOMNI_TIMEOUT_SECONDS"))
63
+ if os.getenv("BIOMNI_LLM") or os.getenv("BIOMNI_LLM_MODEL"):
64
+ self.llm = os.getenv("BIOMNI_LLM") or os.getenv("BIOMNI_LLM_MODEL")
65
+ if os.getenv("BIOMNI_USE_TOOL_RETRIEVER"):
66
+ self.use_tool_retriever = os.getenv("BIOMNI_USE_TOOL_RETRIEVER").lower() == "true"
67
+ if os.getenv("BIOMNI_COMMERCIAL_MODE"):
68
+ self.commercial_mode = os.getenv("BIOMNI_COMMERCIAL_MODE").lower() == "true"
69
+ if os.getenv("BIOMNI_TEMPERATURE"):
70
+ self.temperature = float(os.getenv("BIOMNI_TEMPERATURE"))
71
+ if os.getenv("BIOMNI_CUSTOM_BASE_URL"):
72
+ self.base_url = os.getenv("BIOMNI_CUSTOM_BASE_URL")
73
+ if os.getenv("BIOMNI_CUSTOM_API_KEY"):
74
+ self.api_key = os.getenv("BIOMNI_CUSTOM_API_KEY")
75
+ if os.getenv("BIOMNI_SOURCE"):
76
+ self.source = os.getenv("BIOMNI_SOURCE")
77
+
78
+ # Protocols.io access token (prefer specific env vars)
79
+ env_token = os.getenv("PROTOCOLS_IO_ACCESS_TOKEN") or os.getenv("BIOMNI_PROTOCOLS_IO_ACCESS_TOKEN")
80
+ if env_token:
81
+ self.protocols_io_access_token = env_token
82
+
83
+ def to_dict(self) -> dict:
84
+ """Convert config to dictionary for easy access."""
85
+ return {
86
+ "path": self.path,
87
+ "timeout_seconds": self.timeout_seconds,
88
+ "llm": self.llm,
89
+ "temperature": self.temperature,
90
+ "use_tool_retriever": self.use_tool_retriever,
91
+ "commercial_mode": self.commercial_mode,
92
+ "base_url": self.base_url,
93
+ "api_key": self.api_key,
94
+ "source": self.source,
95
+ }
96
+
97
+
98
+ # Global default config instance (optional, for convenience)
99
+ default_config = BiomniConfig()
biomni/env_desc.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data lake dictionary with detailed descriptions
2
+ data_lake_dict = {
3
+ "affinity_capture-ms.parquet": "Protein-protein interactions detected via affinity capture and mass spectrometry.",
4
+ "affinity_capture-rna.parquet": "Protein-RNA interactions detected by affinity capture.",
5
+ "BindingDB_All_202409.tsv": "Measured binding affinities between proteins and small molecules for drug discovery.",
6
+ "broad_repurposing_hub_molecule_with_smiles.parquet": "Molecules from Broad Institute's Drug Repurposing Hub with SMILES annotations.",
7
+ "broad_repurposing_hub_phase_moa_target_info.parquet": "Drug phases, mechanisms of action, and target information from Broad Institute.",
8
+ "co-fractionation.parquet": "Protein-protein interactions from co-fractionation experiments.",
9
+ "czi_census_datasets_v4.parquet": "Datasets from the Chan Zuckerberg Initiative's Cell Census.",
10
+ "DepMap_CRISPRGeneDependency.csv": "Gene dependency probability estimates for cancer cell lines, including all DepMap models.",
11
+ "DepMap_CRISPRGeneEffect.csv": "Genome-wide CRISPR gene effect estimates for cancer cell lines, including all DepMap models.",
12
+ "DepMap_Model.csv": "Metadata describing all cancer models/cell lines which are referenced by a dataset contained within the DepMap portal.",
13
+ "DepMap_OmicsExpressionProteinCodingGenesTPMLogp1.csv": "Gene expression in TPMs for cancer cell lines, including all DepMap models.",
14
+ "ddinter_alimentary_tract_metabolism.csv": "Drug-drug interactions for alimentary tract and metabolism drugs from DDInter 2.0 database.",
15
+ "ddinter_antineoplastic.csv": "Drug-drug interactions for antineoplastic and immunomodulating agents from DDInter 2.0 database.",
16
+ "ddinter_antiparasitic.csv": "Drug-drug interactions for antiparasitic products from DDInter 2.0 database.",
17
+ "ddinter_blood_organs.csv": "Drug-drug interactions for blood and blood forming organs drugs from DDInter 2.0 database.",
18
+ "ddinter_dermatological.csv": "Drug-drug interactions for dermatological drugs from DDInter 2.0 database.",
19
+ "ddinter_hormonal.csv": "Drug-drug interactions for systemic hormonal preparations from DDInter 2.0 database.",
20
+ "ddinter_respiratory.csv": "Drug-drug interactions for respiratory system drugs from DDInter 2.0 database.",
21
+ "ddinter_various.csv": "Drug-drug interactions for various drugs from DDInter 2.0 database.",
22
+ "DisGeNET.parquet": "Gene-disease associations from multiple sources.",
23
+ "dosage_growth_defect.parquet": "Gene dosage changes affecting growth.",
24
+ "enamine_cloud_library_smiles.pkl": "Compounds from Enamine REAL library with SMILES annotations.",
25
+ "evebio_assay_table.csv": "Assay metadata with one row per assay from EveBio pharmome mapping.",
26
+ "evebio_bundle_table.csv": "Target subfamily bundles used for screening-to-profiling progression.",
27
+ "evebio_compound_table.csv": "Compound metadata with common identifiers from EveBio screening.",
28
+ "evebio_control_table.csv": "Control datapoints for all screening and profiling plates.",
29
+ "evebio_detailed_result_table.csv": "Expanded results on evebio_summary_result_table with curve fit parameters and phase categories.",
30
+ "evebio_observed_points_table.csv": "Raw observed datapoints from all screening and profiling experiments.",
31
+ "evebio_summary_result_table.csv": "Succinct summary of results for each assay-compound combination.",
32
+ "evebio_target_table.csv": "Target metadata with common identifiers from EveBio screening.",
33
+ "genebass_missense_LC_filtered.pkl": "Filtered missense variants from GeneBass.",
34
+ "genebass_pLoF_filtered.pkl": "Predicted loss-of-function variants from GeneBass.",
35
+ "genebass_synonymous_filtered.pkl": "Filtered synonymous variants from GeneBass.",
36
+ "gene_info.parquet": "Comprehensive gene information.",
37
+ "genetic_interaction.parquet": "Genetic interactions between genes.",
38
+ "go-plus.json": "Gene ontology data for functional gene annotations.",
39
+ "gtex_tissue_gene_tpm.parquet": "Gene expression (TPM) across human tissues from GTEx.",
40
+ "gwas_catalog.pkl": "Genome-wide association studies (GWAS) results.",
41
+ "hp.obo": "Official HPO release in obographs format",
42
+ "kg.csv": "Precision medicine knowledge graph with 17,080 diseases and 4+ million relationships across biological scales.",
43
+ "marker_celltype.parquet": "Cell type marker genes for identification.",
44
+ "McPAS-TCR.parquet": "T-cell receptor sequences and specificity data from McPAS database.",
45
+ "miRDB_v6.0_results.parquet": "Predicted microRNA targets from miRDB.",
46
+ "miRTarBase_microRNA_target_interaction.parquet": "Experimentally validated microRNA-target interactions from miRTarBase.",
47
+ "miRTarBase_microRNA_target_interaction_pubmed_abtract.txt": "PubMed abstracts for microRNA-target interactions in miRTarBase.",
48
+ "miRTarBase_MicroRNA_Target_Sites.parquet": "Binding sites of microRNAs on target genes from miRTarBase.",
49
+ "mousemine_m1_positional_geneset.parquet": "Positional gene sets from MouseMine.",
50
+ "mousemine_m2_curated_geneset.parquet": "Curated gene sets from MouseMine.",
51
+ "mousemine_m3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MouseMine.",
52
+ "mousemine_m5_ontology_geneset.parquet": "Ontology-based gene sets from MouseMine.",
53
+ "mousemine_m8_celltype_signature_geneset.parquet": "Cell type signature gene sets from MouseMine.",
54
+ "mousemine_mh_hallmark_geneset.parquet": "Hallmark gene sets from MouseMine.",
55
+ "msigdb_human_c1_positional_geneset.parquet": "Human positional gene sets from MSigDB.",
56
+ "msigdb_human_c2_curated_geneset.parquet": "Curated human gene sets from MSigDB.",
57
+ "msigdb_human_c3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MSigDB.",
58
+ "msigdb_human_c3_subset_transcription_factor_targets_from_GTRD.parquet": "Transcription factor targets from GTRD/MSigDB.",
59
+ "msigdb_human_c4_computational_geneset.parquet": "Computationally derived gene sets from MSigDB.",
60
+ "msigdb_human_c5_ontology_geneset.parquet": "Ontology-based gene sets from MSigDB.",
61
+ "msigdb_human_c6_oncogenic_signature_geneset.parquet": "Oncogenic signatures from MSigDB.",
62
+ "msigdb_human_c7_immunologic_signature_geneset.parquet": "Immunologic signatures from MSigDB.",
63
+ "msigdb_human_c8_celltype_signature_geneset.parquet": "Cell type signatures from MSigDB.",
64
+ "msigdb_human_h_hallmark_geneset.parquet": "Hallmark gene sets from MSigDB.",
65
+ "omim.parquet": "Genetic disorders and associated genes from OMIM.",
66
+ "proteinatlas.tsv": "Protein expression data from Human Protein Atlas.",
67
+ "proximity_label-ms.parquet": "Protein interactions via proximity labeling and mass spectrometry.",
68
+ "reconstituted_complex.parquet": "Protein complexes reconstituted in vitro.",
69
+ "sgRNA_KO_SP_mouse.txt": "sgRNA knockout data for mouse.",
70
+ "sgRNA_KO_SP_human.txt": "sgRNA knockout data for human.",
71
+ "synthetic_growth_defect.parquet": "Synthetic growth defects from genetic interactions.",
72
+ "synthetic_lethality.parquet": "Synthetic lethal interactions.",
73
+ "synthetic_rescue.parquet": "Genetic interactions rescuing phenotypes.",
74
+ "two-hybrid.parquet": "Protein-protein interactions detected by yeast two-hybrid assays.",
75
+ "variant_table.parquet": "Annotated genetic variants table.",
76
+ "Virus-Host_PPI_P-HIPSTER_2020.parquet": "Virus-host protein-protein interactions from P-HIPSTER.",
77
+ "txgnn_name_mapping.pkl": "Name mapping for TXGNN.",
78
+ "txgnn_prediction.pkl": "Prediction data for TXGNN.",
79
+ }
80
+
81
+ # Updated library_content as a dictionary with detailed descriptions
82
+ library_content_dict = {
83
+ # === PYTHON PACKAGES ===
84
+ # Core Bioinformatics Libraries (Python)
85
+ "biopython": "[Python Package] A set of tools for biological computation including parsers for bioinformatics files, access to online services, and interfaces to common bioinformatics programs.",
86
+ "biom-format": "[Python Package] The Biological Observation Matrix (BIOM) format is designed for representing biological sample by observation contingency tables with associated metadata.",
87
+ "scanpy": "[Python Package] A scalable toolkit for analyzing single-cell gene expression data, specifically designed for large datasets using AnnData.",
88
+ "scikit-bio": "[Python Package] Data structures, algorithms, and educational resources for bioinformatics, including sequence analysis, phylogenetics, and ordination methods.",
89
+ "anndata": "[Python Package] A Python package for handling annotated data matrices in memory and on disk, primarily used for single-cell genomics data.",
90
+ "mudata": "[Python Package] A Python package for multimodal data storage and manipulation, extending AnnData to handle multiple modalities.",
91
+ "pyliftover": "[Python Package] A Python implementation of UCSC liftOver tool for converting genomic coordinates between genome assemblies.",
92
+ "biopandas": "[Python Package] A package that provides pandas DataFrames for working with molecular structures and biological data.",
93
+ "biotite": "[Python Package] A comprehensive library for computational molecular biology, providing tools for sequence analysis, structure analysis, and more.",
94
+ "lazyslide": "[Python Package] A Python framework that brings interoperable, reproducible whole slide image analysis, enabling seamless histopathology workflows from preprocessing to deep learning.",
95
+ # Genomics & Variant Analysis (Python)
96
+ "gget": "[Python Package] A toolkit for accessing genomic databases and retrieving sequences, annotations, and other genomic data.",
97
+ "lifelines": "[Python Package] A complete survival analysis library for fitting models, plotting, and statistical tests.",
98
+ # "scvi-tools": "[Python Package] A package for probabilistic modeling of single-cell omics data, including deep generative models.",
99
+ "gseapy": "[Python Package] A Python wrapper for Gene Set Enrichment Analysis (GSEA) and visualization.",
100
+ "scrublet": "[Python Package] A tool for detecting doublets in single-cell RNA-seq data.",
101
+ "cellxgene-census": "[Python Package] A tool for accessing and analyzing the CellxGene Census, a collection of single-cell datasets. To download a dataset, use the download_source_h5ad function with the dataset id as the argument (856c1b98-5727-49da-bf0f-151bdb8cb056, no .h5ad extension).",
102
+ "hyperopt": "[Python Package] A Python library for optimizing hyperparameters of machine learning algorithms.",
103
+ "scvelo": "[Python Package] A tool for RNA velocity analysis in single cells using dynamical models.",
104
+ "pysam": "[Python Package] A Python module for reading, manipulating and writing genomic data sets in SAM/BAM/VCF/BCF formats.",
105
+ "pyfaidx": "[Python Package] A Python package for efficient random access to FASTA files.",
106
+ "pyranges": "[Python Package] A Python package for interval manipulation with a pandas-like interface.",
107
+ "pybedtools": "[Python Package] A Python wrapper for Aaron Quinlan's BEDTools programs.",
108
+ # "panhumanpy": "A Python package for hierarchical, cross-tissue cell type annotation of human single-cell RNA-seq data",
109
+ # Structural Biology & Drug Discovery (Python)
110
+ "rdkit": "[Python Package] A collection of cheminformatics and machine learning tools for working with chemical structures and drug discovery.",
111
+ "deeppurpose": "[Python Package] A deep learning library for drug-target interaction prediction and virtual screening.",
112
+ "pyscreener": "[Python Package] A Python package for virtual screening of chemical compounds.",
113
+ "openbabel": "[Python Package] A chemical toolbox designed to speak the many languages of chemical data, supporting file format conversion and molecular modeling.",
114
+ "descriptastorus": "[Python Package] A library for computing molecular descriptors for machine learning applications in drug discovery.",
115
+ # "pymol": "[Python Package] A molecular visualization system for rendering and animating 3D molecular structures.",
116
+ "openmm": "[Python Package] A toolkit for molecular simulation using high-performance GPU computing.",
117
+ "pytdc": "[Python Package] A Python package for Therapeutics Data Commons, providing access to machine learning datasets for drug discovery.",
118
+ # Data Science & Statistical Analysis (Python)
119
+ "pandas": "[Python Package] A fast, powerful, and flexible data analysis and manipulation library for Python.",
120
+ "numpy": "[Python Package] The fundamental package for scientific computing with Python, providing support for arrays, matrices, and mathematical functions.",
121
+ "scipy": "[Python Package] A Python library for scientific and technical computing, including modules for optimization, linear algebra, integration, and statistics.",
122
+ "scikit-learn": "[Python Package] A machine learning library featuring various classification, regression, and clustering algorithms.",
123
+ "matplotlib": "[Python Package] A comprehensive library for creating static, animated, and interactive visualizations in Python.",
124
+ "seaborn": "[Python Package] A statistical data visualization library based on matplotlib with a high-level interface for drawing attractive statistical graphics.",
125
+ "statsmodels": "[Python Package] A Python module for statistical modeling and econometrics, including descriptive statistics and estimation of statistical models.",
126
+ "pymc3": "[Python Package] A Python package for Bayesian statistical modeling and probabilistic machine learning.",
127
+ # "pystan": "[Python Package] A Python interface to Stan, a platform for statistical modeling and high-performance statistical computation.",
128
+ "umap-learn": "[Python Package] Uniform Manifold Approximation and Projection, a dimension reduction technique.",
129
+ "faiss-cpu": "[Python Package] A library for efficient similarity search and clustering of dense vectors.",
130
+ "harmony-pytorch": "[Python Package] A PyTorch implementation of the Harmony algorithm for integrating single-cell data.",
131
+ # General Bioinformatics & Computational Utilities (Python)
132
+ "tiledb": "[Python Package] A powerful engine for storing and analyzing large-scale genomic data.",
133
+ "tiledbsoma": "[Python Package] A library for working with the SOMA (Stack of Matrices) format using TileDB.",
134
+ "h5py": "[Python Package] A Python interface to the HDF5 binary data format, allowing storage of large amounts of numerical data.",
135
+ "tqdm": "[Python Package] A fast, extensible progress bar for loops and CLI applications.",
136
+ "joblib": "[Python Package] A set of tools to provide lightweight pipelining in Python, including transparent disk-caching and parallel computing.",
137
+ "opencv-python": "[Python Package] OpenCV library for computer vision tasks, useful for image analysis in biological contexts.",
138
+ "PyPDF2": "[Python Package] A library for working with PDF files, useful for extracting text from scientific papers.",
139
+ "googlesearch-python": "[Python Package] A library for performing Google searches programmatically.",
140
+ "scikit-image": "[Python Package] A collection of algorithms for image processing in Python.",
141
+ "pymed": "[Python Package] A Python library for accessing PubMed articles.",
142
+ "arxiv": "[Python Package] A Python wrapper for the arXiv API, allowing access to scientific papers.",
143
+ "scholarly": "[Python Package] A module to retrieve author and publication information from Google Scholar.",
144
+ "cryosparc-tools": "[Python Package] Tools for working with cryoSPARC, a platform for cryo-EM data processing.",
145
+ "mageck": "[Python Package] Analysis of CRISPR screen data.",
146
+ "igraph": "[Python Package] Network analysis and visualization.",
147
+ "pyscenic": "[Python Package] Analysis of single-cell RNA-seq data and gene regulatory networks.",
148
+ "cooler": "[Python Package] Storage and analysis of Hi-C data.",
149
+ "trackpy": "[Python Package] Particle tracking in images and video.",
150
+ "nnunet": "[Python Package] A deep learning framework for biomedical image segmentation, providing a standardized approach to training and inference.",
151
+ # "flowcytometrytools": "[Python Package] Analysis and visualization of flow cytometry data.",
152
+ "cellpose": "[Python Package] Cell segmentation in microscopy images.",
153
+ "viennarna": "[Python Package] RNA secondary structure prediction.",
154
+ "PyMassSpec": "[Python Package] Mass spectrometry data analysis.",
155
+ "python-libsbml": "[Python Package] Working with SBML files for computational biology.",
156
+ "cobra": "[Python Package] Constraint-based modeling of metabolic networks.",
157
+ "reportlab": "[Python Package] Creation of PDF documents.",
158
+ "flowkit": "[Python Package] Toolkit for processing flow cytometry data.",
159
+ "hmmlearn": "[Python Package] Hidden Markov model analysis.",
160
+ "msprime": "[Python Package] Simulation of genetic variation.",
161
+ "tskit": "[Python Package] Handling tree sequences and population genetics data.",
162
+ "cyvcf2": "[Python Package] Fast parsing of VCF files.",
163
+ "pykalman": "[Python Package] Kalman filter and smoother implementation.",
164
+ "fanc": "[Python Package] Analysis of chromatin conformation data.",
165
+ "loompy": "A Python implementation of the Loom file format for efficiently storing and working with large omics datasets.",
166
+ "pyBigWig": "A Python library for accessing bigWig and bigBed files for genome browser track data.",
167
+ "pymzml": "A Python module for high-throughput bioinformatics analysis of mass spectrometry data.",
168
+ "optlang": "A Python package for modeling optimization problems symbolically.",
169
+ "FlowIO": "A Python package for reading and writing flow cytometry data files.",
170
+ "FlowUtils": "Utilities for processing and analyzing flow cytometry data.",
171
+ "arboreto": "A Python package for inferring gene regulatory networks from single-cell RNA-seq data.",
172
+ "pdbfixer": "A Python package for fixing problems in PDB files in preparation for molecular simulations.",
173
+ # === R PACKAGES ===
174
+ # Core R Packages for Data Analysis
175
+ "ggplot2": "[R Package] A system for declaratively creating graphics, based on The Grammar of Graphics. Use with subprocess.run(['Rscript', '-e', 'library(ggplot2); ...']).",
176
+ "dplyr": "[R Package] A grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges. Use with subprocess.",
177
+ "tidyr": "[R Package] A package that helps you create tidy data, where each column is a variable, each row is an observation, and each cell is a single value. Use with subprocess.",
178
+ "readr": "[R Package] A fast and friendly way to read rectangular data like CSV, TSV, and FWF. Use with subprocess.run(['Rscript', '-e', 'library(readr); ...']).",
179
+ "stringr": "[R Package] A cohesive set of functions designed to make working with strings as easy as possible. Use with subprocess calls.",
180
+ "Matrix": "[R Package] A package that provides classes and methods for dense and sparse matrices. Required for Seurat. Use with subprocess calls.",
181
+ # "Rcpp": "[R Package] Seamless R and C++ Integration, allowing R functions to call compiled C++ code. Use with subprocess calls.",
182
+ # "devtools": "[R Package] Tools to make developing R packages easier, including functions to install packages from GitHub. Use with subprocess calls.",
183
+ # "remotes": "[R Package] Install R packages from GitHub, GitLab, Bitbucket, or other remote repositories. Use with subprocess calls.",
184
+ # Bioinformatics R Packages
185
+ "DESeq2": "[R Package] Differential gene expression analysis based on the negative binomial distribution. Use with subprocess.run(['Rscript', '-e', 'library(DESeq2); ...']).",
186
+ "clusterProfiler": "[R Package] A package for statistical analysis and visualization of functional profiles for genes and gene clusters. Use with subprocess calls.",
187
+ # "DADA2": "[R Package] A package for modeling and correcting Illumina-sequenced amplicon errors. Use with subprocess calls.",
188
+ # "xcms": "[R Package] A package for processing and visualization of LC-MS and GC-MS data. Use with subprocess calls.",
189
+ # "FlowCore": "[R Package] Basic infrastructure for flow cytometry data. Use with subprocess calls.",
190
+ "edgeR": "[R Package] Empirical Analysis of Digital Gene Expression Data in R, for differential expression analysis. Use with subprocess calls.",
191
+ "limma": "[R Package] Linear Models for Microarray Data, for differential expression analysis. Use with subprocess calls.",
192
+ "harmony": "[R Package] A method for integrating and analyzing single-cell data across datasets. Use with subprocess calls.",
193
+ "WGCNA": "[R Package] Weighted Correlation Network Analysis for studying biological networks. Use with subprocess calls.",
194
+ # === CLI TOOLS ===
195
+ # Sequence Analysis Tools
196
+ "samtools": "[CLI Tool] A suite of programs for interacting with high-throughput sequencing data. Use with subprocess.run(['samtools', ...]).",
197
+ "bowtie2": "[CLI Tool] An ultrafast and memory-efficient tool for aligning sequencing reads to long reference sequences. Use with subprocess.run(['bowtie2', ...]).",
198
+ "bwa": "[CLI Tool] Burrows-Wheeler Aligner for mapping low-divergent sequences against a large reference genome. Use with subprocess.run(['bwa', ...]).",
199
+ "bedtools": "[CLI Tool] A powerful toolset for genome arithmetic, allowing operations like intersect, merge, count, and complement on genomic features. Use with subprocess.run(['bedtools', ...]).",
200
+ "macs2": "[CLI Tool] Model-based Analysis of ChIP-Seq data, a tool for identifying transcript factor binding sites.",
201
+ # Quality Control and Processing Tools
202
+ "fastqc": "[CLI Tool] A quality control tool for high throughput sequence data. Use with subprocess.run(['fastqc', ...]).",
203
+ "trimmomatic": "[CLI Tool] A flexible read trimming tool for Illumina NGS data. Use with subprocess.run(['trimmomatic', ...]).",
204
+ # Multiple Sequence Alignment and Phylogenetics
205
+ "mafft": "[CLI Tool] A multiple sequence alignment program for unix-like operating systems. Use with subprocess.run(['mafft', ...]).",
206
+ "Homer": "[CLI Tool] Motif discovery and next-gen sequencing analysis.",
207
+ "FastTree": "[CLI Tool] Phylogenetic trees from sequence alignments.",
208
+ "muscle": "[CLI Tool] Multiple sequence alignment tool.",
209
+ # Genetic Analysis Tools
210
+ "plink": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink', ...]).",
211
+ "plink2": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink2', ...]).",
212
+ "gcta64": "[CLI Tool] Genome-wide Complex Trait Analysis (GCTA) tool for estimating the proportion of phenotypic variance explained by genome-wide SNPs and analyzing genetic relationships. Use with subprocess.run(['gcta64', ...]).",
213
+ "iqtree2": "[CLI Tool] An efficient phylogenetic software for maximum likelihood analysis with built-in model selection and ultrafast bootstrap. Use with subprocess.run(['iqtree2', ...]).",
214
+ "ADFR": "AutoDock for Receptors suite for molecular docking and virtual screening. ",
215
+ "diamond": "A sequence aligner for protein and translated DNA searches, designed for high performance analysis of big sequence data. ",
216
+ "fcsparser": "A command-line tool for parsing and analyzing flow cytometry standard (FCS) files. ",
217
+ "plannotate": "[CLI Tool] A tool for annotating plasmid sequences with common features. ",
218
+ "vina": "[CLI Tool] An open-source program for molecular docking and virtual screening, known for its speed and accuracy improvements over AutoDock 4.",
219
+ "autosite": "[CLI Tool] A binding site detection tool used to identify potential ligand binding pockets on protein structures for molecular docking.",
220
+ "PyLabRobot": "[Python Package] A Python package for controlling liquid-handling robots and other lab automation equipment.",
221
+ }
biomni/env_desc_cm.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data lake dictionary with detailed descriptions (Commercial Mode - Non-commercial datasets commented out)
2
+ data_lake_dict = {
3
+ "affinity_capture-ms.parquet": "Protein-protein interactions detected via affinity capture and mass spectrometry.",
4
+ "affinity_capture-rna.parquet": "Protein-RNA interactions detected by affinity capture.",
5
+ # "BindingDB_All_202409.tsv": "Measured binding affinities between proteins and small molecules for drug discovery.", # Requires commercial license
6
+ "broad_repurposing_hub_molecule_with_smiles.parquet": "Molecules from Broad Institute's Drug Repurposing Hub with SMILES annotations.",
7
+ "broad_repurposing_hub_phase_moa_target_info.parquet": "Drug phases, mechanisms of action, and target information from Broad Institute.",
8
+ "co-fractionation.parquet": "Protein-protein interactions from co-fractionation experiments.",
9
+ "czi_census_datasets_v4.parquet": "Datasets from the Chan Zuckerberg Initiative's Cell Census.",
10
+ "DepMap_CRISPRGeneDependency.csv": "Gene dependency probability estimates for cancer cell lines, including all DepMap models.",
11
+ "DepMap_CRISPRGeneEffect.csv": "Genome-wide CRISPR gene effect estimates for cancer cell lines, including all DepMap models.",
12
+ "DepMap_Model.csv": "Metadata describing all cancer models/cell lines which are referenced by a dataset contained within the DepMap portal.",
13
+ "DepMap_OmicsExpressionProteinCodingGenesTPMLogp1.csv": "Gene expression in TPMs for cancer cell lines, including all DepMap models.",
14
+ # "ddinter_alimentary_tract_metabolism.csv": "Drug-drug interactions for alimentary tract and metabolism drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
15
+ # "ddinter_antineoplastic.csv": "Drug-drug interactions for antineoplastic and immunomodulating agents from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
16
+ # "ddinter_antiparasitic.csv": "Drug-drug interactions for antiparasitic products from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
17
+ # "ddinter_blood_organs.csv": "Drug-drug interactions for blood and blood forming organs drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
18
+ # "ddinter_dermatological.csv": "Drug-drug interactions for dermatological drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
19
+ # "ddinter_hormonal.csv": "Drug-drug interactions for systemic hormonal preparations from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
20
+ # "ddinter_respiratory.csv": "Drug-drug interactions for respiratory system drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
21
+ # "ddinter_various.csv": "Drug-drug interactions for various drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only
22
+ # "DisGeNET.parquet": "Gene-disease associations from multiple sources.", # CC BY-NC-SA 4.0 - Non-commercial only
23
+ "dosage_growth_defect.parquet": "Gene dosage changes affecting growth.",
24
+ # "enamine_cloud_library_smiles.pkl": "Compounds from Enamine REAL library with SMILES annotations.", # Proprietary - Requires license
25
+ # "evebio_assay_table.csv": "Assay metadata with one row per assay from EveBio pharmome mapping.", # Proprietary - Requires permission
26
+ # "evebio_bundle_table.csv": "Target subfamily bundles used for screening-to-profiling progression.", # Proprietary - Requires permission
27
+ # "evebio_compound_table.csv": "Compound metadata with common identifiers from EveBio screening.", # Proprietary - Requires permission
28
+ # "evebio_control_table.csv": "Control datapoints for all screening and profiling plates.", # Proprietary - Requires permission
29
+ # "evebio_detailed_result_table.csv": "Expanded results on evebio_summary_result_table with curve fit parameters and phase categories.", # Proprietary - Requires permission
30
+ # "evebio_observed_points_table.csv": "Raw observed datapoints from all screening and profiling experiments.", # Proprietary - Requires permission
31
+ # "evebio_summary_result_table.csv": "Succinct summary of results for each assay-compound combination.", # Proprietary - Requires permission
32
+ # "evebio_target_table.csv": "Target metadata with common identifiers from EveBio screening.", # Proprietary - Requires permission
33
+ "genebass_missense_LC_filtered.pkl": "Filtered missense variants from GeneBass.",
34
+ "genebass_pLoF_filtered.pkl": "Predicted loss-of-function variants from GeneBass.",
35
+ "genebass_synonymous_filtered.pkl": "Filtered synonymous variants from GeneBass.",
36
+ "gene_info.parquet": "Comprehensive gene information.",
37
+ "genetic_interaction.parquet": "Genetic interactions between genes.",
38
+ "go-plus.json": "Gene ontology data for functional gene annotations.",
39
+ "gtex_tissue_gene_tpm.parquet": "Gene expression (TPM) across human tissues from GTEx.",
40
+ "gwas_catalog.pkl": "Genome-wide association studies (GWAS) results.",
41
+ "hp.obo": "Official HPO release in obographs format",
42
+ "kg.csv": "Precision medicine knowledge graph with 17,080 diseases and 4+ million relationships across biological scales.",
43
+ "marker_celltype.parquet": "Cell type marker genes for identification.",
44
+ # "McPAS-TCR.parquet": "T-cell receptor sequences and specificity data from McPAS database.", # CC BY-NC-SA 4.0 - Non-commercial only
45
+ # "miRDB_v6.0_results.parquet": "Predicted microRNA targets from miRDB.", # Non-commercial use only
46
+ # "miRTarBase_microRNA_target_interaction.parquet": "Experimentally validated microRNA-target interactions from miRTarBase.", # CC BY-NC 4.0 - Non-commercial only
47
+ # "miRTarBase_microRNA_target_interaction_pubmed_abtract.txt": "PubMed abstracts for microRNA-target interactions in miRTarBase.", # CC BY-NC 4.0 - Non-commercial only
48
+ # "miRTarBase_MicroRNA_Target_Sites.parquet": "Binding sites of microRNAs on target genes from miRTarBase.", # CC BY-NC 4.0 - Non-commercial only
49
+ "mousemine_m1_positional_geneset.parquet": "Positional gene sets from MouseMine.",
50
+ "mousemine_m2_curated_geneset.parquet": "Curated gene sets from MouseMine.",
51
+ "mousemine_m3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MouseMine.",
52
+ "mousemine_m5_ontology_geneset.parquet": "Ontology-based gene sets from MouseMine.",
53
+ "mousemine_m8_celltype_signature_geneset.parquet": "Cell type signature gene sets from MouseMine.",
54
+ "mousemine_mh_hallmark_geneset.parquet": "Hallmark gene sets from MouseMine.",
55
+ # "msigdb_human_c1_positional_geneset.parquet": "Human positional gene sets from MSigDB.", # Requires commercial license
56
+ # "msigdb_human_c2_curated_geneset.parquet": "Curated human gene sets from MSigDB.", # Requires commercial license
57
+ # "msigdb_human_c3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MSigDB.", # Requires commercial license
58
+ # "msigdb_human_c3_subset_transcription_factor_targets_from_GTRD.parquet": "Transcription factor targets from GTRD/MSigDB.", # Requires commercial license
59
+ # "msigdb_human_c4_computational_geneset.parquet": "Computationally derived gene sets from MSigDB.", # Requires commercial license
60
+ # "msigdb_human_c5_ontology_geneset.parquet": "Ontology-based gene sets from MSigDB.", # Requires commercial license
61
+ # "msigdb_human_c6_oncogenic_signature_geneset.parquet": "Oncogenic signatures from MSigDB.", # Requires commercial license
62
+ # "msigdb_human_c7_immunologic_signature_geneset.parquet": "Immunologic signatures from MSigDB.", # Requires commercial license
63
+ # "msigdb_human_c8_celltype_signature_geneset.parquet": "Cell type signatures from MSigDB.", # Requires commercial license
64
+ # "msigdb_human_h_hallmark_geneset.parquet": "Hallmark gene sets from MSigDB.", # Requires commercial license
65
+ # "omim.parquet": "Genetic disorders and associated genes from OMIM.", # Requires commercial license
66
+ "proteinatlas.tsv": "Protein expression data from Human Protein Atlas.",
67
+ "proximity_label-ms.parquet": "Protein interactions via proximity labeling and mass spectrometry.",
68
+ "reconstituted_complex.parquet": "Protein complexes reconstituted in vitro.",
69
+ "sgRNA_KO_SP_mouse.txt": "sgRNA knockout data for mouse.",
70
+ "sgRNA_KO_SP_human.txt": "sgRNA knockout data for human.",
71
+ "synthetic_growth_defect.parquet": "Synthetic growth defects from genetic interactions.",
72
+ "synthetic_lethality.parquet": "Synthetic lethal interactions.",
73
+ "synthetic_rescue.parquet": "Genetic interactions rescuing phenotypes.",
74
+ "two-hybrid.parquet": "Protein-protein interactions detected by yeast two-hybrid assays.",
75
+ "variant_table.parquet": "Annotated genetic variants table.",
76
+ "Virus-Host_PPI_P-HIPSTER_2020.parquet": "Virus-host protein-protein interactions from P-HIPSTER.",
77
+ "txgnn_name_mapping.pkl": "Name mapping for TXGNN.",
78
+ "txgnn_prediction.pkl": "Prediction data for TXGNN.",
79
+ }
80
+
81
+ # Updated library_content as a dictionary with detailed descriptions
82
+ library_content_dict = {
83
+ # === PYTHON PACKAGES ===
84
+ # Core Bioinformatics Libraries (Python)
85
+ "biopython": "[Python Package] A set of tools for biological computation including parsers for bioinformatics files, access to online services, and interfaces to common bioinformatics programs.",
86
+ "biom-format": "[Python Package] The Biological Observation Matrix (BIOM) format is designed for representing biological sample by observation contingency tables with associated metadata.",
87
+ "scanpy": "[Python Package] A scalable toolkit for analyzing single-cell gene expression data, specifically designed for large datasets using AnnData.",
88
+ "scikit-bio": "[Python Package] Data structures, algorithms, and educational resources for bioinformatics, including sequence analysis, phylogenetics, and ordination methods.",
89
+ "anndata": "[Python Package] A Python package for handling annotated data matrices in memory and on disk, primarily used for single-cell genomics data.",
90
+ "mudata": "[Python Package] A Python package for multimodal data storage and manipulation, extending AnnData to handle multiple modalities.",
91
+ "pyliftover": "[Python Package] A Python implementation of UCSC liftOver tool for converting genomic coordinates between genome assemblies.",
92
+ "biopandas": "[Python Package] A package that provides pandas DataFrames for working with molecular structures and biological data.",
93
+ "biotite": "[Python Package] A comprehensive library for computational molecular biology, providing tools for sequence analysis, structure analysis, and more.",
94
+ "lazyslide": "[Python Package] A Python framework that brings interoperable, reproducible whole slide image analysis, enabling seamless histopathology workflows from preprocessing to deep learning.",
95
+ # Genomics & Variant Analysis (Python)
96
+ "gget": "[Python Package] A toolkit for accessing genomic databases and retrieving sequences, annotations, and other genomic data.",
97
+ "lifelines": "[Python Package] A complete survival analysis library for fitting models, plotting, and statistical tests.",
98
+ # "scvi-tools": "[Python Package] A package for probabilistic modeling of single-cell omics data, including deep generative models.",
99
+ "gseapy": "[Python Package] A Python wrapper for Gene Set Enrichment Analysis (GSEA) and visualization.",
100
+ "scrublet": "[Python Package] A tool for detecting doublets in single-cell RNA-seq data.",
101
+ "cellxgene-census": "[Python Package] A tool for accessing and analyzing the CellxGene Census, a collection of single-cell datasets. To download a dataset, use the download_source_h5ad function with the dataset id as the argument (856c1b98-5727-49da-bf0f-151bdb8cb056, no .h5ad extension).",
102
+ "hyperopt": "[Python Package] A Python library for optimizing hyperparameters of machine learning algorithms.",
103
+ "scvelo": "[Python Package] A tool for RNA velocity analysis in single cells using dynamical models.",
104
+ "pysam": "[Python Package] A Python module for reading, manipulating and writing genomic data sets in SAM/BAM/VCF/BCF formats.",
105
+ "pyfaidx": "[Python Package] A Python package for efficient random access to FASTA files.",
106
+ "pyranges": "[Python Package] A Python package for interval manipulation with a pandas-like interface.",
107
+ "pybedtools": "[Python Package] A Python wrapper for Aaron Quinlan's BEDTools programs.",
108
+ # "panhumanpy": "A Python package for hierarchical, cross-tissue cell type annotation of human single-cell RNA-seq data",
109
+ # Structural Biology & Drug Discovery (Python)
110
+ "rdkit": "[Python Package] A collection of cheminformatics and machine learning tools for working with chemical structures and drug discovery.",
111
+ "deeppurpose": "[Python Package] A deep learning library for drug-target interaction prediction and virtual screening.",
112
+ "pyscreener": "[Python Package] A Python package for virtual screening of chemical compounds.",
113
+ "openbabel": "[Python Package] A chemical toolbox designed to speak the many languages of chemical data, supporting file format conversion and molecular modeling.",
114
+ "descriptastorus": "[Python Package] A library for computing molecular descriptors for machine learning applications in drug discovery.",
115
+ # "pymol": "[Python Package] A molecular visualization system for rendering and animating 3D molecular structures.",
116
+ "openmm": "[Python Package] A toolkit for molecular simulation using high-performance GPU computing.",
117
+ "pytdc": "[Python Package] A Python package for Therapeutics Data Commons, providing access to machine learning datasets for drug discovery.",
118
+ # Data Science & Statistical Analysis (Python)
119
+ "pandas": "[Python Package] A fast, powerful, and flexible data analysis and manipulation library for Python.",
120
+ "numpy": "[Python Package] The fundamental package for scientific computing with Python, providing support for arrays, matrices, and mathematical functions.",
121
+ "scipy": "[Python Package] A Python library for scientific and technical computing, including modules for optimization, linear algebra, integration, and statistics.",
122
+ "scikit-learn": "[Python Package] A machine learning library featuring various classification, regression, and clustering algorithms.",
123
+ "matplotlib": "[Python Package] A comprehensive library for creating static, animated, and interactive visualizations in Python.",
124
+ "seaborn": "[Python Package] A statistical data visualization library based on matplotlib with a high-level interface for drawing attractive statistical graphics.",
125
+ "statsmodels": "[Python Package] A Python module for statistical modeling and econometrics, including descriptive statistics and estimation of statistical models.",
126
+ "pymc3": "[Python Package] A Python package for Bayesian statistical modeling and probabilistic machine learning.",
127
+ # "pystan": "[Python Package] A Python interface to Stan, a platform for statistical modeling and high-performance statistical computation.",
128
+ "umap-learn": "[Python Package] Uniform Manifold Approximation and Projection, a dimension reduction technique.",
129
+ "faiss-cpu": "[Python Package] A library for efficient similarity search and clustering of dense vectors.",
130
+ "harmony-pytorch": "[Python Package] A PyTorch implementation of the Harmony algorithm for integrating single-cell data.",
131
+ # General Bioinformatics & Computational Utilities (Python)
132
+ "tiledb": "[Python Package] A powerful engine for storing and analyzing large-scale genomic data.",
133
+ "tiledbsoma": "[Python Package] A library for working with the SOMA (Stack of Matrices) format using TileDB.",
134
+ "h5py": "[Python Package] A Python interface to the HDF5 binary data format, allowing storage of large amounts of numerical data.",
135
+ "tqdm": "[Python Package] A fast, extensible progress bar for loops and CLI applications.",
136
+ "joblib": "[Python Package] A set of tools to provide lightweight pipelining in Python, including transparent disk-caching and parallel computing.",
137
+ "opencv-python": "[Python Package] OpenCV library for computer vision tasks, useful for image analysis in biological contexts.",
138
+ "PyPDF2": "[Python Package] A library for working with PDF files, useful for extracting text from scientific papers.",
139
+ "googlesearch-python": "[Python Package] A library for performing Google searches programmatically.",
140
+ "scikit-image": "[Python Package] A collection of algorithms for image processing in Python.",
141
+ "pymed": "[Python Package] A Python library for accessing PubMed articles.",
142
+ "arxiv": "[Python Package] A Python wrapper for the arXiv API, allowing access to scientific papers.",
143
+ "scholarly": "[Python Package] A module to retrieve author and publication information from Google Scholar.",
144
+ "cryosparc-tools": "[Python Package] Tools for working with cryoSPARC, a platform for cryo-EM data processing.",
145
+ "mageck": "[Python Package] Analysis of CRISPR screen data.",
146
+ "igraph": "[Python Package] Network analysis and visualization.",
147
+ "pyscenic": "[Python Package] Analysis of single-cell RNA-seq data and gene regulatory networks.",
148
+ "cooler": "[Python Package] Storage and analysis of Hi-C data.",
149
+ "trackpy": "[Python Package] Particle tracking in images and video.",
150
+ # "flowcytometrytools": "[Python Package] Analysis and visualization of flow cytometry data.",
151
+ "cellpose": "[Python Package] Cell segmentation in microscopy images.",
152
+ "viennarna": "[Python Package] RNA secondary structure prediction.",
153
+ "PyMassSpec": "[Python Package] Mass spectrometry data analysis.",
154
+ "python-libsbml": "[Python Package] Working with SBML files for computational biology.",
155
+ "cobra": "[Python Package] Constraint-based modeling of metabolic networks.",
156
+ "reportlab": "[Python Package] Creation of PDF documents.",
157
+ "flowkit": "[Python Package] Toolkit for processing flow cytometry data.",
158
+ "hmmlearn": "[Python Package] Hidden Markov model analysis.",
159
+ "msprime": "[Python Package] Simulation of genetic variation.",
160
+ "tskit": "[Python Package] Handling tree sequences and population genetics data.",
161
+ "cyvcf2": "[Python Package] Fast parsing of VCF files.",
162
+ "pykalman": "[Python Package] Kalman filter and smoother implementation.",
163
+ "fanc": "[Python Package] Analysis of chromatin conformation data.",
164
+ "loompy": "A Python implementation of the Loom file format for efficiently storing and working with large omics datasets.",
165
+ "pyBigWig": "A Python library for accessing bigWig and bigBed files for genome browser track data.",
166
+ "pymzml": "A Python module for high-throughput bioinformatics analysis of mass spectrometry data.",
167
+ "optlang": "A Python package for modeling optimization problems symbolically.",
168
+ "FlowIO": "A Python package for reading and writing flow cytometry data files.",
169
+ "FlowUtils": "Utilities for processing and analyzing flow cytometry data.",
170
+ "arboreto": "A Python package for inferring gene regulatory networks from single-cell RNA-seq data.",
171
+ "pdbfixer": "A Python package for fixing problems in PDB files in preparation for molecular simulations.",
172
+ # === R PACKAGES ===
173
+ # Core R Packages for Data Analysis
174
+ "ggplot2": "[R Package] A system for declaratively creating graphics, based on The Grammar of Graphics. Use with subprocess.run(['Rscript', '-e', 'library(ggplot2); ...']).",
175
+ "dplyr": "[R Package] A grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges. Use with subprocess.",
176
+ "tidyr": "[R Package] A package that helps you create tidy data, where each column is a variable, each row is an observation, and each cell is a single value. Use with subprocess.",
177
+ "readr": "[R Package] A fast and friendly way to read rectangular data like CSV, TSV, and FWF. Use with subprocess.run(['Rscript', '-e', 'library(readr); ...']).",
178
+ "stringr": "[R Package] A cohesive set of functions designed to make working with strings as easy as possible. Use with subprocess calls.",
179
+ "Matrix": "[R Package] A package that provides classes and methods for dense and sparse matrices. Required for Seurat. Use with subprocess calls.",
180
+ # "Rcpp": "[R Package] Seamless R and C++ Integration, allowing R functions to call compiled C++ code. Use with subprocess calls.",
181
+ # "devtools": "[R Package] Tools to make developing R packages easier, including functions to install packages from GitHub. Use with subprocess calls.",
182
+ # "remotes": "[R Package] Install R packages from GitHub, GitLab, Bitbucket, or other remote repositories. Use with subprocess calls.",
183
+ # Bioinformatics R Packages
184
+ "DESeq2": "[R Package] Differential gene expression analysis based on the negative binomial distribution. Use with subprocess.run(['Rscript', '-e', 'library(DESeq2); ...']).",
185
+ "clusterProfiler": "[R Package] A package for statistical analysis and visualization of functional profiles for genes and gene clusters. Use with subprocess calls.",
186
+ # "DADA2": "[R Package] A package for modeling and correcting Illumina-sequenced amplicon errors. Use with subprocess calls.",
187
+ # "xcms": "[R Package] A package for processing and visualization of LC-MS and GC-MS data. Use with subprocess calls.",
188
+ # "FlowCore": "[R Package] Basic infrastructure for flow cytometry data. Use with subprocess calls.",
189
+ "edgeR": "[R Package] Empirical Analysis of Digital Gene Expression Data in R, for differential expression analysis. Use with subprocess calls.",
190
+ "limma": "[R Package] Linear Models for Microarray Data, for differential expression analysis. Use with subprocess calls.",
191
+ "harmony": "[R Package] A method for integrating and analyzing single-cell data across datasets. Use with subprocess calls.",
192
+ "WGCNA": "[R Package] Weighted Correlation Network Analysis for studying biological networks. Use with subprocess calls.",
193
+ # === CLI TOOLS ===
194
+ # Sequence Analysis Tools
195
+ "samtools": "[CLI Tool] A suite of programs for interacting with high-throughput sequencing data. Use with subprocess.run(['samtools', ...]).",
196
+ "bowtie2": "[CLI Tool] An ultrafast and memory-efficient tool for aligning sequencing reads to long reference sequences. Use with subprocess.run(['bowtie2', ...]).",
197
+ "bwa": "[CLI Tool] Burrows-Wheeler Aligner for mapping low-divergent sequences against a large reference genome. Use with subprocess.run(['bwa', ...]).",
198
+ "bedtools": "[CLI Tool] A powerful toolset for genome arithmetic, allowing operations like intersect, merge, count, and complement on genomic features. Use with subprocess.run(['bedtools', ...]).",
199
+ "macs2": "[CLI Tool] Model-based Analysis of ChIP-Seq data, a tool for identifying transcript factor binding sites.",
200
+ # Quality Control and Processing Tools
201
+ "fastqc": "[CLI Tool] A quality control tool for high throughput sequence data. Use with subprocess.run(['fastqc', ...]).",
202
+ "trimmomatic": "[CLI Tool] A flexible read trimming tool for Illumina NGS data. Use with subprocess.run(['trimmomatic', ...]).",
203
+ # Multiple Sequence Alignment and Phylogenetics
204
+ "mafft": "[CLI Tool] A multiple sequence alignment program for unix-like operating systems. Use with subprocess.run(['mafft', ...]).",
205
+ "Homer": "[CLI Tool] Motif discovery and next-gen sequencing analysis.",
206
+ "FastTree": "[CLI Tool] Phylogenetic trees from sequence alignments.",
207
+ "muscle": "[CLI Tool] Multiple sequence alignment tool.",
208
+ # Genetic Analysis Tools
209
+ "plink": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink', ...]).",
210
+ "plink2": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink2', ...]).",
211
+ "gcta64": "[CLI Tool] Genome-wide Complex Trait Analysis (GCTA) tool for estimating the proportion of phenotypic variance explained by genome-wide SNPs and analyzing genetic relationships. Use with subprocess.run(['gcta64', ...]).",
212
+ "iqtree2": "[CLI Tool] An efficient phylogenetic software for maximum likelihood analysis with built-in model selection and ultrafast bootstrap. Use with subprocess.run(['iqtree2', ...]).",
213
+ "ADFR": "AutoDock for Receptors suite for molecular docking and virtual screening. ",
214
+ "diamond": "A sequence aligner for protein and translated DNA searches, designed for high performance analysis of big sequence data. ",
215
+ "fcsparser": "A command-line tool for parsing and analyzing flow cytometry standard (FCS) files. ",
216
+ "plannotate": "[CLI Tool] A tool for annotating plasmid sequences with common features. ",
217
+ "vina": "[CLI Tool] An open-source program for molecular docking and virtual screening, known for its speed and accuracy improvements over AutoDock 4.",
218
+ "autosite": "[CLI Tool] A binding site detection tool used to identify potential ligand binding pockets on protein structures for molecular docking.",
219
+ }
biomni/llm.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TYPE_CHECKING, Literal, Optional
3
+
4
+ from langchain_core.language_models.chat_models import BaseChatModel
5
+
6
+ if TYPE_CHECKING:
7
+ from biomni.config import BiomniConfig
8
+
9
+ SourceType = Literal["OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", "Groq", "Custom"]
10
+ ALLOWED_SOURCES: set[str] = set(SourceType.__args__)
11
+
12
+
13
+ def get_llm(
14
+ model: str | None = None,
15
+ temperature: float | None = None,
16
+ stop_sequences: list[str] | None = None,
17
+ source: SourceType | None = None,
18
+ base_url: str | None = None,
19
+ api_key: str | None = None,
20
+ config: Optional["BiomniConfig"] = None,
21
+ ) -> BaseChatModel:
22
+ """
23
+ Get a language model instance based on the specified model name and source.
24
+ This function supports models from OpenAI, Azure OpenAI, Anthropic, Ollama, Gemini, Bedrock, and custom model serving.
25
+ Args:
26
+ model (str): The model name to use
27
+ temperature (float): Temperature setting for generation
28
+ stop_sequences (list): Sequences that will stop generation
29
+ source (str): Source provider: "OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", or "Custom"
30
+ If None, will attempt to auto-detect from model name
31
+ base_url (str): The base URL for custom model serving (e.g., "http://localhost:8000/v1"), default is None
32
+ api_key (str): The API key for the custom llm
33
+ config (BiomniConfig): Optional configuration object. If provided, unspecified parameters will use config values
34
+ """
35
+ # Use config values for any unspecified parameters
36
+ if config is not None:
37
+ if model is None:
38
+ model = config.llm_model
39
+ if temperature is None:
40
+ temperature = config.temperature
41
+ if source is None:
42
+ source = config.source
43
+ if base_url is None:
44
+ base_url = config.base_url
45
+ if api_key is None:
46
+ api_key = config.api_key or "EMPTY"
47
+
48
+ # Use defaults if still not specified
49
+ if model is None:
50
+ model = "claude-3-5-sonnet-20241022"
51
+ if temperature is None:
52
+ temperature = 0.7
53
+ if api_key is None:
54
+ api_key = "EMPTY"
55
+ # Auto-detect source from model name if not specified
56
+ if source is None:
57
+ env_source = os.getenv("LLM_SOURCE")
58
+ if env_source in ALLOWED_SOURCES:
59
+ source = env_source
60
+ else:
61
+ if model[:7] == "claude-":
62
+ source = "Anthropic"
63
+ elif model[:7] == "gpt-oss":
64
+ source = "Ollama"
65
+ elif model[:4] == "gpt-":
66
+ source = "OpenAI"
67
+ elif model.startswith("azure-"):
68
+ source = "AzureOpenAI"
69
+ elif model[:7] == "gemini-":
70
+ source = "Gemini"
71
+ elif "groq" in model.lower():
72
+ source = "Groq"
73
+ elif base_url is not None:
74
+ source = "Custom"
75
+ elif "/" in model or any(
76
+ name in model.lower()
77
+ for name in [
78
+ "llama",
79
+ "mistral",
80
+ "qwen",
81
+ "gemma",
82
+ "phi",
83
+ "dolphin",
84
+ "orca",
85
+ "vicuna",
86
+ "deepseek",
87
+ ]
88
+ ):
89
+ source = "Ollama"
90
+ elif model.startswith(
91
+ ("anthropic.claude-", "amazon.titan-", "meta.llama-", "mistral.", "cohere.", "ai21.", "us.")
92
+ ):
93
+ source = "Bedrock"
94
+ else:
95
+ raise ValueError("Unable to determine model source. Please specify 'source' parameter.")
96
+
97
+ # Create appropriate model based on source
98
+ if source == "OpenAI":
99
+ try:
100
+ from langchain_openai import ChatOpenAI
101
+ except ImportError:
102
+ raise ImportError( # noqa: B904
103
+ "langchain-openai package is required for OpenAI models. Install with: pip install langchain-openai"
104
+ )
105
+ # Newer OpenAI models (e.g., gpt-5-*) require the Responses API and may reject
106
+ # legacy Chat Completions parameters like `stop`. Force Responses API when
107
+ # using gpt-5 models to avoid 400 errors such as: "Unsupported parameter: 'stop'".
108
+ use_responses = model.startswith("gpt-5")
109
+
110
+ if use_responses:
111
+ # Define a minimal subclass that drops the `stop` field when using the
112
+ # Responses API, since certain models (gpt-5-*) reject it entirely.
113
+ class _ChatOpenAIResponsesNoStop(ChatOpenAI):
114
+ def _get_request_payload(self, input_, *, stop=None, **kwargs): # type: ignore[override]
115
+ payload = super()._get_request_payload(input_, stop=stop, **kwargs)
116
+ try:
117
+ # If this call will use the Responses API, drop `stop` to avoid 400s.
118
+ if hasattr(self, "_use_responses_api") and self._use_responses_api(payload): # type: ignore[attr-defined]
119
+ payload.pop("stop", None)
120
+ # Also drop temperature for gpt-5 models as they only support default value
121
+ payload.pop("temperature", None)
122
+ except Exception:
123
+ # Be conservative: if anything goes wrong, still remove `stop` and `temperature`.
124
+ payload.pop("stop", None)
125
+ payload.pop("temperature", None)
126
+ return payload
127
+
128
+ return _ChatOpenAIResponsesNoStop(
129
+ model=model,
130
+ temperature=1, # Set to default value for gpt-5, will be removed in payload
131
+ stop_sequences=stop_sequences,
132
+ use_responses_api=True,
133
+ output_version="v0",
134
+ )
135
+ else:
136
+ return ChatOpenAI(
137
+ model=model,
138
+ temperature=temperature,
139
+ stop_sequences=stop_sequences,
140
+ )
141
+
142
+ elif source == "AzureOpenAI":
143
+ try:
144
+ from langchain_openai import AzureChatOpenAI
145
+ except ImportError:
146
+ raise ImportError( # noqa: B904
147
+ "langchain-openai package is required for Azure OpenAI models. Install with: pip install langchain-openai"
148
+ )
149
+ API_VERSION = "2024-12-01-preview"
150
+ model = model.replace("azure-", "")
151
+ return AzureChatOpenAI(
152
+ openai_api_key=os.getenv("OPENAI_API_KEY"),
153
+ azure_endpoint=os.getenv("OPENAI_ENDPOINT"),
154
+ azure_deployment=model,
155
+ openai_api_version=API_VERSION,
156
+ temperature=temperature,
157
+ )
158
+
159
+ elif source == "Anthropic":
160
+ try:
161
+ from langchain_anthropic import ChatAnthropic
162
+ except ImportError:
163
+ raise ImportError( # noqa: B904
164
+ "langchain-anthropic package is required for Anthropic models. Install with: pip install langchain-anthropic"
165
+ )
166
+
167
+ # Ensure ANTHROPIC_API_KEY is loaded from bash_profile if not in environment
168
+ if not os.environ.get("ANTHROPIC_API_KEY"):
169
+ try:
170
+ import subprocess
171
+
172
+ result = subprocess.run(
173
+ ["bash", "-c", "source ~/.bash_profile 2>/dev/null && echo $ANTHROPIC_API_KEY"],
174
+ capture_output=True,
175
+ text=True,
176
+ timeout=5,
177
+ )
178
+ if result.stdout.strip():
179
+ os.environ["ANTHROPIC_API_KEY"] = result.stdout.strip()
180
+ print("✓ Loaded ANTHROPIC_API_KEY from ~/.bash_profile")
181
+ except Exception as e:
182
+ print(f"Note: Could not load ANTHROPIC_API_KEY from bash_profile: {e}")
183
+
184
+ return ChatAnthropic(
185
+ model=model,
186
+ temperature=temperature,
187
+ max_tokens=8192,
188
+ stop_sequences=stop_sequences,
189
+ )
190
+
191
+ elif source == "Gemini":
192
+ # If you want to use ChatGoogleGenerativeAI, you need to pass the stop sequences upon invoking the model.
193
+ # return ChatGoogleGenerativeAI(
194
+ # model=model,
195
+ # temperature=temperature,
196
+ # google_api_key=api_key,
197
+ # )
198
+ try:
199
+ from langchain_openai import ChatOpenAI
200
+ except ImportError:
201
+ raise ImportError( # noqa: B904
202
+ "langchain-openai package is required for Gemini models. Install with: pip install langchain-openai"
203
+ )
204
+ return ChatOpenAI(
205
+ model=model,
206
+ temperature=temperature,
207
+ api_key=os.getenv("GEMINI_API_KEY"),
208
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
209
+ stop_sequences=stop_sequences,
210
+ )
211
+
212
+ elif source == "Groq":
213
+ try:
214
+ from langchain_openai import ChatOpenAI
215
+ except ImportError:
216
+ raise ImportError( # noqa: B904
217
+ "langchain-openai package is required for Groq models. Install with: pip install langchain-openai"
218
+ )
219
+ return ChatOpenAI(
220
+ model=model,
221
+ temperature=temperature,
222
+ api_key=os.getenv("GROQ_API_KEY"),
223
+ base_url="https://api.groq.com/openai/v1",
224
+ stop_sequences=stop_sequences,
225
+ )
226
+
227
+ elif source == "Ollama":
228
+ try:
229
+ from langchain_ollama import ChatOllama
230
+ except ImportError:
231
+ raise ImportError( # noqa: B904
232
+ "langchain-ollama package is required for Ollama models. Install with: pip install langchain-ollama"
233
+ )
234
+ return ChatOllama(
235
+ model=model,
236
+ temperature=temperature,
237
+ )
238
+
239
+ elif source == "Bedrock":
240
+ try:
241
+ from langchain_aws import ChatBedrock
242
+ except ImportError:
243
+ raise ImportError( # noqa: B904
244
+ "langchain-aws package is required for Bedrock models. Install with: pip install langchain-aws"
245
+ )
246
+ return ChatBedrock(
247
+ model=model,
248
+ temperature=temperature,
249
+ stop_sequences=stop_sequences,
250
+ region_name=os.getenv("AWS_REGION", "us-east-1"),
251
+ )
252
+
253
+ elif source == "Custom":
254
+ try:
255
+ from langchain_openai import ChatOpenAI
256
+ except ImportError:
257
+ raise ImportError( # noqa: B904
258
+ "langchain-openai package is required for custom models. Install with: pip install langchain-openai"
259
+ )
260
+ # Custom LLM serving such as SGLang. Must expose an openai compatible API.
261
+ assert base_url is not None, "base_url must be provided for customly served LLMs"
262
+ llm = ChatOpenAI(
263
+ model=model,
264
+ temperature=temperature,
265
+ max_tokens=8192,
266
+ stop_sequences=stop_sequences,
267
+ base_url=base_url,
268
+ api_key=api_key,
269
+ )
270
+ return llm
271
+
272
+ else:
273
+ raise ValueError(
274
+ f"Invalid source: {source}. Valid options are 'OpenAI', 'AzureOpenAI', 'Anthropic', 'Gemini', 'Groq', 'Bedrock', or 'Ollama'"
275
+ )
biomni/utils.py ADDED
@@ -0,0 +1,2366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import enum
3
+ import importlib
4
+ import json
5
+ import os
6
+ import pickle
7
+ import subprocess
8
+ import tempfile
9
+ import traceback
10
+ import zipfile
11
+ from typing import Any, ClassVar
12
+ from urllib.parse import urljoin
13
+
14
+ import pandas as pd
15
+ import requests
16
+ import tqdm # Add tqdm for progress bar
17
+ from langchain_core.callbacks import BaseCallbackHandler
18
+ from langchain_core.messages.base import get_msg_title_repr
19
+ from langchain_core.tools import StructuredTool
20
+ from langchain_core.utils.interactive_env import is_interactive_env
21
+ from pydantic import BaseModel, Field, ValidationError
22
+
23
+
24
+ # Add these new functions for running R code and CLI commands
25
+ def run_r_code(code: str) -> str:
26
+ """Run R code using subprocess.
27
+
28
+ Args:
29
+ code: R code to run
30
+
31
+ Returns:
32
+ Output of the R code
33
+
34
+ """
35
+ try:
36
+ # Create a temporary file to store the R code
37
+ with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as f:
38
+ f.write(code)
39
+ temp_file = f.name
40
+
41
+ # Run the R code using Rscript
42
+ result = subprocess.run(["Rscript", temp_file], capture_output=True, text=True, check=False)
43
+
44
+ # Clean up the temporary file
45
+ os.unlink(temp_file)
46
+
47
+ # Return the output
48
+ if result.returncode != 0:
49
+ return f"Error running R code:\n{result.stderr}"
50
+ else:
51
+ return result.stdout
52
+ except Exception as e:
53
+ return f"Error running R code: {str(e)}"
54
+
55
+
56
+ def run_bash_script(script: str) -> str:
57
+ """Run a Bash script using subprocess.
58
+
59
+ Args:
60
+ script: Bash script to run
61
+
62
+ Returns:
63
+ Output of the Bash script
64
+
65
+ Example:
66
+ This is how to use the function
67
+
68
+ .. code-block:: python
69
+
70
+ # Example of a complex Bash script
71
+ script = '''
72
+ #!/bin/bash
73
+
74
+ # Define variables
75
+ DATA_DIR="/path/to/data"
76
+ OUTPUT_FILE="results.txt"
77
+
78
+ # Create output directory if it doesn't exist
79
+ mkdir -p $(dirname $OUTPUT_FILE)
80
+
81
+ # Loop through files
82
+ for file in $DATA_DIR/*.txt; do
83
+ echo "Processing $file..."
84
+ # Count lines in each file
85
+ line_count=$(wc -l < $file)
86
+ echo "$file: $line_count lines" >> $OUTPUT_FILE
87
+ done
88
+
89
+ echo "Processing complete. Results saved to $OUTPUT_FILE"
90
+ '''
91
+ result = run_bash_script(script)
92
+ print(result)
93
+
94
+ """
95
+ try:
96
+ # Trim any leading/trailing whitespace
97
+ script = script.strip()
98
+
99
+ # If the script is empty, return an error
100
+ if not script:
101
+ return "Error: Empty script"
102
+
103
+ # Create a temporary file to store the Bash script
104
+ with tempfile.NamedTemporaryFile(suffix=".sh", mode="w", delete=False) as f:
105
+ # Add shebang if not present
106
+ if not script.startswith("#!/"):
107
+ f.write("#!/bin/bash\n")
108
+ # Add set -e to exit on error
109
+ if "set -e" not in script:
110
+ f.write("set -e\n")
111
+ f.write(script)
112
+ temp_file = f.name
113
+
114
+ # Make the script executable
115
+ os.chmod(temp_file, 0o755)
116
+
117
+ # Get current environment variables and working directory
118
+ env = os.environ.copy()
119
+ cwd = os.getcwd()
120
+
121
+ # Run the Bash script with the current environment and working directory
122
+ result = subprocess.run(
123
+ [temp_file],
124
+ shell=True,
125
+ capture_output=True,
126
+ text=True,
127
+ check=False,
128
+ env=env,
129
+ cwd=cwd,
130
+ )
131
+
132
+ # Clean up the temporary file
133
+ os.unlink(temp_file)
134
+
135
+ # Return the output
136
+ if result.returncode != 0:
137
+ traceback.print_stack()
138
+ print(result)
139
+ return f"Error running Bash script (exit code {result.returncode}):\n{result.stderr}"
140
+ else:
141
+ return result.stdout
142
+ except Exception as e:
143
+ traceback.print_exc()
144
+ return f"Error running Bash script: {str(e)}"
145
+
146
+
147
+ # Keep the run_cli_command for backward compatibility
148
+ def run_cli_command(command: str) -> str:
149
+ """Run a CLI command using subprocess.
150
+
151
+ Args:
152
+ command: CLI command to run
153
+
154
+ Returns:
155
+ Output of the CLI command
156
+
157
+ """
158
+ try:
159
+ # Trim any leading/trailing whitespace
160
+ command = command.strip()
161
+
162
+ # If the command is empty, return an error
163
+ if not command:
164
+ return "Error: Empty command"
165
+
166
+ # Split the command into a list of arguments, handling quoted arguments correctly
167
+ import shlex
168
+
169
+ args = shlex.split(command)
170
+
171
+ # Run the command
172
+ result = subprocess.run(args, capture_output=True, text=True, check=False)
173
+
174
+ # Return the output
175
+ if result.returncode != 0:
176
+ return f"Error running command '{command}':\n{result.stderr}"
177
+ else:
178
+ return result.stdout
179
+ except Exception as e:
180
+ return f"Error running command '{command}': {str(e)}"
181
+
182
+
183
+ def run_with_timeout(func, args=None, kwargs=None, timeout=600):
184
+ """Run a function with a timeout using threading instead of multiprocessing.
185
+ This allows variables to persist in the global namespace between function calls.
186
+ Returns the function result or a timeout error message.
187
+ """
188
+ if args is None:
189
+ args = []
190
+ if kwargs is None:
191
+ kwargs = {}
192
+
193
+ import ctypes
194
+ import queue
195
+ import threading
196
+
197
+ result_queue = queue.Queue()
198
+
199
+ def thread_func(func, args, kwargs, result_queue):
200
+ """Function to run in a separate thread."""
201
+ try:
202
+ result = func(*args, **kwargs)
203
+ result_queue.put(("success", result))
204
+ except Exception as e:
205
+ result_queue.put(("error", str(e)))
206
+
207
+ # Start a separate thread
208
+ thread = threading.Thread(target=thread_func, args=(func, args, kwargs, result_queue))
209
+ thread.daemon = True # Set as daemon so it will be killed when main thread exits
210
+ thread.start()
211
+
212
+ # Wait for the specified timeout
213
+ thread.join(timeout)
214
+
215
+ # Check if the thread is still running after timeout
216
+ if thread.is_alive():
217
+ print(f"TIMEOUT: Code execution timed out after {timeout} seconds")
218
+
219
+ # Unfortunately, there's no clean way to force terminate a thread in Python
220
+ # The recommended approach is to use daemon threads and let them be killed when main thread exits
221
+ # Here, we'll try to raise an exception in the thread to make it stop
222
+ try:
223
+ # Get thread ID and try to terminate it
224
+ thread_id = thread.ident
225
+ if thread_id:
226
+ # This is a bit dangerous and not 100% reliable
227
+ # It attempts to raise a SystemExit exception in the thread
228
+ res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), ctypes.py_object(SystemExit))
229
+ if res > 1:
230
+ # Oops, we raised too many exceptions
231
+ ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), None)
232
+ except Exception as e:
233
+ print(f"Error trying to terminate thread: {e}")
234
+
235
+ return f"ERROR: Code execution timed out after {timeout} seconds. Please try with simpler inputs or break your task into smaller steps."
236
+
237
+ # Get the result from the queue if available
238
+ try:
239
+ status, result = result_queue.get(block=False)
240
+ return result if status == "success" else f"Error in execution: {result}"
241
+ except queue.Empty:
242
+ return "Error: Execution completed but no result was returned"
243
+
244
+
245
+ class api_schema(BaseModel):
246
+ """api schema specification."""
247
+
248
+ api_schema: str | None = Field(description="The api schema as a dictionary")
249
+
250
+
251
+ def function_to_api_schema(function_string, llm):
252
+ prompt = """
253
+ Based on a code snippet and help me write an API docstring in the format like this:
254
+
255
+ {{'name': 'get_gene_set_enrichment',
256
+ 'description': 'Given a list of genes, identify a pathway that is enriched for this gene set. Return a list of pathway name, p-value, z-scores.',
257
+ 'required_parameters': [{{'name': 'genes',
258
+ 'type': 'List[str]',
259
+ 'description': 'List of g`ene symbols to analyze',
260
+ 'default': None}}],
261
+ 'optional_parameters': [{{'name': 'top_k',
262
+ 'type': 'int',
263
+ 'description': 'Top K pathways to return',
264
+ 'default': 10}}, {{'name': 'database',
265
+ 'type': 'str',
266
+ 'description': 'Name of the database to use for enrichment analysis',
267
+ 'default': "gene_ontology"}}]}}
268
+
269
+ Strictly follow the input from the function - don't create fake optional parameters.
270
+ For variable without default values, set them as None, not null.
271
+ For variable with boolean values, use capitalized True or False, not true or false.
272
+ Do not add any return type in the docstring.
273
+ Be as clear and succint as possible for the descriptions. Please do not make it overly verbose.
274
+ Here is the code snippet:
275
+ {code}
276
+ """
277
+ llm = llm.with_structured_output(api_schema)
278
+
279
+ for _ in range(7):
280
+ try:
281
+ api = llm.invoke(prompt.format(code=function_string)).dict()["api_schema"]
282
+ return ast.literal_eval(api) # -> prefer "default": None
283
+ # return json.loads(api) # -> prefer "default": null
284
+ except Exception as e:
285
+ print("API string:", api)
286
+ print("Error parsing the API string:", e)
287
+ continue
288
+
289
+ return "Error: Could not parse the API schema"
290
+ # return
291
+
292
+
293
+ def get_all_functions_from_file(file_path):
294
+ with open(file_path) as file:
295
+ file_content = file.read()
296
+
297
+ # Parse the file content into an AST (Abstract Syntax Tree)
298
+ tree = ast.parse(file_content)
299
+
300
+ # List to hold the top-level functions as strings
301
+ functions = []
302
+
303
+ # Walk through the AST nodes
304
+ for node in tree.body: # Only consider top-level nodes in the body
305
+ if isinstance(node, ast.FunctionDef): # Check if the node is a function definition
306
+ # Skip if function name starts with underscore
307
+ if node.name.startswith("_"):
308
+ continue
309
+
310
+ start_line = node.lineno - 1 # Get the starting line of the function
311
+ end_line = node.end_lineno # Get the ending line of the function (only available in Python 3.8+)
312
+ func_code = file_content.splitlines()[start_line:end_line]
313
+ functions.append("\n".join(func_code)) # Join lines of the function and add to the list
314
+
315
+ return functions
316
+
317
+
318
+ def write_python_code(request: str):
319
+ from langchain_anthropic import ChatAnthropic
320
+ from langchain_core.output_parsers import StrOutputParser
321
+ from langchain_core.prompts import ChatPromptTemplate
322
+
323
+ model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
324
+ template = """Write some python code to solve the user's problem.
325
+
326
+ Return only python code in Markdown format, e.g.:
327
+
328
+ ```python
329
+ ....
330
+ ```"""
331
+ prompt = ChatPromptTemplate.from_messages([("system", template), ("human", "{input}")])
332
+
333
+ def _sanitize_output(text: str):
334
+ _, after = text.split("```python")
335
+ return after.split("```")[0]
336
+
337
+ chain = prompt | model | StrOutputParser() | _sanitize_output
338
+ return chain.invoke({"input": "write a code that " + request})
339
+
340
+
341
+ def execute_graphql_query(
342
+ query: str,
343
+ variables: dict,
344
+ api_address: str = "https://api.genetics.opentargets.org/graphql",
345
+ ) -> dict:
346
+ """Executes a GraphQL query with variables and returns the data as a dictionary."""
347
+ headers = {"Content-Type": "application/json"}
348
+ response = requests.post(api_address, json={"query": query, "variables": variables}, headers=headers)
349
+ if response.status_code == 200:
350
+ return response.json()
351
+ else:
352
+ print(response.text)
353
+ response.raise_for_status()
354
+
355
+
356
+ def get_tool_decorated_functions(relative_path):
357
+ import ast
358
+ import importlib.util
359
+ import os
360
+
361
+ # Get the directory of the current file (__init__.py)
362
+ current_dir = os.path.dirname(os.path.abspath(__file__))
363
+
364
+ # Construct the absolute path from the relative path
365
+ file_path = os.path.join(current_dir, relative_path)
366
+
367
+ with open(file_path) as file:
368
+ tree = ast.parse(file.read(), filename=file_path)
369
+
370
+ tool_function_names = []
371
+
372
+ for node in ast.walk(tree):
373
+ if isinstance(node, ast.FunctionDef):
374
+ for decorator in node.decorator_list:
375
+ if (
376
+ isinstance(decorator, ast.Name)
377
+ and decorator.id == "tool"
378
+ or (
379
+ isinstance(decorator, ast.Call)
380
+ and isinstance(decorator.func, ast.Name)
381
+ and decorator.func.id == "tool"
382
+ )
383
+ ):
384
+ tool_function_names.append(node.name)
385
+
386
+ # Calculate the module name from the relative path
387
+ package_path = os.path.relpath(file_path, start=current_dir)
388
+ module_name = package_path.replace(os.path.sep, ".").rsplit(".", 1)[0]
389
+
390
+ # Import the module and get the function objects
391
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
392
+ module = importlib.util.module_from_spec(spec)
393
+ spec.loader.exec_module(module)
394
+
395
+ tool_functions = [getattr(module, name) for name in tool_function_names]
396
+
397
+ return tool_functions
398
+
399
+
400
+ def process_bio_retrieval_ducoment(documents_df):
401
+ ir_corpus = {}
402
+ corpus2tool = {}
403
+ for row in documents_df.itertuples():
404
+ doc = row.document_content
405
+ ir_corpus[row.docid] = (
406
+ (doc.get("name", "") or "")
407
+ + ", "
408
+ + (doc.get("description", "") or "")
409
+ + ", "
410
+ + (doc.get("url", "") or "")
411
+ + ", "
412
+ + ", required_params: "
413
+ + json.dumps(doc.get("required_parameters", ""))
414
+ + ", optional_params: "
415
+ + json.dumps(doc.get("optional_parameters", ""))
416
+ )
417
+
418
+ corpus2tool[
419
+ (doc.get("name", "") or "")
420
+ + ", "
421
+ + (doc.get("description", "") or "")
422
+ + ", "
423
+ + (doc.get("url", "") or "")
424
+ + ", "
425
+ + ", required_params: "
426
+ + json.dumps(doc.get("required_parameters", ""))
427
+ + ", optional_params: "
428
+ + json.dumps(doc.get("optional_parameters", ""))
429
+ ] = doc["name"]
430
+ return ir_corpus, corpus2tool
431
+
432
+
433
+ def load_pickle(file):
434
+ import pickle
435
+
436
+ with open(file, "rb") as f:
437
+ return pickle.load(f)
438
+
439
+
440
+ def pretty_print(message, printout=True):
441
+ if isinstance(message, tuple):
442
+ title = message
443
+ elif isinstance(message.content, list):
444
+ title = get_msg_title_repr(message.type.title().upper() + " Message", bold=is_interactive_env())
445
+ if message.name is not None:
446
+ title += f"\nName: {message.name}"
447
+
448
+ for i in message.content:
449
+ if i["type"] == "text":
450
+ title += f"\n{i['text']}\n"
451
+ elif i["type"] == "tool_use":
452
+ title += f"\nTool: {i['name']}"
453
+ title += f"\nInput: {i['input']}"
454
+ if printout:
455
+ print(f"{title}")
456
+ else:
457
+ title = get_msg_title_repr(message.type.title() + " Message", bold=is_interactive_env())
458
+ if message.name is not None:
459
+ title += f"\nName: {message.name}"
460
+ title += f"\n\n{message.content}"
461
+ if printout:
462
+ print(f"{title}")
463
+ return title
464
+
465
+
466
+ class CustomBaseModel(BaseModel):
467
+ api_schema: ClassVar[dict] = None # Class variable to store api_schema
468
+
469
+ # Add model_config with arbitrary_types_allowed=True
470
+ model_config = {"arbitrary_types_allowed": True}
471
+
472
+ @classmethod
473
+ def set_api_schema(cls, schema: dict):
474
+ cls.api_schema = schema
475
+
476
+ @classmethod
477
+ def model_validate(cls, obj):
478
+ try:
479
+ return super().model_validate(obj)
480
+ except (ValidationError, AttributeError) as e:
481
+ if not cls.api_schema:
482
+ raise e # If no api_schema is set, raise original error
483
+
484
+ error_msg = "Required Parameters:\n"
485
+ for param in cls.api_schema["required_parameters"]:
486
+ error_msg += f"- {param['name']} ({param['type']}): {param['description']}\n"
487
+
488
+ error_msg += "\nErrors:\n"
489
+ for err in e.errors():
490
+ field = err["loc"][0] if err["loc"] else "input"
491
+ error_msg += f"- {field}: {err['msg']}\n"
492
+
493
+ if not obj:
494
+ error_msg += "\nNo input provided"
495
+ else:
496
+ error_msg += "\nProvided Input:\n"
497
+ for key, value in obj.items():
498
+ error_msg += f"- {key}: {value}\n"
499
+
500
+ missing_params = {param["name"] for param in cls.api_schema["required_parameters"]} - set(obj.keys())
501
+ if missing_params:
502
+ error_msg += "\nMissing Parameters:\n"
503
+ for param in missing_params:
504
+ error_msg += f"- {param}\n"
505
+
506
+ # # Create proper validation error structure
507
+ raise ValidationError.from_exception_data(
508
+ title="Validation Error",
509
+ line_errors=[
510
+ {
511
+ "type": "value_error",
512
+ "loc": ("input",),
513
+ "input": obj,
514
+ "ctx": {
515
+ "error": error_msg,
516
+ },
517
+ }
518
+ ],
519
+ ) from None
520
+
521
+
522
+ def safe_execute_decorator(func):
523
+ def wrapper(*args, **kwargs):
524
+ try:
525
+ return func(*args, **kwargs)
526
+ except Exception as e:
527
+ return str(e)
528
+
529
+ return wrapper
530
+
531
+
532
+ def api_schema_to_langchain_tool(api_schema, mode="generated_tool", module_name=None):
533
+ if mode == "generated_tool":
534
+ module = importlib.import_module("biomni.tool.generated_tool." + api_schema["tool_name"] + ".api")
535
+ elif mode == "custom_tool":
536
+ module = importlib.import_module(module_name)
537
+
538
+ api_function = getattr(module, api_schema["name"])
539
+ api_function = safe_execute_decorator(api_function)
540
+
541
+ # Define a mapping from string type names to actual Python type objects
542
+ type_mapping = {
543
+ "string": str,
544
+ "integer": int,
545
+ "boolean": bool,
546
+ "pandas": pd.DataFrame, # Use the imported pandas.DataFrame directly
547
+ "str": str,
548
+ "int": int,
549
+ "bool": bool,
550
+ "List[str]": list[str],
551
+ "List[int]": list[int],
552
+ "Dict": dict,
553
+ "Any": Any,
554
+ }
555
+
556
+ # Create the fields and annotations
557
+ annotations = {}
558
+ for param in api_schema["required_parameters"]:
559
+ param_type = param["type"]
560
+ if param_type in type_mapping:
561
+ annotations[param["name"]] = type_mapping[param_type]
562
+ else:
563
+ # For types not in the mapping, try a safer approach than direct eval
564
+ try:
565
+ annotations[param["name"]] = eval(param_type)
566
+ except (NameError, SyntaxError):
567
+ # Default to Any for unknown types
568
+ annotations[param["name"]] = Any
569
+
570
+ fields = {param["name"]: Field(description=param["description"]) for param in api_schema["required_parameters"]}
571
+
572
+ # Create the ApiInput class dynamically
573
+ ApiInput = type("Input", (CustomBaseModel,), {"__annotations__": annotations, **fields})
574
+ # Set the api_schema
575
+ ApiInput.set_api_schema(api_schema)
576
+
577
+ # Create the StructuredTool
578
+ api_tool = StructuredTool.from_function(
579
+ func=api_function,
580
+ name=api_schema["name"],
581
+ description=api_schema["description"],
582
+ args_schema=ApiInput,
583
+ return_direct=True,
584
+ )
585
+
586
+ return api_tool
587
+
588
+
589
+ class ID(enum.Enum):
590
+ ENTREZ = "Entrez"
591
+ ENSEMBL = "Ensembl without version" # e.g. ENSG00000123374
592
+ ENSEMBL_W_VERSION = "Ensembl with version" # e.g. ENSG00000123374.10 (needed for GTEx)
593
+
594
+
595
+ def get_gene_id(gene_symbol: str, id_type: ID):
596
+ """Get the ID for a gene symbol. If no match found, returns None."""
597
+ if id_type == ID.ENTREZ:
598
+ return _get_gene_id_entrez(gene_symbol)
599
+ elif id_type == ID.ENSEMBL:
600
+ return _get_gene_id_ensembl(gene_symbol)
601
+ elif id_type == ID.ENSEMBL_W_VERSION:
602
+ return _get_gene_id_ensembl_with_version(gene_symbol)
603
+ else:
604
+ raise ValueError(f"ID type {id_type} not supported")
605
+
606
+
607
+ def _get_gene_id_entrez(gene_symbol: str):
608
+ """Get the Entrez ID for a gene symbol. If no match found, returns None
609
+ e.g. 1017 (CDK2).
610
+ """
611
+ api_call = f"https://mygene.info/v3/query?species=human&q=symbol:{gene_symbol}"
612
+ response = requests.get(api_call)
613
+ response_json = response.json()
614
+
615
+ if len(response_json["hits"]) == 0:
616
+ return None
617
+ else:
618
+ return response_json["hits"][0]["entrezgene"]
619
+
620
+
621
+ def _get_gene_id_ensembl(gene_symbol):
622
+ """Get the Ensembl ID for a gene symbol. If no match found, returns None
623
+ e.g. ENSG00000123374.
624
+ """
625
+ api_call = f"https://mygene.info/v3/query?species=human&fields=ensembl&q=symbol:{gene_symbol}"
626
+ response = requests.get(api_call)
627
+ response_json = response.json()
628
+
629
+ if len(response_json["hits"]) == 0:
630
+ return None
631
+ else:
632
+ ensembl = response_json["hits"][0]["ensembl"]
633
+ if isinstance(ensembl, list):
634
+ return ensembl[0][
635
+ "gene"
636
+ ] # Sometimes returns a list, for example RNH1 (first elem is on chr11, second is on scaffold_hschr11)
637
+ else:
638
+ return ensembl["gene"]
639
+
640
+
641
+ def _get_gene_id_ensembl_with_version(gene_symbol):
642
+ """Get the Ensembl ID for a gene symbol. If no match found, returns None
643
+ e.g. ENSG00000123374.10.
644
+ """
645
+ api_base = "https://gtexportal.org/api/v2/reference/gene"
646
+ params = {"geneId": gene_symbol}
647
+ response_json = requests.get(api_base, params=params).json()
648
+
649
+ if len(response_json["data"]) == 0:
650
+ return None
651
+ else:
652
+ return response_json["data"][0]["gencodeId"]
653
+
654
+
655
+ def save_pkl(f, filename):
656
+ with open(filename, "wb") as file:
657
+ pickle.dump(f, file)
658
+
659
+
660
+ def load_pkl(filename):
661
+ with open(filename, "rb") as file:
662
+ return pickle.load(file)
663
+
664
+
665
+ _TEXT_COLOR_MAPPING = {
666
+ "blue": "36;1",
667
+ "yellow": "33;1",
668
+ "pink": "38;5;200",
669
+ "green": "32;1",
670
+ "red": "31;1",
671
+ }
672
+
673
+
674
+ def color_print(text, color="blue"):
675
+ color_str = _TEXT_COLOR_MAPPING[color]
676
+ print(f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m")
677
+
678
+
679
+ class PromptLogger(BaseCallbackHandler):
680
+ def on_chat_model_start(self, serialized, messages, **kwargs):
681
+ for message in messages[0]:
682
+ color_print(message.pretty_repr(), color="green")
683
+
684
+
685
+ class NodeLogger(BaseCallbackHandler):
686
+ def on_llm_end(self, response, **kwargs): # response of type LLMResult
687
+ for generations in response.generations: # response.generations of type List[List[Generations]] becuase "each input could have multiple candidate generations"
688
+ for generation in generations:
689
+ generated_text = generation.message.content
690
+ # token_usage = generation.message.response_metadata["token_usage"]
691
+ color_print(generated_text, color="yellow")
692
+
693
+ def on_agent_action(self, action, **kwargs):
694
+ color_print(action.log, color="pink")
695
+
696
+ def on_agent_finish(self, finish, **kwargs):
697
+ color_print(finish, color="red")
698
+
699
+ def on_tool_start(self, serialized, input_str, **kwargs):
700
+ tool_name = serialized.get("name")
701
+ color_print(f"Calling {tool_name} with inputs: {input_str}", color="pink")
702
+
703
+ def on_tool_end(self, output, **kwargs):
704
+ output = str(output)
705
+ color_print(output, color="blue")
706
+
707
+
708
+ def check_or_create_path(path=None):
709
+ # Set a default path if none is provided
710
+ if path is None:
711
+ path = os.path.join(os.getcwd(), "tmp_directory")
712
+
713
+ # Check if the path exists
714
+ if not os.path.exists(path):
715
+ # If it doesn't exist, create the directory
716
+ os.makedirs(path)
717
+ print(f"Directory created at: {path}")
718
+ else:
719
+ print(f"Directory already exists at: {path}")
720
+
721
+ return path
722
+
723
+
724
+ def langchain_to_gradio_message(message):
725
+ # Build the title and content based on the message type
726
+ if isinstance(message.content, list):
727
+ # For a message with multiple content items (like text and tool use)
728
+ gradio_messages = []
729
+ for item in message.content:
730
+ gradio_message = {
731
+ "role": "user" if message.type == "human" else "assistant",
732
+ "content": "",
733
+ "metadata": {},
734
+ }
735
+
736
+ if item["type"] == "text":
737
+ item["text"] = item["text"].replace("<think>", "\n")
738
+ item["text"] = item["text"].replace("</think>", "\n")
739
+ gradio_message["content"] += f"{item['text']}\n"
740
+ gradio_messages.append(gradio_message)
741
+ elif item["type"] == "tool_use":
742
+ if item["name"] == "run_python_repl":
743
+ gradio_message["metadata"]["title"] = "🛠️ Writing code..."
744
+ # input = "```python {code_block}```\n".format(code_block=item['input']["command"])
745
+ gradio_message["metadata"]["log"] = "Executing Code block..."
746
+ gradio_message["content"] = f"##### Code: \n ```python \n {item['input']['command']} \n``` \n"
747
+ else:
748
+ gradio_message["metadata"]["title"] = f"🛠️ Used tool ```{item['name']}```"
749
+ to_print = ";".join([i + ": " + str(j) for i, j in item["input"].items()])
750
+ gradio_message["metadata"]["log"] = f"🔍 Input -- {to_print}\n"
751
+ gradio_message["metadata"]["status"] = "pending"
752
+ gradio_messages.append(gradio_message)
753
+
754
+ else:
755
+ gradio_message = {
756
+ "role": "user" if message.type == "human" else "assistant",
757
+ "content": "",
758
+ "metadata": {},
759
+ }
760
+ print(message)
761
+ content = message.content
762
+ content = content.replace("<think>", "\n")
763
+ content = content.replace("</think>", "\n")
764
+ content = content.replace("<solution>", "\n")
765
+ content = content.replace("</solution>", "\n")
766
+
767
+ gradio_message["content"] = content
768
+ gradio_messages = [gradio_message]
769
+ return gradio_messages
770
+
771
+
772
+ def parse_hpo_obo(file_path):
773
+ """Parse the HPO OBO file and create a dictionary mapping HP IDs to phenotype descriptions.
774
+
775
+ Args:
776
+ file_path (str): Path to the HPO OBO file.
777
+
778
+ Returns:
779
+ dict: A dictionary where keys are HP IDs and values are phenotype descriptions.
780
+
781
+ """
782
+ hp_dict = {}
783
+ current_id = None
784
+ current_name = None
785
+
786
+ with open(file_path) as file:
787
+ for line in file:
788
+ line = line.strip()
789
+ if line.startswith("[Term]"):
790
+ # If a new term block starts, save the previous term
791
+ if current_id and current_name:
792
+ hp_dict[current_id] = current_name
793
+ current_id = None
794
+ current_name = None
795
+ elif line.startswith("id: HP:"):
796
+ current_id = line.split(": ")[1]
797
+ elif line.startswith("name:"):
798
+ current_name = line.split(": ", 1)[1]
799
+
800
+ # Add the last term to the dictionary
801
+ if current_id and current_name:
802
+ hp_dict[current_id] = current_name
803
+
804
+ return hp_dict
805
+
806
+
807
+ def textify_api_dict(api_dict):
808
+ """Convert a nested API dictionary to a nicely formatted string."""
809
+ lines = []
810
+ for category, methods in api_dict.items():
811
+ lines.append(f"Import file: {category}")
812
+ lines.append("=" * (len("Import file: ") + len(category)))
813
+ for method in methods:
814
+ lines.append(f"Method: {method.get('name', 'N/A')}")
815
+ lines.append(f" Description: {method.get('description', 'No description provided.')}")
816
+
817
+ # Process required parameters
818
+ req_params = method.get("required_parameters", [])
819
+ if req_params:
820
+ lines.append(" Required Parameters:")
821
+ for param in req_params:
822
+ param_name = param.get("name", "N/A")
823
+ param_type = param.get("type", "N/A")
824
+ param_desc = param.get("description", "No description")
825
+ param_default = param.get("default", "None")
826
+ lines.append(f" - {param_name} ({param_type}): {param_desc} [Default: {param_default}]")
827
+
828
+ # Process optional parameters
829
+ opt_params = method.get("optional_parameters", [])
830
+ if opt_params:
831
+ lines.append(" Optional Parameters:")
832
+ for param in opt_params:
833
+ param_name = param.get("name", "N/A")
834
+ param_type = param.get("type", "N/A")
835
+ param_desc = param.get("description", "No description")
836
+ param_default = param.get("default", "None")
837
+ lines.append(f" - {param_name} ({param_type}): {param_desc} [Default: {param_default}]")
838
+
839
+ lines.append("") # Empty line between methods
840
+ lines.append("") # Extra empty line after each category
841
+
842
+ return "\n".join(lines)
843
+
844
+
845
+ def read_module2api():
846
+ fields = [
847
+ "literature",
848
+ "biochemistry",
849
+ "bioimaging",
850
+ "bioengineering",
851
+ "biophysics",
852
+ "glycoengineering",
853
+ "cancer_biology",
854
+ "cell_biology",
855
+ "molecular_biology",
856
+ "genetics",
857
+ "genomics",
858
+ "immunology",
859
+ "microbiology",
860
+ "pathology",
861
+ "pharmacology",
862
+ "physiology",
863
+ "synthetic_biology",
864
+ "systems_biology",
865
+ "support_tools",
866
+ "database",
867
+ "lab_automation",
868
+ "protocols",
869
+ ]
870
+
871
+ module2api = {}
872
+ for field in fields:
873
+ module_name = f"biomni.tool.tool_description.{field}"
874
+ module = importlib.import_module(module_name)
875
+ module2api[f"biomni.tool.{field}"] = module.description
876
+ return module2api
877
+
878
+
879
+ def download_and_unzip(url: str, dest_dir: str) -> str:
880
+ """Download a zip file from a URL and extract it to the destination directory.
881
+
882
+ Args:
883
+ url: The URL to download the zip file from.
884
+ dest_dir: The directory to extract the contents to.
885
+
886
+ Returns:
887
+ The path to the extracted directory, or an error message.
888
+
889
+ """
890
+ try:
891
+ os.makedirs(dest_dir, exist_ok=True)
892
+ print(f"Downloading from {url} ...")
893
+ with requests.get(url, stream=True) as r:
894
+ r.raise_for_status()
895
+ total_size = int(r.headers.get("content-length", 0))
896
+ chunk_size = 8192
897
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
898
+ with tqdm.tqdm(
899
+ total=total_size / (1024**3),
900
+ unit="GB",
901
+ unit_scale=True,
902
+ desc="Downloading",
903
+ ncols=80,
904
+ ) as pbar:
905
+ for chunk in r.iter_content(chunk_size=chunk_size):
906
+ if chunk:
907
+ tmp_file.write(chunk)
908
+ pbar.update(len(chunk) / (1024**3))
909
+ tmp_zip_path = tmp_file.name
910
+ print(f"Downloaded to {tmp_zip_path}. Extracting...")
911
+ with zipfile.ZipFile(tmp_zip_path, "r") as zip_ref:
912
+ zip_ref.extractall(dest_dir)
913
+ os.unlink(tmp_zip_path)
914
+ print(f"Extraction complete to {dest_dir}")
915
+ return dest_dir
916
+ except Exception as e:
917
+ print(f"Error downloading or extracting zip: {e}")
918
+ return f"Error: {e}"
919
+
920
+
921
+ def check_and_download_s3_files(
922
+ s3_bucket_url: str, local_data_lake_path: str, expected_files: list[str], folder: str = "data_lake"
923
+ ) -> dict[str, bool]:
924
+ """Check for missing files in the local data lake and download them from S3 bucket.
925
+
926
+ Args:
927
+ s3_bucket_url: Base URL of the S3 bucket (e.g., "https://biomni-release.s3.amazonaws.com")
928
+ local_data_lake_path: Local path to the data lake directory
929
+ expected_files: List of expected file names in the data lake
930
+ folder: S3 folder name ("data_lake" or "benchmark")
931
+
932
+ Returns:
933
+ Dictionary mapping file names to download success status
934
+ """
935
+
936
+ os.makedirs(local_data_lake_path, exist_ok=True)
937
+ download_results = {}
938
+
939
+ def download_with_progress(url: str, file_path: str, desc: str) -> bool:
940
+ """Download file with progress bar."""
941
+ try:
942
+ response = requests.get(url, stream=True)
943
+ response.raise_for_status()
944
+
945
+ total_size = int(response.headers.get("content-length", 0))
946
+
947
+ with open(file_path, "wb") as f:
948
+ if total_size > 0:
949
+ with tqdm.tqdm(total=total_size, unit="B", unit_scale=True, desc=desc, ncols=80) as pbar:
950
+ for chunk in response.iter_content(chunk_size=8192):
951
+ if chunk:
952
+ f.write(chunk)
953
+ pbar.update(len(chunk))
954
+ else:
955
+ for chunk in response.iter_content(chunk_size=8192):
956
+ if chunk:
957
+ f.write(chunk)
958
+ return True
959
+ except Exception as e:
960
+ print(f"✗ Failed to download {desc}: {e}")
961
+ if os.path.exists(file_path):
962
+ try:
963
+ os.remove(file_path)
964
+ except OSError:
965
+ pass
966
+ return False
967
+
968
+ def cleanup_file(file_path: str):
969
+ """Clean up file if it exists."""
970
+ if os.path.exists(file_path):
971
+ try:
972
+ os.remove(file_path)
973
+ except OSError:
974
+ pass
975
+
976
+ # Handle benchmark folder (download as zip)
977
+ if folder == "benchmark":
978
+ print(f"Downloading entire {folder} folder structure...")
979
+ s3_zip_url = urljoin(s3_bucket_url + "/", folder + ".zip")
980
+
981
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip:
982
+ tmp_zip_path = tmp_zip.name
983
+
984
+ if download_with_progress(s3_zip_url, tmp_zip_path, f"{folder}.zip"):
985
+ print(f"Extracting {folder}.zip...")
986
+ try:
987
+ with zipfile.ZipFile(tmp_zip_path, "r") as zip_ref:
988
+ zip_ref.extractall(local_data_lake_path)
989
+ print(f"✓ Successfully downloaded and extracted {folder} folder")
990
+ download_results = dict.fromkeys(expected_files, True)
991
+ except Exception as e:
992
+ print(f"✗ Error extracting {folder}.zip: {e}")
993
+ download_results = dict.fromkeys(expected_files, False)
994
+ finally:
995
+ cleanup_file(tmp_zip_path)
996
+ else:
997
+ download_results = dict.fromkeys(expected_files, False)
998
+
999
+ return download_results
1000
+
1001
+ # Handle data_lake folder (download individual files)
1002
+ for filename in expected_files:
1003
+ local_file_path = os.path.join(local_data_lake_path, filename)
1004
+
1005
+ if os.path.exists(local_file_path):
1006
+ download_results[filename] = True
1007
+ continue
1008
+
1009
+ s3_file_url = urljoin(s3_bucket_url + "/" + folder + "/", filename)
1010
+ print(f"Downloading {filename} from {folder}...")
1011
+
1012
+ if download_with_progress(s3_file_url, local_file_path, filename):
1013
+ print(f"✓ Successfully downloaded: {filename}")
1014
+ download_results[filename] = True
1015
+ else:
1016
+ download_results[filename] = False
1017
+
1018
+ return download_results
1019
+
1020
+
1021
+ def clean_message_content(content: str) -> str:
1022
+ """Clean message content by removing ANSI escape codes.
1023
+
1024
+ This function removes ANSI escape sequences (like color codes) from text content
1025
+ that might be present in terminal output or console messages. This ensures clean
1026
+ text for markdown generation and PDF conversion.
1027
+
1028
+ Args:
1029
+ content: The raw message content that may contain ANSI escape codes
1030
+
1031
+ Returns:
1032
+ Cleaned content with ANSI escape codes removed
1033
+
1034
+ Example:
1035
+ >>> clean_message_content("Hello \x1b[31mworld\x1b[0m!")
1036
+ "Hello world!"
1037
+ """
1038
+ import re
1039
+
1040
+ return re.sub(r"\x1b\[[0-9;]*m", "", content)
1041
+
1042
+
1043
+ def should_skip_message(clean_output: str) -> bool:
1044
+ """Check if message should be skipped during markdown generation.
1045
+
1046
+ This function determines whether a message should be excluded from the final
1047
+ markdown output. It skips empty or meaningless messages but preserves important
1048
+ error messages that should be displayed to users.
1049
+
1050
+ Args:
1051
+ clean_output: The cleaned message content to evaluate
1052
+
1053
+ Returns:
1054
+ True if the message should be skipped, False otherwise
1055
+
1056
+ Note:
1057
+ Parsing error messages are intentionally not skipped as they provide
1058
+ important feedback to users about conversation flow issues.
1059
+ """
1060
+ return (
1061
+ clean_output.strip() in ["", "None", "null", "undefined"]
1062
+ # Don't skip parsing error messages - they should be displayed and increment step counter
1063
+ # or "There are no tags" in clean_output
1064
+ # or "Execution terminated due to repeated parsing errors" in clean_output
1065
+ )
1066
+
1067
+
1068
+ def has_execution_results(clean_output: str, execution_results) -> bool:
1069
+ """Check if message contains code execution and has associated results.
1070
+
1071
+ This function determines whether a message contains executable code and has
1072
+ corresponding execution results available for display in the markdown output.
1073
+
1074
+ Args:
1075
+ clean_output: The cleaned message content to check for execute tags
1076
+ execution_results: List of execution results from the agent's execution history
1077
+
1078
+ Returns:
1079
+ True if the message contains <execute> tags and has execution results available
1080
+ """
1081
+ return "<execute>" in clean_output and execution_results is not None and execution_results
1082
+
1083
+
1084
+ def find_matching_execution(clean_output: str, execution_results) -> dict | None:
1085
+ """Find the execution result that matches the given message content.
1086
+
1087
+ This function searches through the execution results to find the one that
1088
+ corresponds to the current message. It matches based on the triggering message
1089
+ content to associate execution results with their originating AI messages.
1090
+
1091
+ Args:
1092
+ clean_output: The cleaned message content to match against
1093
+ execution_results: List of execution result dictionaries containing
1094
+ triggering messages and execution data
1095
+
1096
+ Returns:
1097
+ The matching execution result dictionary if found, None otherwise
1098
+
1099
+ Note:
1100
+ The matching is bidirectional - it checks if either the triggering message
1101
+ is contained in the current output or vice versa to handle partial matches.
1102
+ """
1103
+ for exec_result in execution_results:
1104
+ if exec_result["triggering_message"] in clean_output or clean_output in exec_result["triggering_message"]:
1105
+ return exec_result
1106
+ return None
1107
+
1108
+
1109
+ def create_parsing_error_html() -> str:
1110
+ """Create HTML markup for displaying parsing errors in markdown output.
1111
+
1112
+ This function generates a styled HTML block that displays parsing errors
1113
+ when the agent's response doesn't contain the required tags. The HTML
1114
+ uses CSS classes for consistent styling in the final PDF output.
1115
+
1116
+ Returns:
1117
+ HTML string containing a styled parsing error message box
1118
+
1119
+ Note:
1120
+ The returned HTML uses CSS classes defined in get_pdf_css_content()
1121
+ for consistent styling across the document.
1122
+ """
1123
+ return """
1124
+ <div class="parsing-error-box">
1125
+ <div class="parsing-error-header">Parsing Error</div>
1126
+ <div class="parsing-error-content">Each response must include thinking process followed by either execute or solution tag. But there are no tags in the current response.</div>
1127
+ </div>
1128
+ """
1129
+
1130
+
1131
+ def parse_tool_calls_from_code(code: str, module2api: dict, custom_functions: dict = None) -> list[str]:
1132
+ """Parse code to detect imported tools by analyzing import statements.
1133
+
1134
+ This function analyzes Python code to identify which tools/functions are being
1135
+ imported and used. It extracts tool names from import statements and function
1136
+ calls, then returns a deduplicated list of detected tool names.
1137
+
1138
+ Args:
1139
+ code: The Python code string to analyze for tool imports
1140
+ module2api: Dictionary mapping module names to their available API tools
1141
+ custom_functions: Optional dictionary of custom functions that have been
1142
+ added to the agent
1143
+
1144
+ Returns:
1145
+ Sorted list of unique tool names detected in the code
1146
+
1147
+ Example:
1148
+ >>> code = "from biomni.tool import analyze_data\nimport pandas as pd"
1149
+ >>> parse_tool_calls_from_code(code, module2api)
1150
+ ['analyze_data', 'pandas']
1151
+ """
1152
+ tool_module_pairs = parse_tool_calls_with_modules(code, module2api, custom_functions)
1153
+ return sorted({pair[0] for pair in tool_module_pairs})
1154
+
1155
+
1156
+ def parse_tool_calls_with_modules(code: str, module2api: dict, custom_functions: dict = None) -> list[tuple[str, str]]:
1157
+ """Parse code to detect imported tools and their associated modules.
1158
+
1159
+ This function performs detailed analysis of Python code to identify which
1160
+ tools/functions are being imported and which modules they belong to. It
1161
+ handles various import patterns including direct imports, from-imports,
1162
+ and module.function patterns.
1163
+
1164
+ Args:
1165
+ code: The Python code string to analyze for tool imports
1166
+ module2api: Dictionary mapping module names to their available API tools
1167
+ custom_functions: Optional dictionary of custom functions that have been
1168
+ added to the agent
1169
+
1170
+ Returns:
1171
+ List of tuples containing (tool_name, module_name) pairs for each
1172
+ detected tool and its associated module
1173
+
1174
+ Note:
1175
+ The function uses regex patterns to match various import statement
1176
+ formats and also detects direct function calls without explicit imports.
1177
+ """
1178
+ import re
1179
+
1180
+ detected_tools = set()
1181
+
1182
+ # Get all available tools from module2api
1183
+ all_tools = {}
1184
+ for module_name, module_tools in module2api.items():
1185
+ for tool in module_tools:
1186
+ if isinstance(tool, dict) and "name" in tool:
1187
+ tool_name = tool["name"]
1188
+ if tool_name not in all_tools:
1189
+ all_tools[tool_name] = []
1190
+ all_tools[tool_name].append(module_name)
1191
+
1192
+ # Add custom tools
1193
+ if custom_functions:
1194
+ for tool_name in custom_functions.keys():
1195
+ if tool_name not in all_tools:
1196
+ all_tools[tool_name] = []
1197
+ all_tools[tool_name].append("custom_tools")
1198
+
1199
+ # Look for import statements in the code
1200
+ import_patterns = [
1201
+ r"from\s+([\w.]+)\s+import\s+([\w,\s]+)", # from module import tool1, tool2
1202
+ r"import\s+([\w.]+)", # import module
1203
+ ]
1204
+
1205
+ for pattern in import_patterns:
1206
+ matches = re.findall(pattern, code)
1207
+ for match in matches:
1208
+ if len(match) == 2: # from module import tools
1209
+ module_name, tools_str = match
1210
+ # Split tools by comma and clean up
1211
+ tools = [tool.strip() for tool in tools_str.split(",")]
1212
+
1213
+ for tool in tools:
1214
+ # Check if this tool exists in any module
1215
+ if tool in all_tools:
1216
+ # Find the best matching module
1217
+ best_module = find_best_module_match(module_name, all_tools[tool])
1218
+ detected_tools.add((tool, best_module))
1219
+ # Also check if it's a module.function pattern
1220
+ elif "." in tool:
1221
+ parts = tool.split(".")
1222
+ if len(parts) == 2:
1223
+ module_part, func_part = parts
1224
+ if func_part in all_tools:
1225
+ best_module = find_best_module_match(module_part, all_tools[func_part])
1226
+ detected_tools.add((func_part, best_module))
1227
+
1228
+ elif len(match) == 1: # import module
1229
+ module_name = match[0]
1230
+ # Check if any tools from this module are used
1231
+ for tool_name, modules in all_tools.items():
1232
+ if any(module_name in mod for mod in modules):
1233
+ # Look for usage of this tool in the code
1234
+ if re.search(rf"\b{tool_name}\s*\(", code):
1235
+ best_module = find_best_module_match(module_name, modules)
1236
+ detected_tools.add((tool_name, best_module))
1237
+
1238
+ # Also look for direct function calls without imports
1239
+ function_call_pattern = r"(\w+)\s*\("
1240
+ function_calls = re.findall(function_call_pattern, code)
1241
+
1242
+ for func_call in function_calls:
1243
+ if func_call in all_tools:
1244
+ # For direct calls, use the first available module
1245
+ best_module = all_tools[func_call][0]
1246
+ detected_tools.add((func_call, best_module))
1247
+
1248
+ return sorted(detected_tools)
1249
+
1250
+
1251
+ def find_best_module_match(target_module: str, available_modules: list[str]) -> str:
1252
+ """Find the best matching module from a list of available modules.
1253
+
1254
+ This function attempts to match a target module name against a list of
1255
+ available modules using various matching strategies: exact match, partial
1256
+ substring matches, and fallback to the first available module.
1257
+
1258
+ Args:
1259
+ target_module: The module name we're trying to match
1260
+ available_modules: List of available module names to search through
1261
+
1262
+ Returns:
1263
+ The best matching module name from the available modules list.
1264
+ Returns "unknown" if no modules are available.
1265
+
1266
+ Note:
1267
+ The matching strategy prioritizes exact matches, then partial matches
1268
+ (where either the target is contained in the module name or vice versa),
1269
+ and finally falls back to the first available module.
1270
+ """
1271
+ # First try exact match
1272
+ if target_module in available_modules:
1273
+ return target_module
1274
+
1275
+ # Try partial matches
1276
+ for module in available_modules:
1277
+ if target_module in module or module in target_module:
1278
+ return module
1279
+
1280
+ # Return the first available module as fallback
1281
+ return available_modules[0] if available_modules else "unknown"
1282
+
1283
+
1284
+ def inject_custom_functions_to_repl(custom_functions: dict):
1285
+ """Inject custom functions into the Python REPL execution environment.
1286
+
1287
+ This function makes custom tools available during code execution by injecting
1288
+ them into both the persistent execution namespace and the builtins module.
1289
+ This allows the agent to call custom functions that users have added via
1290
+ agent.add_tool() when executing Python code in <execute> blocks.
1291
+
1292
+ Args:
1293
+ custom_functions: Dictionary mapping function names to their callable objects
1294
+
1295
+ Note:
1296
+ The function modifies both the persistent namespace used by run_python_repl
1297
+ and the builtins module to ensure maximum compatibility and accessibility
1298
+ of custom functions during code execution.
1299
+ """
1300
+ if custom_functions:
1301
+ # Access the persistent namespace used by run_python_repl
1302
+ from biomni.tool.support_tools import _persistent_namespace
1303
+
1304
+ # Inject all custom functions into the execution namespace
1305
+ for name, func in custom_functions.items():
1306
+ _persistent_namespace[name] = func
1307
+
1308
+ # Also make them available in builtins for broader access
1309
+ import builtins
1310
+
1311
+ if not hasattr(builtins, "_biomni_custom_functions"):
1312
+ builtins._biomni_custom_functions = {}
1313
+ builtins._biomni_custom_functions.update(custom_functions)
1314
+
1315
+
1316
+ def format_execute_tags_in_content(content: str, parse_tool_calls_with_modules_func) -> str:
1317
+ """Format execute tags in content by extracting code and creating highlighted tool call blocks.
1318
+
1319
+ This function processes content that contains <execute>...</execute> tags and
1320
+ converts them into styled HTML blocks that display the code with syntax highlighting
1321
+ and information about which tools are being used.
1322
+
1323
+ Args:
1324
+ content: The content string that may contain <execute> tags
1325
+ parse_tool_calls_with_modules_func: Function to parse tool calls with modules
1326
+ (typically parse_tool_calls_with_modules)
1327
+
1328
+ Returns:
1329
+ Formatted content with execute tags converted to highlighted tool call blocks.
1330
+ Also processes <solution> tags in the same pass.
1331
+
1332
+ Note:
1333
+ The function also calls format_solution_tags_in_content() to handle
1334
+ solution tags in the same processing pass.
1335
+ """
1336
+ import re
1337
+
1338
+ # Pattern to match <execute>...</execute> blocks
1339
+ execute_pattern = r"<execute>(.*?)</execute>"
1340
+
1341
+ def replace_execute_tag(match):
1342
+ code_content = match.group(1).strip()
1343
+ language, tool_name = detect_code_language_and_tool(code_content)
1344
+ code_content = clean_code_content(code_content, language)
1345
+
1346
+ # Parse tools from the code content with module information
1347
+ detected_tool_modules = parse_tool_calls_with_modules_func(code_content)
1348
+
1349
+ # Create the formatted block
1350
+ formatted_block = create_tool_call_block(code_content, language, tool_name, detected_tool_modules)
1351
+ return formatted_block
1352
+
1353
+ # Replace all execute tags with formatted tool call blocks
1354
+ formatted_content = re.sub(execute_pattern, replace_execute_tag, content, flags=re.DOTALL)
1355
+
1356
+ # Also format solution tags
1357
+ formatted_content = format_solution_tags_in_content(formatted_content)
1358
+
1359
+ return formatted_content
1360
+
1361
+
1362
+ def detect_code_language_and_tool(code_content: str) -> tuple[str, str]:
1363
+ """Detect the programming language and tool name from code content.
1364
+
1365
+ This function analyzes code content to determine the programming language
1366
+ and appropriate tool name based on language markers at the beginning of
1367
+ the code block.
1368
+
1369
+ Args:
1370
+ code_content: The code content to analyze for language markers
1371
+
1372
+ Returns:
1373
+ Tuple containing (language, tool_name) where:
1374
+ - language: The detected programming language ("python", "r", "bash")
1375
+ - tool_name: The human-readable tool name for display
1376
+
1377
+ Example:
1378
+ >>> detect_code_language_and_tool("#!R\nlibrary(ggplot2)")
1379
+ ("r", "R REPL")
1380
+ >>> detect_code_language_and_tool("#!BASH\necho 'hello'")
1381
+ ("bash", "Bash Script")
1382
+ """
1383
+ if code_content.startswith("#!R") or code_content.startswith("# R code") or code_content.startswith("# R script"):
1384
+ return "r", "R REPL"
1385
+ elif code_content.startswith("#!BASH") or code_content.startswith("# Bash script"):
1386
+ return "bash", "Bash Script"
1387
+ elif code_content.startswith("#!CLI"):
1388
+ return "bash", "CLI Command"
1389
+ else:
1390
+ return "python", "Python REPL"
1391
+
1392
+
1393
+ def clean_code_content(code_content: str, language: str) -> str:
1394
+ """Clean code content by removing language markers.
1395
+
1396
+ This function removes language-specific markers from the beginning of code
1397
+ content to prepare it for display in code blocks. The markers are used
1398
+ internally for language detection but should not appear in the final output.
1399
+
1400
+ Args:
1401
+ code_content: The raw code content that may contain language markers
1402
+ language: The detected programming language ("python", "r", "bash")
1403
+
1404
+ Returns:
1405
+ Cleaned code content with language markers removed
1406
+
1407
+ Example:
1408
+ >>> clean_code_content("#!R\nlibrary(ggplot2)", "r")
1409
+ "library(ggplot2)"
1410
+ >>> clean_code_content("#!BASH\necho 'hello'", "bash")
1411
+ "echo 'hello'"
1412
+ """
1413
+ import re
1414
+
1415
+ if language == "r":
1416
+ return re.sub(r"^#!R|^# R code|^# R script", "", code_content, count=1).strip()
1417
+ elif language == "bash":
1418
+ if code_content.startswith("#!BASH") or code_content.startswith("# Bash script"):
1419
+ return re.sub(r"^#!BASH|^# Bash script", "", code_content, count=1).strip()
1420
+ elif code_content.startswith("#!CLI"):
1421
+ return re.sub(r"^#!CLI", "", code_content, count=1).strip()
1422
+ return code_content
1423
+
1424
+
1425
+ def create_tool_call_block(code_content: str, language: str, tool_name: str, detected_tool_modules: list) -> str:
1426
+ """Create the HTML block for tool call highlighting.
1427
+
1428
+ This function generates a styled HTML block that displays code execution
1429
+ information including the code itself, syntax highlighting, and a list of
1430
+ tools that were used during execution.
1431
+
1432
+ Args:
1433
+ code_content: The cleaned code content to display
1434
+ language: The programming language for syntax highlighting
1435
+ tool_name: The default tool name to display if no specific tools detected
1436
+ detected_tool_modules: List of (tool_name, module_name) tuples for tools used
1437
+
1438
+ Returns:
1439
+ HTML string containing a styled tool call block with code and tool information
1440
+
1441
+ Note:
1442
+ The HTML uses CSS classes defined in get_pdf_css_content() for styling.
1443
+ If no specific tools are detected, it falls back to a default tool name.
1444
+ """
1445
+ # Create the formatted block with code and tools used
1446
+ formatted_block = f"""<div class="tool-call-highlight">
1447
+ <div class="tool-call-header">
1448
+ <strong>Code Execution</strong>
1449
+ </div>
1450
+ <div class="tool-call-input">
1451
+ ```{language}
1452
+ {code_content}
1453
+ ```
1454
+ </div>"""
1455
+
1456
+ # Add tools used section
1457
+ if detected_tool_modules:
1458
+ tools_list = format_detected_tools(detected_tool_modules)
1459
+ formatted_block += f"""
1460
+ <div class="tools-used">
1461
+ <strong>Tools Used:</strong> {tools_list}
1462
+ </div>"""
1463
+ else:
1464
+ formatted_block += format_default_tool_name(language, tool_name)
1465
+
1466
+ formatted_block += "</div>"
1467
+ return formatted_block
1468
+
1469
+
1470
+ def format_detected_tools(detected_tool_modules: list) -> str:
1471
+ """Format detected tools with their modules for display.
1472
+
1473
+ This function takes a list of (tool_name, module_name) tuples and formats
1474
+ them into a human-readable string for display in the tool call blocks.
1475
+ It handles special cases for common tools and formats module names appropriately.
1476
+
1477
+ Args:
1478
+ detected_tool_modules: List of (tool_name, module_name) tuples
1479
+
1480
+ Returns:
1481
+ Comma-separated string of formatted tool descriptions
1482
+
1483
+ Example:
1484
+ >>> format_detected_tools([("analyze_data", "biomni.tool"), ("pandas", "pandas")])
1485
+ "biomni → analyze_data, pandas → pandas"
1486
+ """
1487
+ tool_descriptions = []
1488
+ for tool_name, module_name in detected_tool_modules:
1489
+ if tool_name == "python_repl":
1490
+ tool_descriptions.append("Python REPL")
1491
+ elif tool_name == "r_repl":
1492
+ tool_descriptions.append("R REPL")
1493
+ elif "bash" in tool_name.lower():
1494
+ tool_descriptions.append("Bash Script")
1495
+ else:
1496
+ # Extract the last part of the module name for display
1497
+ display_module = module_name.split(".")[-1] if "." in module_name else module_name
1498
+ tool_descriptions.append(f"{display_module} → {tool_name}")
1499
+
1500
+ return ", ".join(sorted(tool_descriptions))
1501
+
1502
+
1503
+ def format_default_tool_name(language: str, tool_name: str) -> str:
1504
+ """Format default tool name based on programming language.
1505
+
1506
+ This function generates HTML for displaying the default tool name when
1507
+ no specific tools are detected in the code. It maps programming languages
1508
+ to their appropriate default tool names.
1509
+
1510
+ Args:
1511
+ language: The programming language ("python", "r", "bash")
1512
+ tool_name: The detected tool name (used for bash CLI vs script distinction)
1513
+
1514
+ Returns:
1515
+ HTML string containing a styled tools-used section
1516
+
1517
+ Note:
1518
+ For bash, it distinguishes between CLI commands and bash scripts
1519
+ based on the tool_name parameter.
1520
+ """
1521
+ if language == "r":
1522
+ return """
1523
+ <div class="tools-used">
1524
+ <strong>Tools Used:</strong> R REPL
1525
+ </div>"""
1526
+ elif language == "bash":
1527
+ if tool_name == "CLI Command":
1528
+ return """
1529
+ <div class="tools-used">
1530
+ <strong>Tools Used:</strong> CLI Command
1531
+ </div>"""
1532
+ else:
1533
+ return """
1534
+ <div class="tools-used">
1535
+ <strong>Tools Used:</strong> Bash Script
1536
+ </div>"""
1537
+ else:
1538
+ return """
1539
+ <div class="tools-used">
1540
+ <strong>Tools Used:</strong> Python REPL
1541
+ </div>"""
1542
+
1543
+
1544
+ def format_solution_tags_in_content(content: str) -> str:
1545
+ """Format solution tags in content by extracting text and formatting as solution blocks.
1546
+
1547
+ This function processes content that contains <solution>...</solution> tags and
1548
+ converts them into styled HTML blocks that display solution content with appropriate
1549
+ formatting and CSS classes.
1550
+
1551
+ Args:
1552
+ content: The content string that may contain <solution> tags
1553
+
1554
+ Returns:
1555
+ Formatted content with solution tags converted to styled solution blocks
1556
+
1557
+ Note:
1558
+ The solution blocks use the "title-text summary" CSS class for consistent
1559
+ styling with other content blocks in the markdown output.
1560
+ """
1561
+ import re
1562
+
1563
+ # Pattern to match <solution>...</solution> blocks
1564
+ solution_pattern = r"<solution>(.*?)</solution>"
1565
+
1566
+ def replace_solution_tag(match):
1567
+ solution_content = match.group(1).strip()
1568
+ # Format as regular text, not terminal
1569
+ return f"""<div class="title-text summary">
1570
+ <div class="title-text-header">
1571
+ <strong>Summary and Solution</strong>
1572
+ </div>
1573
+ <div class="title-text-content">
1574
+ {solution_content}
1575
+ </div>
1576
+ </div>"""
1577
+
1578
+ # Replace all solution tags with formatted solution blocks
1579
+ formatted_content = re.sub(solution_pattern, replace_solution_tag, content, flags=re.DOTALL)
1580
+
1581
+ return formatted_content
1582
+
1583
+
1584
+ def format_observation_as_terminal(content: str) -> str | None:
1585
+ """Format observation content with terminal-like styling.
1586
+
1587
+ This function processes observation content from the agent's execution results
1588
+ and formats it as a styled terminal block. It handles both text and image content,
1589
+ with length limits to ensure the output fits within PDF page constraints.
1590
+
1591
+ Args:
1592
+ content: The observation content string, potentially containing <observation> tags
1593
+
1594
+ Returns:
1595
+ Formatted HTML content with terminal styling, or None if observation is
1596
+ empty, invalid, or contains only meaningless content
1597
+
1598
+ Note:
1599
+ - Content is limited to 10,000 characters to fit within 2 A4 pages
1600
+ - Handles both text and base64-encoded images
1601
+ - Uses CSS classes for consistent styling with other content blocks
1602
+ """
1603
+ import re
1604
+
1605
+ # Character limit for 2 A4 pages (approximately 10,000 characters)
1606
+ MAX_OBSERVATION_LENGTH = 10000
1607
+
1608
+ # Remove the <observation> tags and extract the content
1609
+ observation_pattern = r"<observation>(.*?)</observation>"
1610
+ observation_match = re.search(observation_pattern, content, re.DOTALL)
1611
+
1612
+ if observation_match:
1613
+ observation_content = observation_match.group(1).strip()
1614
+ else:
1615
+ # Fallback if no observation tags found - check if content is meaningful
1616
+ if not (content.strip() and content.strip() not in ["", "None", "null", "undefined"]):
1617
+ return None
1618
+ observation_content = content.strip()
1619
+
1620
+ # Skip empty observations
1621
+ if not observation_content or observation_content in ["", "None", "null", "undefined"]:
1622
+ return None
1623
+
1624
+ # Check if observation is too long for 2 pages
1625
+ if len(observation_content) > MAX_OBSERVATION_LENGTH:
1626
+ cropped_content = observation_content[:MAX_OBSERVATION_LENGTH]
1627
+ truncation_notice = f"\n\n[Output truncated - content was too long to display here ({len(observation_content)} characters total)]"
1628
+ observation_content = cropped_content + truncation_notice
1629
+
1630
+ # Check if it contains plot data (base64 images)
1631
+ if "data:image/" in observation_content:
1632
+ content_html = process_observation_with_images(observation_content)
1633
+ else:
1634
+ # Regular text output - format as terminal output
1635
+ content_html = f"```terminal\n{observation_content}\n```"
1636
+
1637
+ return f"""<div class="title-text observation">
1638
+ <div class="title-text-header">
1639
+ <strong>Observation</strong>
1640
+ </div>
1641
+ <div class="title-text-content">
1642
+ {content_html}
1643
+ </div>
1644
+ </div>"""
1645
+
1646
+
1647
+ def process_observation_with_images(observation_content: str) -> str:
1648
+ """Process observation content that contains both text and base64-encoded images.
1649
+
1650
+ This function handles observation content that includes both text output and
1651
+ base64-encoded images (typically plots from data analysis). It separates the
1652
+ text and image content and formats them appropriately for markdown display.
1653
+
1654
+ Args:
1655
+ observation_content: The observation content containing both text and images
1656
+
1657
+ Returns:
1658
+ HTML string containing formatted text (as terminal blocks) and images
1659
+ (as markdown image tags)
1660
+
1661
+ Note:
1662
+ The function uses "data:image/" as a delimiter to split content into
1663
+ text and image parts, then processes each part separately.
1664
+ """
1665
+ # Split content into text and image parts
1666
+ parts = observation_content.split("data:image/")
1667
+ text_parts = []
1668
+ image_parts = []
1669
+
1670
+ for i, part in enumerate(parts):
1671
+ if i == 0:
1672
+ # First part is text only
1673
+ if part.strip():
1674
+ text_parts.append(part.strip())
1675
+ else:
1676
+ # Find the end of the base64 data
1677
+ end_markers = ["\n", "\r", " ", "\t", ">", "<", "]", ")", "}"]
1678
+ image_end = len(part)
1679
+ for marker in end_markers:
1680
+ marker_pos = part.find(marker)
1681
+ if marker_pos != -1 and marker_pos < image_end:
1682
+ image_end = marker_pos
1683
+
1684
+ # Extract image data
1685
+ image_data = "data:image/" + part[:image_end]
1686
+ image_parts.append(image_data)
1687
+
1688
+ # Extract remaining text
1689
+ remaining_text = part[image_end:].strip()
1690
+ if remaining_text:
1691
+ text_parts.append(remaining_text)
1692
+
1693
+ # Build the content
1694
+ content_html = ""
1695
+ if text_parts:
1696
+ # Add text content as terminal output
1697
+ text_content = "\n".join(text_parts)
1698
+ content_html += f"```terminal\n{text_content}\n```\n\n"
1699
+
1700
+ if image_parts:
1701
+ # Add image content
1702
+ for image_data in image_parts:
1703
+ content_html += f"![Plot]({image_data})\n\n"
1704
+
1705
+ return content_html
1706
+
1707
+
1708
+ def remove_emojis_from_text(text: str) -> str:
1709
+ """Remove emojis from text for markdown/PDF output.
1710
+
1711
+ This function removes common emojis used in the system prompt and configuration
1712
+ display from text content before it's converted to markdown or PDF. This ensures
1713
+ clean, professional output while preserving emojis in the console display.
1714
+
1715
+ Args:
1716
+ text: The text content that may contain emojis
1717
+
1718
+ Returns:
1719
+ Text content with emojis removed
1720
+
1721
+ Note:
1722
+ The function targets specific emojis used in the Biomni system:
1723
+ - 🔧 for tools
1724
+ - 📊 for data
1725
+ - ⚙️ for software
1726
+ - 📋 for configuration
1727
+ - 🤖 for agent
1728
+ """
1729
+ import re
1730
+
1731
+ # Remove common emojis used in the system prompt, this makes conversion simpler
1732
+ emoji_patterns = [
1733
+ r"🔧\s*", # Tool emoji
1734
+ r"📊\s*", # Data emoji
1735
+ r"⚙️\s*", # Software emoji
1736
+ r"📋\s*", # Config emoji
1737
+ r"🤖\s*", # Agent emoji
1738
+ ]
1739
+
1740
+ for pattern in emoji_patterns:
1741
+ text = re.sub(pattern, "", text)
1742
+
1743
+ return text
1744
+
1745
+
1746
+ def format_lists_in_text(text: str) -> str:
1747
+ """Format numbered lists and bullet points in text to proper markdown format.
1748
+
1749
+ This function processes text content to identify and format various types of lists,
1750
+ including numbered lists with checkboxes, regular lists, and plan structures.
1751
+ It also handles preprocessing tasks like removing bold formatting from plan titles
1752
+ and removing emojis for clean PDF output.
1753
+
1754
+ Args:
1755
+ text: The text content to process for list formatting
1756
+
1757
+ Returns:
1758
+ Formatted text with properly structured lists and cleaned formatting
1759
+
1760
+ Note:
1761
+ The function performs several preprocessing steps:
1762
+ - Removes bold formatting from plan titles
1763
+ - Removes emojis for PDF output
1764
+ - Identifies and formats checkbox lists
1765
+ - Processes regular text blocks
1766
+ """
1767
+ import re
1768
+
1769
+ # Preprocess to remove bold formatting from plan titles
1770
+ # Remove **Plan:**, **Updated Plan:**, **Completed Plan:**, etc.
1771
+ text = re.sub(r"\*\*([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan):\*\*", r"\1:", text)
1772
+ # Also handle cases without colons
1773
+ text = re.sub(r"\*\*([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan)\*\*", r"\1", text)
1774
+ # Handle any other bold formatting patterns for plan titles
1775
+ text = re.sub(r"<strong>([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan):</strong>", r"\1:", text)
1776
+ text = re.sub(r"<strong>([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan)</strong>", r"\1", text)
1777
+
1778
+ # Remove emojis from the text for markdown/PDF output
1779
+ text = remove_emojis_from_text(text)
1780
+
1781
+ lines = text.split("\n")
1782
+ list_blocks = identify_list_blocks(lines)
1783
+
1784
+ # Process each block
1785
+ result_blocks = []
1786
+ for block_text, is_checkbox_list in list_blocks:
1787
+ if is_checkbox_list:
1788
+ result_blocks.append(format_single_list(block_text))
1789
+ else:
1790
+ result_blocks.append(block_text)
1791
+
1792
+ return "\n".join(result_blocks)
1793
+
1794
+
1795
+ def identify_list_blocks(lines: list) -> list[tuple[str, bool]]:
1796
+ """Identify blocks of text that contain lists.
1797
+
1798
+ This function analyzes a list of text lines to identify contiguous blocks
1799
+ that contain numbered lists with checkboxes. It groups lines into blocks
1800
+ and marks whether each block contains a checkbox list or regular text.
1801
+
1802
+ Args:
1803
+ lines: List of text lines to analyze
1804
+
1805
+ Returns:
1806
+ List of tuples containing (block_text, is_checkbox_list) where:
1807
+ - block_text: The text content of the block
1808
+ - is_checkbox_list: True if the block contains numbered items with checkboxes
1809
+
1810
+ Note:
1811
+ The function looks for patterns like "1. [ ]", "2. [✓]", "3. [✗]" to
1812
+ identify checkbox sequences and groups them into separate blocks.
1813
+ """
1814
+ import re
1815
+
1816
+ list_blocks = []
1817
+ current_block = []
1818
+ in_checkbox_sequence = False
1819
+
1820
+ for line in lines:
1821
+ line_stripped = line.strip()
1822
+
1823
+ # Check if this line starts a numbered item with checkbox
1824
+ if re.match(r"^\d+\.\s*\[[ ✓✗]\]", line_stripped):
1825
+ if not in_checkbox_sequence:
1826
+ # Start of a new checkbox sequence
1827
+ if current_block:
1828
+ list_blocks.append(("\n".join(current_block), False))
1829
+ current_block = [line]
1830
+ in_checkbox_sequence = True
1831
+ else:
1832
+ # Continue the sequence
1833
+ current_block.append(line)
1834
+ else:
1835
+ if in_checkbox_sequence:
1836
+ # End of checkbox sequence
1837
+ if current_block:
1838
+ list_blocks.append(("\n".join(current_block), True))
1839
+ current_block = []
1840
+ in_checkbox_sequence = False
1841
+ current_block.append(line)
1842
+
1843
+ # Handle the last block
1844
+ if current_block:
1845
+ if in_checkbox_sequence:
1846
+ list_blocks.append(("\n".join(current_block), True))
1847
+ else:
1848
+ list_blocks.append(("\n".join(current_block), False))
1849
+
1850
+ return list_blocks
1851
+
1852
+
1853
+ def format_single_list(text: str) -> str:
1854
+ """Format a single list block with checkboxes and plan titles.
1855
+
1856
+ This function processes a text block that may contain numbered lists with
1857
+ checkboxes and plan titles. It converts checkbox symbols to HTML list items
1858
+ and wraps the content in a styled container with appropriate CSS classes.
1859
+
1860
+ Args:
1861
+ text: The text block to format, potentially containing numbered lists
1862
+
1863
+ Returns:
1864
+ HTML string containing either a formatted list with plan title or
1865
+ regular text if no list items are found
1866
+
1867
+ Note:
1868
+ The function recognizes plan titles like "Plan", "Updated Plan", "Completed Plan"
1869
+ and converts checkbox symbols (✓, ✗) to HTML format ([x], [ ]).
1870
+ """
1871
+ import re
1872
+
1873
+ lines = text.split("\n")
1874
+ list_items = []
1875
+ has_list_items = False
1876
+ plan_title = "Plan" # Default title
1877
+
1878
+ for line in lines:
1879
+ line = line.strip()
1880
+ if not line:
1881
+ continue
1882
+
1883
+ # Check for plan title patterns
1884
+ if re.match(r"^(Plan|Updated Plan|Completed Plan)$", line, re.IGNORECASE):
1885
+ plan_title = line
1886
+ continue
1887
+
1888
+ # Check for numbered lists with checkboxes (1. [ ] or 1. [✓] or 1. [✗])
1889
+ if re.match(r"^\d+\.\s*\[[ ✓✗]\]", line):
1890
+ has_list_items = True
1891
+ # Extract the content after the checkbox
1892
+ content = re.sub(r"^\d+\.\s*\[[ ✓✗]\]\s*", "", line)
1893
+
1894
+ # Replace checkbox symbols with text format
1895
+ if "[✓]" in line:
1896
+ list_items.append(f"<li><strong>[x]</strong> {content}</li>")
1897
+ elif "[✗]" in line:
1898
+ list_items.append(f"<li><strong>[ ]</strong> {content}</li>")
1899
+ else:
1900
+ list_items.append(f"<li><strong>[ ]</strong> {content}</li>")
1901
+ else:
1902
+ # Regular text - add as is (don't convert to list items)
1903
+ list_items.append(line)
1904
+
1905
+ if has_list_items and list_items:
1906
+ # This is a list - return with container div and styled title
1907
+ return f"""<div class="title-text plan">
1908
+ <div class="title-text-header">
1909
+ <span class="plan-title">{plan_title}</span>
1910
+ </div>
1911
+ <div class="title-text-content">
1912
+ <ul>
1913
+ {chr(10).join(list_items)}
1914
+ </ul>
1915
+ </div>
1916
+ </div>"""
1917
+ else:
1918
+ # Regular text
1919
+ return "\n".join(list_items)
1920
+
1921
+
1922
+ def convert_markdown_to_pdf(markdown_path: str, pdf_path: str) -> None:
1923
+ """Convert markdown file to PDF using weasyprint or fallback libraries.
1924
+
1925
+ This function converts a markdown file to PDF format using multiple fallback
1926
+ strategies. It prioritizes weasyprint for better layout control, then falls back
1927
+ to markdown2pdf and finally pandoc if the preferred libraries are not available.
1928
+
1929
+ Args:
1930
+ markdown_path: Path to the input markdown file
1931
+ pdf_path: Path where the output PDF file should be saved
1932
+
1933
+ Raises:
1934
+ ImportError: If no PDF conversion library is available
1935
+ Exception: If PDF conversion fails for any other reason
1936
+
1937
+ Note:
1938
+ The function uses minimal markdown extensions for better performance
1939
+ and applies custom CSS styling for consistent formatting.
1940
+ """
1941
+ try:
1942
+ # Try weasyprint first (better for complex layouts)
1943
+ from weasyprint import HTML
1944
+ from weasyprint.text.fonts import FontConfiguration
1945
+
1946
+ # Read markdown content
1947
+ with open(markdown_path, encoding="utf-8") as f:
1948
+ markdown_content = f.read()
1949
+
1950
+ # Convert markdown to HTML with minimal extensions for better performance
1951
+ import markdown
1952
+
1953
+ # Use minimal extensions to improve performance
1954
+ html_content = markdown.markdown(
1955
+ markdown_content,
1956
+ extensions=["fenced_code"], # Removed codehilite for better performance
1957
+ )
1958
+
1959
+ # Add CSS styling
1960
+ css_content = get_pdf_css_content()
1961
+
1962
+ # Create HTML document
1963
+ html_doc = f"""
1964
+ <!DOCTYPE html>
1965
+ <html>
1966
+ <head>
1967
+ <meta charset="utf-8">
1968
+ <title>Biomni Conversation History</title>
1969
+ <style>{css_content}</style>
1970
+ </head>
1971
+ <body>
1972
+ {html_content}
1973
+ </body>
1974
+ </html>
1975
+ """
1976
+
1977
+ # Convert to PDF with performance optimizations
1978
+ font_config = FontConfiguration()
1979
+ html_obj = HTML(string=html_doc)
1980
+ html_obj.write_pdf(pdf_path, font_config=font_config, optimize_images=True)
1981
+
1982
+ except ImportError:
1983
+ # Fallback to markdown2pdf if weasyprint is not available
1984
+ try:
1985
+ from markdown2pdf import markdown2pdf
1986
+
1987
+ markdown2pdf(markdown_path, pdf_path)
1988
+ except ImportError:
1989
+ # Final fallback - try using pandoc if available
1990
+ import subprocess
1991
+
1992
+ try:
1993
+ subprocess.run(["pandoc", markdown_path, "-o", pdf_path], check=True)
1994
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
1995
+ raise ImportError(
1996
+ "No PDF conversion library available. Please install weasyprint, markdown2pdf, or pandoc."
1997
+ ) from e
1998
+ except Exception as e:
1999
+ raise Exception(f"PDF conversion failed: {e}") from e
2000
+
2001
+
2002
+ def get_pdf_css_content() -> str:
2003
+ """Get the CSS content for PDF generation.
2004
+
2005
+ This function returns a comprehensive CSS stylesheet designed specifically
2006
+ for PDF generation from markdown content. It includes styling for all
2007
+ HTML elements that may appear in the converted markdown, with optimized
2008
+ typography, spacing, and layout for print media.
2009
+
2010
+ Returns:
2011
+ CSS string containing all styles needed for PDF generation
2012
+
2013
+ Note:
2014
+ The CSS includes styles for:
2015
+ - Typography and font families
2016
+ - Headings and text formatting
2017
+ - Code blocks and syntax highlighting
2018
+ - Tables and lists
2019
+ - Custom classes for tool calls, observations, and plans
2020
+ - Print-optimized spacing and layout
2021
+ """
2022
+ return """
2023
+ body {
2024
+ /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */
2025
+ font-family: sans-serif;
2026
+ font-size: 9pt;
2027
+ line-height: 1.4;
2028
+ max-width: 800px;
2029
+ margin: 0 auto;
2030
+ padding: 15px;
2031
+ color: #333;
2032
+ }
2033
+ h1, h2, h3, h4, h5, h6 {
2034
+ /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */
2035
+ font-family: sans-serif;
2036
+ color: #2c3e50;
2037
+ margin-top: 1em;
2038
+ margin-bottom: 0.5em;
2039
+ }
2040
+ h1 {
2041
+ border-bottom: 2px solid #3498db;
2042
+ padding-bottom: 8px;
2043
+ font-size: 16pt;
2044
+ }
2045
+ h2 {
2046
+ border-bottom: 1px solid #bdc3c7;
2047
+ padding-bottom: 3px;
2048
+ font-size: 14pt;
2049
+ }
2050
+ h3 {
2051
+ font-size: 12pt;
2052
+ }
2053
+ h4 {
2054
+ font-size: 10pt;
2055
+ margin-top: 0.8em;
2056
+ margin-bottom: 0.3em;
2057
+ }
2058
+ h5, h6 {
2059
+ font-size: 9pt;
2060
+ margin-top: 0.6em;
2061
+ margin-bottom: 0.2em;
2062
+ }
2063
+ code {
2064
+ background-color: #f8f9fa;
2065
+ padding: 1px 3px;
2066
+ border-radius: 2px;
2067
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
2068
+ font-size: 8pt;
2069
+ white-space: pre-wrap;
2070
+ word-wrap: break-word;
2071
+ }
2072
+ pre {
2073
+ background-color: #f8f9fa;
2074
+ padding: 10px;
2075
+ border-radius: 3px;
2076
+ overflow-x: auto;
2077
+ border-left: 3px solid #3498db;
2078
+ white-space: pre-wrap;
2079
+ word-wrap: break-word;
2080
+ font-size: 8pt;
2081
+ margin: 0.5em 0;
2082
+ }
2083
+ pre code {
2084
+ background-color: transparent;
2085
+ padding: 0;
2086
+ border-radius: 0;
2087
+ font-size: 8pt;
2088
+ }
2089
+ /* Code header styling */
2090
+ strong {
2091
+ font-size: 9pt;
2092
+ font-weight: normal;
2093
+ color: #6c757d;
2094
+ font-style: italic;
2095
+ }
2096
+ blockquote {
2097
+ border-left: 3px solid #bdc3c7;
2098
+ margin: 0.5em 0;
2099
+ padding-left: 15px;
2100
+ color: #7f8c8d;
2101
+ font-size: 8pt;
2102
+ }
2103
+ table {
2104
+ border-collapse: collapse;
2105
+ width: 100%;
2106
+ margin: 0.5em 0;
2107
+ font-size: 8pt;
2108
+ }
2109
+ th, td {
2110
+ border: 1px solid #bdc3c7;
2111
+ padding: 4px 8px;
2112
+ text-align: left;
2113
+ }
2114
+ th {
2115
+ background-color: #ecf0f1;
2116
+ font-weight: bold;
2117
+ }
2118
+ img {
2119
+ max-width: 100%;
2120
+ height: auto;
2121
+ display: block;
2122
+ margin: 10px auto;
2123
+ border: 1px solid #ddd;
2124
+ border-radius: 3px;
2125
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
2126
+ }
2127
+ p {
2128
+ /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */
2129
+ font-family: sans-serif;
2130
+ margin: 0.3em 0;
2131
+ }
2132
+ /* Tool call highlighting - matching observation and code formatting */
2133
+ .tool-call-highlight {
2134
+ background-color: #f8f9fa;
2135
+ border: 1px solid #e9ecef;
2136
+ border-radius: 3px;
2137
+ padding: 0;
2138
+ margin: 10px 0;
2139
+ overflow: hidden;
2140
+ }
2141
+ .tool-call-header {
2142
+ background-color: #e9ecef;
2143
+ color: #495057;
2144
+ padding: 8px 12px;
2145
+ margin: 0;
2146
+ font-weight: normal;
2147
+ font-size: 9pt;
2148
+ font-style: italic;
2149
+ border-bottom: 1px solid #dee2e6;
2150
+ }
2151
+ .tool-call-input {
2152
+ background-color: #f8f9fa;
2153
+ border: none;
2154
+ border-radius: 0;
2155
+ padding: 10px 12px;
2156
+ margin: 0;
2157
+ color: #333;
2158
+ font-size: 8pt;
2159
+ line-height: 1.4;
2160
+ }
2161
+ .tool-call-input strong {
2162
+ color: #495057;
2163
+ font-weight: normal;
2164
+ font-size: 8pt;
2165
+ font-style: italic;
2166
+ }
2167
+ .tool-call-input pre {
2168
+ background-color: #f8f9fa;
2169
+ border: 1px solid #e9ecef;
2170
+ border-radius: 3px;
2171
+ padding: 10px;
2172
+ margin: 0;
2173
+ font-size: 8pt;
2174
+ line-height: 1.4;
2175
+ overflow-x: auto;
2176
+ white-space: pre-wrap;
2177
+ word-wrap: break-word;
2178
+ }
2179
+ .tool-call-input code {
2180
+ background-color: transparent;
2181
+ padding: 0;
2182
+ border-radius: 0;
2183
+ font-size: 8pt;
2184
+ color: #2c3e50;
2185
+ }
2186
+ .tools-used {
2187
+ background-color: #f8f9fa;
2188
+ border-top: 1px solid #dee2e6;
2189
+ padding: 8px 12px;
2190
+ margin: 0;
2191
+ font-size: 8pt;
2192
+ color: #6c757d;
2193
+ }
2194
+ .tools-used strong {
2195
+ color: #6c757d;
2196
+ font-weight: normal;
2197
+ font-size: 8pt;
2198
+ font-style: italic;
2199
+ }
2200
+ /* Title-text styling - unified for observations, plans, and solutions */
2201
+ .title-text {
2202
+ background-color: #f8f9fa;
2203
+ border: 1px solid #e9ecef;
2204
+ border-radius: 3px;
2205
+ padding: 0;
2206
+ margin: 10px 0;
2207
+ overflow: hidden;
2208
+ }
2209
+ .title-text-header {
2210
+ background-color: #e9ecef;
2211
+ color: #495057;
2212
+ padding: 8px 12px;
2213
+ margin: 0;
2214
+ font-weight: normal;
2215
+ font-size: 9pt;
2216
+ font-style: italic;
2217
+ border-bottom: 1px solid #dee2e6;
2218
+ }
2219
+ .title-text-header strong {
2220
+ color: #495057;
2221
+ font-weight: normal;
2222
+ font-size: 9pt;
2223
+ font-style: italic;
2224
+ }
2225
+ .title-text-content {
2226
+ background-color: #f8f9fa;
2227
+ border: none;
2228
+ border-radius: 0;
2229
+ padding: 10px 12px;
2230
+ margin: 0;
2231
+ color: #333;
2232
+ font-size: 8pt;
2233
+ line-height: 1.4;
2234
+ }
2235
+ /* Plan-specific styling - soft blue pastel */
2236
+ .title-text.plan {
2237
+ background-color: #e3f2fd;
2238
+ border-color: #bbdefb;
2239
+ }
2240
+ .title-text.plan .title-text-header {
2241
+ background-color: #bbdefb;
2242
+ color: #1976d2;
2243
+ }
2244
+ .title-text.plan .title-text-content {
2245
+ background-color: #e3f2fd;
2246
+ }
2247
+ .plan-title {
2248
+ font-style: italic;
2249
+ font-weight: normal;
2250
+ color: #1565c0;
2251
+ text-shadow: 0 1px 2px rgba(0,0,0,0.1);
2252
+ }
2253
+ .plan-title strong {
2254
+ font-weight: normal;
2255
+ }
2256
+ /* Code execution-specific styling - matching title-text styling */
2257
+ .tool-call-highlight {
2258
+ background-color: #f8f9fa;
2259
+ border-color: #e9ecef;
2260
+ }
2261
+ .tool-call-header {
2262
+ background-color: #e9ecef;
2263
+ color: #495057;
2264
+ }
2265
+ .tool-call-input {
2266
+ background-color: #f8f9fa;
2267
+ color: #333;
2268
+ }
2269
+ /* Observation-specific styling - soft purple pastel */
2270
+ .title-text.observation {
2271
+ background-color: #f3e5f5;
2272
+ border-color: #e1bee7;
2273
+ }
2274
+ .title-text.observation .title-text-header {
2275
+ background-color: #e1bee7;
2276
+ color: #7b1fa2;
2277
+ }
2278
+ .title-text.observation .title-text-content {
2279
+ background-color: #f3e5f5;
2280
+ }
2281
+ /* Summary and solution-specific styling - soft orange pastel, no overlay */
2282
+ .title-text.summary {
2283
+ background-color: #fff3e0;
2284
+ border-color: #ffcc02;
2285
+ }
2286
+ .title-text.summary .title-text-header {
2287
+ background-color: #ffcc02;
2288
+ color: #f57c00;
2289
+ }
2290
+ .title-text.summary .title-text-content {
2291
+ background-color: #fff3e0;
2292
+ }
2293
+ .title-text-content ul {
2294
+ background-color: transparent;
2295
+ border: none;
2296
+ border-radius: 0;
2297
+ padding: 0;
2298
+ margin: 0;
2299
+ color: #333;
2300
+ font-size: 8pt;
2301
+ line-height: 1.4;
2302
+ }
2303
+ .title-text-content li {
2304
+ margin: 3px 0;
2305
+ color: #333;
2306
+ }
2307
+ .title-text-content li strong {
2308
+ color: #495057;
2309
+ font-weight: normal;
2310
+ font-size: 8pt;
2311
+ font-style: italic;
2312
+ }
2313
+ .title-text-content li code {
2314
+ background-color: #e9ecef;
2315
+ color: #333;
2316
+ padding: 1px 3px;
2317
+ border-radius: 2px;
2318
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
2319
+ font-size: 7pt;
2320
+ }
2321
+ .title-text-content pre {
2322
+ background-color: #f8f9fa;
2323
+ border: 1px solid #e9ecef;
2324
+ border-radius: 3px;
2325
+ padding: 10px;
2326
+ margin: 0;
2327
+ font-size: 8pt;
2328
+ line-height: 1.4;
2329
+ overflow-x: auto;
2330
+ white-space: pre-wrap;
2331
+ word-wrap: break-word;
2332
+ }
2333
+ .title-text-content code {
2334
+ background-color: transparent;
2335
+ padding: 0;
2336
+ border-radius: 0;
2337
+ font-size: 8pt;
2338
+ color: #2c3e50;
2339
+ }
2340
+ /* Parsing error display styling */
2341
+ .parsing-error-box {
2342
+ background-color: #ffebee;
2343
+ border: 1px solid #f44336;
2344
+ border-radius: 4px;
2345
+ padding: 8px 12px;
2346
+ margin: 8px 0;
2347
+ font-size: 9pt;
2348
+ color: #c62828;
2349
+ box-shadow: 0 2px 4px rgba(244, 67, 54, 0.1);
2350
+ }
2351
+ .parsing-error-header {
2352
+ font-weight: bold;
2353
+ margin-bottom: 4px;
2354
+ color: #d32f2f;
2355
+ }
2356
+ .parsing-error-content {
2357
+ font-family: 'Courier New', monospace;
2358
+ background-color: #ffcdd2;
2359
+ padding: 4px 6px;
2360
+ border-radius: 2px;
2361
+ margin-top: 4px;
2362
+ font-size: 8pt;
2363
+ white-space: pre-wrap;
2364
+ word-wrap: break-word;
2365
+ }
2366
+ """
biomni/version.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "0.0.8" # pragma: no cover
biomni_env/README.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni Environment Setup
2
+
3
+ This directory contains scripts and configuration files to set up a comprehensive bioinformatics environment with various tools and packages.
4
+
5
+ 1. Clone the repository:
6
+ ```bash
7
+ git clone https://github.com/snap-stanford/Biomni.git
8
+ cd Biomni/biomni_env
9
+ ```
10
+
11
+ 2. Setting up the environment:
12
+ - (a) If you want to use or try out the basic agent without the full E1 or install your own softwares, run the following script:
13
+
14
+ ```bash
15
+ conda env create -f environment.yml
16
+ ```
17
+
18
+ - (b) If you want to use the full environment E1, run the setup script (this script takes > 10 hours to setup, and requires a disk of at least 30 GB quota). Follow the prompts to install the desired components.
19
+
20
+ ```bash
21
+ bash setup.sh
22
+ ```
23
+
24
+ If you already installed the base version, and just wants to add the additional packages in the new release, you can simply do:
25
+
26
+ ```bash
27
+ bash new_software_v005.sh
28
+ ```
29
+
30
+ Note: we have only tested this setup.sh script with Ubuntu 22.04, 64 bit.
31
+
32
+ - (c) If you want to use a reduced conda environment without R or CLI tools, run the following script:
33
+
34
+ ```bash
35
+ conda env create -f fixed_env.yml
36
+ ```
37
+
38
+ This contains most of the packages from environment.yml and bio_env.yml, and requires a disk of at elast 13GB quota.
39
+
40
+ - (d) **Python 3.10 Environment for Copy Number Analysis**: If you specifically need to use the `analyze_copy_number_purity_ploidy_and_focal_events` function, we provide a Python 3.10 environment option. This function has specific dependency requirements that are best met with Python 3.10. To set up this environment:
41
+
42
+ ```bash
43
+ conda env create -f bio_env_py310.yml
44
+ ```
45
+
46
+ This environment is optimized for copy number variation analysis and includes the necessary packages for purity, ploidy, and focal event detection.
47
+
48
+ 3. Lastly, to activate the biomni environment:
49
+ ```bash
50
+ conda activate biomni_e1
51
+ ```
52
+
53
+ For the Python 3.10 environment specifically:
54
+ ```bash
55
+ conda activate biomni_py310
56
+ ```
57
+
58
+ ### 📦 Langchain Package Support
59
+
60
+ The Biomni environment comes with a minimal set of langchain packages by default:
61
+ - `langchain-openai` - for OpenAI model support
62
+ - `langchain-anthropic` - for Anthropic model support
63
+ - `langchain-ollama` - for Ollama model support
64
+
65
+ If you need support for other external models or services, you'll need to install additional langchain packages manually. For example:
66
+
67
+ ```bash
68
+ # For AWS Bedrock support
69
+ pip install langchain-aws
70
+
71
+ # For Google Gemini support
72
+ pip install langchain-google-genai
73
+
74
+ ```
biomni_env/bio_env.yml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: biomni_e1
2
+ channels:
3
+ - conda-forge
4
+ - bioconda
5
+ - defaults
6
+ dependencies:
7
+ - blast
8
+ - samtools
9
+ - bowtie2
10
+ - bwa
11
+ - bedtools
12
+ - fastqc
13
+ - trimmomatic
14
+ - mafft
15
+ - gseapy
16
+ - mageck
17
+ - plannotate
18
+ - pip:
19
+ - scanpy
20
+ - biopython
21
+ - biopandas
22
+ - biotite
23
+ - opencv-python
24
+ - rdkit
25
+ - macs2
26
+ - pymc3
27
+ - cryosparc-tools
28
+ - lifelines
29
+ - scikit-image
30
+ - googlesearch-python
31
+ - PyPDF2
32
+ - gget
33
+ - faiss-cpu
34
+ - cellxgene-census
35
+ - scikit-bio
36
+ - pymed
37
+ - arxiv
38
+ - scholarly
39
+ - umap-learn
40
+ - scrublet
41
+ - scvelo
42
+ - scvi-tools
43
+ - harmony-pytorch
44
+ - pyliftover
45
+ - pytdc==0.4.0
46
+ - pysam
47
+ - pyfaidx
48
+ - pyranges
49
+ - pybedtools
50
+ - openmm
51
+ - pystan
52
+ - igraph
53
+ - pyscenic
54
+ - cooler
55
+ - trackpy
56
+ - flowcytometrytools
57
+ - cellpose
58
+ - viennarna
59
+ - PyMassSpec
60
+ - python-libsbml
61
+ - cobra
62
+ - reportlab
63
+ #- flowkit
64
+ - hmmlearn
65
+ - msprime
66
+ - tskit
67
+ - cyvcf2
68
+ - pykalman
69
+ - community
70
+ - fanc
biomni_env/bio_env_py310.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: bio_env_py310
2
+ channels:
3
+ - conda-forge
4
+ - bioconda
5
+ - defaults
6
+ dependencies:
7
+ - python=3.10
8
+ - cnvkit
9
+ - samtools
10
+ - bedtools
11
+ - pip
12
+
13
+ # Purpose: Python 3.10 compatibility environment for tools not yet supporting newer Python versions.
14
+ # Automaticlly setup on setup.sh execution.
15
+ # Contains tools that require Python 3.10 or earlier versions.
16
+ # Can be create with:
17
+ # micromamba create -f bio_env_py310.yml OR conda env create -f bio_env_py310.yml
18
+ # Use tools via:
19
+ # conda run -n bio_env_py310 <command> ...
biomni_env/cli_tools_config.json ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tools": [
3
+ {
4
+ "name": "PLINK 2.0",
5
+ "function_name": "install_plink2",
6
+ "description": "Whole genome association analysis toolset",
7
+ "website": "https://www.cog-genomics.org/plink/2.0/",
8
+ "downloads": {
9
+ "linux": "https://s3.amazonaws.com/plink2-assets/alpha6/plink2_linux_avx2_20250129.zip",
10
+ "macos_intel": "https://s3.amazonaws.com/plink2-assets/alpha6/plink2_mac_avx2_20250129.zip",
11
+ "macos_arm64": "https://s3.amazonaws.com/plink2-assets/alpha6/plink2_mac_arm64_20250129.zip"
12
+ },
13
+ "binary_path": "plink2",
14
+ "version_command": "--version"
15
+ },
16
+ {
17
+ "name": "IQ-TREE",
18
+ "function_name": "install_iqtree",
19
+ "description": "Efficient phylogenetic software by maximum likelihood",
20
+ "website": "http://www.iqtree.org/",
21
+ "downloads": {
22
+ "linux": "https://github.com/iqtree/iqtree2/releases/download/v2.2.2.7/iqtree-2.2.2.7-Linux.tar.gz",
23
+ "macos_intel": "https://github.com/iqtree/iqtree2/releases/download/v2.2.2.7/iqtree-2.2.2.7-MacOSX.zip",
24
+ "macos_arm64": "https://github.com/iqtree/iqtree2/releases/download/v2.2.2.7/iqtree-2.2.2.7-MacOSX.zip"
25
+ },
26
+ "binary_path": "bin/iqtree2",
27
+ "version_command": "--version"
28
+ },
29
+ {
30
+ "name": "GCTA",
31
+ "function_name": "install_gcta",
32
+ "description": "Genome-wide Complex Trait Analysis",
33
+ "website": "https://yanglab.westlake.edu.cn/software/gcta/",
34
+ "downloads": {
35
+ "linux": "https://yanglab.westlake.edu.cn/software/gcta/bin/gcta-1.94.4-linux-kernel-3-x86_64.zip",
36
+ "macos_intel": "https://yanglab.westlake.edu.cn/software/gcta/bin/gcta-1.94.1-macOS-x86_64.zip",
37
+ "macos_arm64": "https://yanglab.westlake.edu.cn/software/gcta/bin/gcta-1.94.3-macOS-arm64.zip"
38
+ },
39
+ "binary_path": "gcta64",
40
+ "version_command": ""
41
+ },
42
+ {
43
+ "name": "BWA",
44
+ "function_name": "install_bwa",
45
+ "description": "Burrow-Wheeler Aligner for short-read alignment",
46
+ "website": "https://github.com/lh3/bwa",
47
+ "downloads": {
48
+ "linux": "https://github.com/lh3/bwa.git",
49
+ "macos_intel": "https://github.com/lh3/bwa.git",
50
+ "macos_arm64": "https://github.com/lh3/bwa.git"
51
+ },
52
+ "binary_path": "bwa",
53
+ "version_command": ""
54
+ },
55
+ {
56
+ "name": "FastTree",
57
+ "function_name": "install_fasttree",
58
+ "description": "Approximately-Maximum-Likelihood phylogenetic trees from alignments",
59
+ "website": "https://morgannprice.github.io/fasttree/",
60
+ "downloads": {
61
+ "linux": "https://morgannprice.github.io/fasttree/FastTree.c",
62
+ "macos_intel": "https://morgannprice.github.io/fasttree/FastTree.c",
63
+ "macos_arm64": "https://morgannprice.github.io/fasttree/FastTree.c"
64
+ },
65
+ "binary_path": "FastTree",
66
+ "version_command": "-help | head -n 2"
67
+ },
68
+ {
69
+ "name": "MUSCLE",
70
+ "function_name": "install_muscle",
71
+ "description": "Multiple sequence alignment with high accuracy and performance",
72
+ "website": "https://github.com/rcedgar/muscle",
73
+ "downloads": {
74
+ "linux": "https://github.com/rcedgar/muscle/releases/download/v5.3/muscle-linux-x86.v5.3",
75
+ "macos_arm64": "https://github.com/rcedgar/muscle/releases/download/v5.3/muscle-osx-arm64.v5.3"
76
+ },
77
+ "binary_path": "muscle",
78
+ "version_command": "-version"
79
+ },
80
+ {
81
+ "name": "HOMER",
82
+ "function_name": "install_homer",
83
+ "description": "Software for motif discovery and next-gen sequencing analysis",
84
+ "website": "http://homer.ucsd.edu/homer/",
85
+ "downloads": {
86
+ "linux": "http://homer.ucsd.edu/homer/configureHomer.pl",
87
+ "macos_intel": "http://homer.ucsd.edu/homer/configureHomer.pl",
88
+ "macos_arm64": "http://homer.ucsd.edu/homer/configureHomer.pl"
89
+ },
90
+ "binary_path": "bin/findMotifs.pl",
91
+ "version_command": "-h"
92
+ }
93
+ ]
94
+ }
biomni_env/environment.yml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: biomni_e1
2
+ channels:
3
+ - conda-forge
4
+ - defaults
5
+ - bioconda
6
+ dependencies:
7
+ - python=3.11
8
+ - pip
9
+ - pip:
10
+ - numpy==2.1
11
+ - pandas
12
+ - matplotlib
13
+ - scipy
14
+ - statsmodels
15
+ - scikit-learn
16
+ - gradio
17
+ - langchain
18
+ - langgraph==0.3.18
19
+ - langchain-openai
20
+ - langchain-anthropic
21
+ - langchain-ollama
22
+ - langchain-community
23
+ - openai
24
+ - beautifulsoup4
25
+ - lxml
26
+ - tqdm
27
+ - seaborn
28
+ - networkx
29
+ - requests
30
+ - pyyaml
31
+ - jupyter
32
+ - notebook
33
+ - ipykernel
34
+ - pytest
35
+ - transformers
36
+ - sentencepiece
37
+ - mcp
38
+ - tooluniverse
39
+ - python-dotenv
biomni_env/fixed_env.yml ADDED
@@ -0,0 +1,742 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: biomni_e1
2
+ channels:
3
+ - bioconda
4
+ - conda-forge
5
+ - defaults
6
+ dependencies:
7
+ - altair=5.5.0
8
+ - asttokens=3.0.0
9
+ - attrs=25.3.0
10
+ - aws-c-auth=0.9.0
11
+ - aws-c-cal=0.9.2
12
+ - aws-c-common=0.12.4
13
+ - aws-c-compression=0.3.1
14
+ - aws-c-event-stream=0.5.5
15
+ - aws-c-http=0.10.4
16
+ - aws-c-io=0.21.2
17
+ - aws-c-mqtt=0.13.3
18
+ - aws-c-s3=0.8.6
19
+ - aws-c-sdkutils=0.2.4
20
+ - aws-checksums=0.2.7
21
+ - aws-crt-cpp=0.33.1
22
+ - aws-sdk-cpp=1.11.606
23
+ - azure-core-cpp=1.16.0
24
+ - azure-identity-cpp=1.12.0
25
+ - azure-storage-blobs-cpp=12.14.0
26
+ - azure-storage-common-cpp=12.10.0
27
+ - azure-storage-files-datalake-cpp=12.12.0
28
+ - bedtools=2.31.1
29
+ - biopython=1.85
30
+ - blast=2.16.0
31
+ - blinker=1.9.0
32
+ - bokeh=3.7.3
33
+ - bowtie2=2.5.4
34
+ - brotli-bin=1.1.0
35
+ - brotli-python=1.1.0
36
+ - bwa=0.7.19
37
+ - bzip2=1.0.8
38
+ - c-ares=1.34.5
39
+ - ca-certificates=2025.8.3
40
+ - cairo=1.18.4
41
+ - certifi=2025.8.3
42
+ - cffi=1.17.1
43
+ - charset-normalizer=3.4.2
44
+ - contourpy=1.3.3
45
+ - curl=8.14.1
46
+ - cycler=0.12.1
47
+ - decorator=5.2.1
48
+ - diamond=2.1.13
49
+ - entrez-direct=22.4
50
+ - exceptiongroup=1.3.0
51
+ - executing=2.2.0
52
+ - fastqc=0.12.1
53
+ - font-ttf-dejavu-sans-mono=2.37
54
+ - font-ttf-inconsolata=3.000
55
+ - font-ttf-source-code-pro=2.038
56
+ - font-ttf-ubuntu=0.83
57
+ - fontconfig=2.15.0
58
+ - fonts-conda-ecosystem=1
59
+ - fonts-conda-forge=1
60
+ - fonttools=4.59.0
61
+ - freetype=2.13.3
62
+ - gflags=2.2.2
63
+ - giflib=5.2.2
64
+ - gitdb=4.0.12
65
+ - gitpython=3.1.45
66
+ - glog=0.7.1
67
+ - gmp=6.3.0
68
+ - graphite2=1.3.14
69
+ - gseapy=1.1.9
70
+ - h2=4.2.0
71
+ - harfbuzz=11.3.3
72
+ - hpack=4.1.0
73
+ - htslib=1.22.1
74
+ - hyperframe=6.1.0
75
+ - icu=75.1
76
+ - idna=3.10
77
+ - importlib-metadata=8.7.0
78
+ - infernal=1.1.5
79
+ - ipython=9.4.0
80
+ - ipython_pygments_lexers=1.1.1
81
+ - jedi=0.19.2
82
+ - jinja2=3.1.6
83
+ - jsonschema=4.25.0
84
+ - jsonschema-specifications=2025.4.1
85
+ - kiwisolver=1.4.8
86
+ - krb5=1.21.3
87
+ - lcms2=2.17
88
+ - lerc=4.0.0
89
+ - libabseil=20250512.1
90
+ - libarrow=21.0.0
91
+ - libarrow-acero=21.0.0
92
+ - libarrow-compute=21.0.0
93
+ - libarrow-dataset=21.0.0
94
+ - libarrow-substrait=21.0.0
95
+ - libasprintf=0.25.1
96
+ - libblas=3.9.0
97
+ - libbrotlicommon=1.1.0
98
+ - libbrotlidec=1.1.0
99
+ - libbrotlienc=1.1.0
100
+ - libcblas=3.9.0
101
+ - libcrc32c=1.1.2
102
+ - libcurl=8.14.1
103
+ - libdeflate=1.22
104
+ - libedit=3.1.20250104
105
+ - libev=4.33
106
+ - libevent=2.1.12
107
+ - libexpat=2.7.1
108
+ - libffi=3.4.6
109
+ - libfreetype=2.13.3
110
+ - libfreetype6=2.13.3
111
+ - libgettextpo=0.25.1
112
+ - libgfortran=15.1.0
113
+ - libgfortran5=15.1.0
114
+ - libglib=2.84.2
115
+ - libgoogle-cloud=2.39.0
116
+ - libgoogle-cloud-storage=2.39.0
117
+ - libgrpc=1.73.1
118
+ - libiconv=1.18
119
+ - libidn2=2.3.8
120
+ - libjpeg-turbo=3.1.0
121
+ - liblapack=3.9.0
122
+ - liblzma=5.8.1
123
+ - libnghttp2=1.64.0
124
+ - libopenblas=0.3.30
125
+ - libopentelemetry-cpp=1.21.0
126
+ - libopentelemetry-cpp-headers=1.21.0
127
+ - libparquet=21.0.0
128
+ - libpng=1.6.50
129
+ - libprotobuf=6.31.1
130
+ - libre2-11=2025.07.22
131
+ - libsqlite=3.50.4
132
+ - libssh2=1.11.1
133
+ - libthrift=0.22.0
134
+ - libtiff=4.7.0
135
+ - libunistring=0.9.10
136
+ - libutf8proc=2.10.0
137
+ - libwebp-base=1.6.0
138
+ - libxml2=2.13.8
139
+ - libzlib=1.3.1
140
+ - lz4-c=1.10.0
141
+ - mafft=7.526
142
+ # - mageck=0.5.9.5
143
+ - markupsafe=3.0.2
144
+ - matplotlib-base=3.10.5
145
+ - matplotlib-inline=0.1.7
146
+ - mpfr=4.2.1
147
+ - munkres=1.1.4
148
+ - narwhals=2.0.1
149
+ - ncbi-vdb=3.2.1
150
+ - ncurses=6.5
151
+ - nlohmann_json=3.12.0
152
+ - openjdk=23.0.2
153
+ - openjpeg=2.5.3
154
+ - openssl=3.5.1
155
+ - orc=2.1.3
156
+ - packaging=25.0
157
+ - pandas=2.3.1
158
+ - parso=0.8.4
159
+ - pcre2=10.45
160
+ - perl=5.32.1
161
+ - perl-archive-tar=3.04
162
+ - perl-carp=1.50
163
+ - perl-common-sense=3.75
164
+ - perl-compress-raw-bzip2=2.201
165
+ - perl-compress-raw-zlib=2.202
166
+ - perl-encode=3.21
167
+ - perl-exporter=5.74
168
+ - perl-exporter-tiny=1.002002
169
+ - perl-extutils-makemaker=7.70
170
+ - perl-io-compress=2.201
171
+ - perl-io-zlib=1.15
172
+ - perl-json=4.10
173
+ - perl-json-xs=4.03
174
+ - perl-list-moreutils=0.430
175
+ - perl-list-moreutils-xs=0.430
176
+ - perl-parent=0.243
177
+ - perl-pathtools=3.75
178
+ - perl-scalar-list-utils=1.70
179
+ - perl-storable=3.15
180
+ - perl-types-serialiser=1.01
181
+ - pexpect=4.9.0
182
+ - pickleshare=0.7.5
183
+ - pillow=11.3.0
184
+ - pip=25.2
185
+ - pixman=0.46.4
186
+ # - plannotate=1.2.0
187
+ - prometheus-cpp=1.3.0
188
+ - prompt-toolkit=3.0.51
189
+ - protobuf=6.31.1
190
+ - ptyprocess=0.7.0
191
+ - pure_eval=0.2.3
192
+ - pyarrow=21.0.0
193
+ - pyarrow-core=21.0.0
194
+ - pycparser=2.22
195
+ - pydeck=0.9.1
196
+ - pygments=2.19.2
197
+ - pyopengl=3.1.9
198
+ - pyparsing=3.2.3
199
+ - pysocks=1.7.1
200
+ - python=3.11.13
201
+ - python-dateutil=2.9.0.post0
202
+ - python-tzdata=2025.2
203
+ - python_abi=3.11
204
+ - pytz=2025.2
205
+ - pyyaml=6.0.2
206
+ - qhull=2020.2
207
+ - re2=2025.07.22
208
+ - readline=8.2
209
+ - referencing=0.36.2
210
+ - requests=2.32.4
211
+ - ripgrep=14.1.1
212
+ - rpds-py=0.26.0
213
+ - rpsbproc=0.5.0
214
+ - s2n=1.5.23
215
+ - samtools=1.22.1
216
+ - setuptools=80.9.0
217
+ - six=1.17.0
218
+ - smmap=5.0.2
219
+ - snappy=1.2.2
220
+ - stack_data=0.6.3
221
+ - streamlit=1.47.1
222
+ - tabulate=0.9.0
223
+ - tenacity=9.1.2
224
+ - tk=8.6.13
225
+ - toml=0.10.2
226
+ - tornado=6.5.1
227
+ - traitlets=5.14.3
228
+ - trimmomatic=0.39
229
+ - trnascan-se=2.0.12
230
+ - typing-extensions=4.14.1
231
+ - typing_extensions=4.14.1
232
+ - tzdata=2025b
233
+ - unicodedata2=16.0.0
234
+ - urllib3=2.5.0
235
+ - watchdog=6.0.0
236
+ - wcwidth=0.2.13
237
+ - wget=1.21.4
238
+ - wheel=0.45.1
239
+ - xyzservices=2025.4.0
240
+ - yaml=0.2.5
241
+ - zipp=3.23.0
242
+ - zlib=1.3.1
243
+ - zstd=1.5.7
244
+ - pip:
245
+ - absl-py==2.3.1
246
+ - adjusttext==1.3.0
247
+ - aiobotocore
248
+ - aiofiles==24.1.0
249
+ - aiohappyeyeballs==2.6.1
250
+ - aiohttp==3.12.15
251
+ - aioitertools==0.12.0
252
+ - aiosignal==1.4.0
253
+ - airr==1.5.1
254
+ - alabaster==1.0.0
255
+ - anndata==0.12.1
256
+ - annotated-types==0.7.0
257
+ - annoy==1.17.3
258
+ - anthropic==0.60.0
259
+ - anyio==4.9.0
260
+ - appdirs==1.4.4
261
+ - arboreto==0.1.6
262
+ - argon2-cffi==25.1.0
263
+ - argon2-cffi-bindings==25.1.0
264
+ - array-api-compat==1.12.0
265
+ - arrow==1.3.0
266
+ - arviz==0.22.0
267
+ - arxiv==2.2.0
268
+ - asciitree==0.3.3
269
+ - async-lru==2.0.5
270
+ - authlib==1.6.1
271
+ - autograd==1.8.0
272
+ - autograd-gamma==0.5.0
273
+ - awkward==2.8.6
274
+ - awkward-cpp==48
275
+ - babel==2.17.0
276
+ - beautifulsoup4==4.13.4
277
+ - bibtexparser==1.4.3
278
+ - biom-format==2.1.16
279
+ # - biomni==0.0.8
280
+ - biopandas==0.5.1
281
+ - biotite==1.4.0
282
+ - biotraj==1.2.2
283
+ - bleach==6.2.0
284
+ - blitzgsea==1.3.54
285
+ - blosc2==3.6.1
286
+ - boltons==25.0.0
287
+ - boto3
288
+ - botocore
289
+ - brotli==1.1.0
290
+ - cachetools==5.5.2
291
+ - cellpose==4.0.6
292
+ - cellxgene-census==1.17.0
293
+ - cftime==1.6.4.post1
294
+ - chex==0.1.90
295
+ - click==8.2.1
296
+ - clikit==0.6.2
297
+ - cloudpickle==3.1.1
298
+ - cobra==0.29.1
299
+ - colorcet==3.1.0
300
+ - coloredlogs==15.0.1
301
+ - comm==0.2.3
302
+ - community==1.0.0b1
303
+ - cooler==0.10.4
304
+ - crashtest==0.3.1
305
+ - cryosparc-tools==4.7.1
306
+ - cryptography==45.0.5
307
+ - ctxcore==0.2.0
308
+ - cyclopts==3.22.5
309
+ - cython==0.29.37
310
+ - cytoolz==1.0.1
311
+ - cyvcf2==0.31.1
312
+ - dask==2024.11.2
313
+ - dask-expr==1.1.19
314
+ - dask-image==2024.5.3
315
+ - dataclasses==0.6
316
+ - dataclasses-json==0.6.7
317
+ - datashader==0.18.1
318
+ - debugpy==1.8.15
319
+ - decoupler==2.1.1
320
+ - defusedxml==0.7.1
321
+ - demes==0.2.3
322
+ - depinfo==2.2.0
323
+ - deprecated==1.2.18
324
+ - deprecation==2.1.0
325
+ - dill==0.4.0
326
+ - diskcache==5.6.3
327
+ - distributed==2024.11.2
328
+ - distro==1.9.0
329
+ - dnspython==2.7.0
330
+ - docrep==0.3.2
331
+ - docstring-parser==0.17.0
332
+ - docutils==0.21.2
333
+ - domdf-python-tools==3.10.0
334
+ - drmaa==0.7.9
335
+ - email-validator==2.2.0
336
+ - enum-tools==0.13.0
337
+ - equinox==0.13.0
338
+ - et-xmlfile==2.0.0
339
+ - etils==1.13.0
340
+ - faiss-cpu==1.11.0.post1
341
+ - fake-useragent==2.2.0
342
+ - fanc==0.9.28
343
+ - fast-array-utils==1.2.1
344
+ - fastapi==0.116.1
345
+ - fasteners==0.19
346
+ - fastjsonschema==2.21.1
347
+ - fastmcp==2.11.0
348
+ - fastprogress==1.0.3
349
+ - fastremap==1.17.2
350
+ - fcsparser==0.2.4
351
+ - feedparser==6.0.11
352
+ - ffmpy==0.6.1
353
+ - filelock==3.18.0
354
+ - filetype==1.2.0
355
+ - fill-voids==2.1.0
356
+ - flask==3.1.1
357
+ - flax==0.11.0
358
+ - flowcytometrytools==0.5.1
359
+ - formulaic==1.2.0
360
+ - formulaic-contrasts==1.0.0
361
+ - fqdn==1.5.1
362
+ - free-proxy==1.1.3
363
+ - frozendict==2.4.6
364
+ - frozenlist==1.7.0
365
+ - fsspec==2025.7.0
366
+ - future==1.0.0
367
+ - fuzzywuzzy==0.18.0
368
+ - genomic-regions==0.0.10
369
+ - geopandas==1.1.1
370
+ - gget==0.29.2
371
+ - google-ai-generativelanguage==0.6.18
372
+ - google-api-core==2.25.1
373
+ - google-auth==2.40.3
374
+ - googleapis-common-protos==1.70.0
375
+ - googlesearch-python==1.3.0
376
+ - gradio==5.39.0
377
+ - gradio-client==1.11.0
378
+ - graphql-core==3.2.6
379
+ - greenlet==3.2.3
380
+ - gridmap==0.15.0
381
+ - groovy==0.1.2
382
+ - grpcio==1.74.0
383
+ - grpcio-status==1.74.0
384
+ - h11==0.16.0
385
+ - h5netcdf==1.6.3
386
+ - h5py==3.14.0
387
+ - harmony-pytorch==0.1.8
388
+ - hf-xet==1.1.5
389
+ - hmmlearn==0.3.3
390
+ - httpcore==1.0.9
391
+ # - httpstan==4.13.0
392
+ - httpx==0.28.1
393
+ - httpx-sse==0.4.1
394
+ - huggingface-hub==0.34.3
395
+ - humanfriendly==10.0
396
+ - humanize==4.12.3
397
+ - igraph==0.11.9
398
+ - imagecodecs==2025.8.2
399
+ - imageio==2.37.0
400
+ - imagesize==1.4.1
401
+ - importlib-resources==6.5.2
402
+ - inflect==7.5.0
403
+ - iniconfig==2.1.0
404
+ - interface-meta==1.3.0
405
+ - interlap==0.2.7
406
+ - intervaltree==3.1.0
407
+ - ipykernel==6.30.0
408
+ - ipywidgets==8.1.7
409
+ - isodate==0.7.2
410
+ - isoduration==20.11.0
411
+ - itsdangerous==2.2.0
412
+ - jax==0.6.2
413
+ - jaxlib==0.6.2
414
+ - jaxopt==0.8.5
415
+ - jaxtyping==0.3.2
416
+ - jiter==0.10.0
417
+ - jmespath==1.0.1
418
+ - joblib==1.5.1
419
+ - json5==0.12.0
420
+ - jsonpatch==1.33
421
+ - jsonpointer==3.0.0
422
+ - jsonschema-path==0.3.4
423
+ - jupyter==1.1.1
424
+ - jupyter-client==8.6.3
425
+ - jupyter-console==6.6.3
426
+ - jupyter-core==5.8.1
427
+ - jupyter-events==0.12.0
428
+ - jupyter-lsp==2.2.6
429
+ - jupyter-server==2.16.0
430
+ - jupyter-server-terminals==0.5.3
431
+ - jupyterlab==4.4.5
432
+ - jupyterlab-pygments==0.3.0
433
+ - jupyterlab-server==2.27.3
434
+ - jupyterlab-widgets==3.0.15
435
+ - lamin-utils==0.15.0
436
+ - langchain==0.3.27
437
+ - langchain-anthropic==0.3.18
438
+ - langchain-community==0.3.27
439
+ - langchain-core==0.3.72
440
+ # - langchain-google-genai==2.1.8
441
+ - langchain-ollama==0.3.6
442
+ - langchain-openai==0.3.28
443
+ - langchain-text-splitters==0.3.9
444
+ - langgraph==0.3.18
445
+ - langgraph-checkpoint==2.1.1
446
+ - langgraph-prebuilt==0.1.8
447
+ - langgraph-sdk==0.1.74
448
+ - langsmith==0.4.10
449
+ - lark==1.2.2
450
+ - lazy-loader==0.4
451
+ - lazy-object-proxy==1.11.0
452
+ - legacy-api-wrap==1.4.1
453
+ - legendkit==0.3.6
454
+ - levenshtein==0.27.1
455
+ - lifelines==0.30.0
456
+ - lightning==2.5.2
457
+ - lightning-utilities==0.15.0
458
+ - lineax==0.0.8
459
+ - llvmlite==0.44.0
460
+ - locket==1.0.0
461
+ - logomaker==0.8.7
462
+ - loompy==3.0.8
463
+ - looseversion==1.1.2
464
+ - lxml==6.0.0
465
+ - lz4==4.4.4
466
+ - macs2==2.2.9.1
467
+ - markdown==3.8.2
468
+ - markdown-it-py==3.0.0
469
+ - marshmallow==3.26.1
470
+ - marsilea==0.5.4
471
+ - matplotlib-scalebar==0.9.0
472
+ - mcp==1.12.3
473
+ - mdurl==0.1.2
474
+ - mistune==3.1.3
475
+ - ml-collections==1.1.0
476
+ - ml-dtypes==0.5.3
477
+ - mmtf-python==1.1.3
478
+ - more-itertools==10.7.0
479
+ - mpmath==1.3.0
480
+ - msgpack==1.1.1
481
+ - msgpack-numpy==0.4.8
482
+ - msprime==1.3.4
483
+ - mudata==0.3.2
484
+ - multidict==6.6.3
485
+ - multipledispatch==1.0.0
486
+ - multiprocess==0.70.18
487
+ - multiprocessing-on-dill==3.5.0a4
488
+ - multiscale-spatial-image==2.0.2
489
+ - muon==0.1.7
490
+ - mypy-extensions==1.1.0
491
+ - mysql-connector-python==9.4.0
492
+ - natsort==8.4.0
493
+ - nbclient==0.10.2
494
+ - nbconvert==7.16.6
495
+ - nbformat==5.10.4
496
+ - ncls==0.0.70
497
+ - ndindex==1.10.0
498
+ - nest-asyncio==1.6.0
499
+ - netcdf4==1.7.2
500
+ - networkx==3.5
501
+ - newick==1.10.0
502
+ - notebook==7.4.4
503
+ - notebook-shim==0.2.4
504
+ - numba==0.61.2
505
+ - numcodecs==0.15.1
506
+ - numexpr==2.11.0
507
+ - numpy==2.1.0
508
+ - numpy-groupies==0.11.3
509
+ - numpyro==0.18.0
510
+ # - nvidia-cublas-cu12==12.6.4.1
511
+ # - nvidia-cuda-cupti-cu12==12.6.80
512
+ # - nvidia-cuda-nvrtc-cu12==12.6.77
513
+ # - nvidia-cuda-runtime-cu12==12.6.77
514
+ # - nvidia-cudnn-cu12==9.5.1.17
515
+ # - nvidia-cufft-cu12==11.3.0.4
516
+ # - nvidia-cufile-cu12==1.11.1.6
517
+ # - nvidia-curand-cu12==10.3.7.77
518
+ # - nvidia-cusolver-cu12==11.7.1.2
519
+ # - nvidia-cusparse-cu12==12.5.4.2
520
+ # - nvidia-cusparselt-cu12==0.6.3
521
+ # - nvidia-nccl-cu12==2.26.2
522
+ # - nvidia-nvjitlink-cu12==12.6.85
523
+ # - nvidia-nvtx-cu12==12.6.77
524
+ - ollama==0.5.1
525
+ - ome-zarr==0.11.1
526
+ - omnipath==1.0.12
527
+ - openai==1.98.0
528
+ - openapi-core==0.19.5
529
+ - openapi-pydantic==0.5.1
530
+ - openapi-schema-validator==0.6.3
531
+ - openapi-spec-validator==0.7.2
532
+ - opencv-python==4.12.0.88
533
+ - opencv-python-headless==4.12.0.88
534
+ - openmm==8.3.1
535
+ - openpyxl==3.1.5
536
+ - opt-einsum==3.4.0
537
+ - optax==0.2.5
538
+ - optlang==1.8.3
539
+ - orbax-checkpoint==0.11.20
540
+ - orjson==3.11.1
541
+ - ormsgpack==1.10.0
542
+ - ott-jax==0.5.1
543
+ - outcome==1.3.0.post0
544
+ - overrides==7.7.0
545
+ - pandocfilters==1.5.1
546
+ - param==2.2.1
547
+ - parse==1.20.2
548
+ - partd==1.4.2
549
+ - pastel==0.2.1
550
+ - pathable==0.4.4
551
+ - patsy==1.0.1
552
+ - pertpy==1.0.1
553
+ - pims==0.7
554
+ - platformdirs==4.3.8
555
+ - pluggy==1.6.0
556
+ - ply==3.11
557
+ - pooch==1.8.2
558
+ - progressbar2==4.5.0
559
+ - prometheus-client==0.22.1
560
+ - propcache==0.3.2
561
+ - proto-plus==1.26.1
562
+ - psutil==7.0.0
563
+ - pubchempy==1.0.4
564
+ - py-cpuinfo==9.0.0
565
+ - pyarrow-hotfix==0.7
566
+ - pyasn1==0.6.1
567
+ - pyasn1-modules==0.4.2
568
+ - pybedtools==0.12.0
569
+ - pybigwig==0.3.24
570
+ - pyct==0.5.0
571
+ - pydantic==2.11.7
572
+ - pydantic-core==2.33.2
573
+ - pydantic-settings==2.10.1
574
+ - pydeseq2==0.5.2
575
+ - pydub==0.25.1
576
+ - pyfaidx==0.8.1.4
577
+ - pykalman==0.10.1
578
+ - pylev==1.4.0
579
+ - pyliftover==0.4.1
580
+ - pymassspec==2.6.0.post1
581
+ - pymc3==3.11.4
582
+ - pymed==0.8.9
583
+ - pymzml==2.5.11
584
+ - pynndescent==0.5.13
585
+ - pyogrio==0.11.1
586
+ - pyomo==6.9.2
587
+ - pypdf2==3.0.1
588
+ - pyperclip==1.9.0
589
+ - pyproj==3.7.1
590
+ - pyranges==0.1.4
591
+ - pyro-api==0.1.2
592
+ - pyro-ppl==1.9.1
593
+ - pysam==0.23.3
594
+ - pyscenic==0.12.1
595
+ - pysimdjson==6.0.2
596
+ # - pystan==3.10.0
597
+ - pytdc==0.4.0
598
+ - pytest==8.4.1
599
+ - python-dotenv==1.1.1
600
+ - python-json-logger==3.3.0
601
+ - python-levenshtein==0.27.1
602
+ - python-libsbml==5.20.5
603
+ - python-multipart==0.0.20
604
+ - python-utils==3.9.1
605
+ - pytorch-lightning==2.5.2
606
+ - pyzmq==27.0.1
607
+ - rapidfuzz==3.13.0
608
+ - rdkit==2025.3.5
609
+ - rdkit-pypi==2022.9.5
610
+ - regex==2025.7.34
611
+ - reportlab==4.4.3
612
+ - requests-toolbelt==1.0.0
613
+ - rfc3339-validator==0.1.4
614
+ - rfc3986-validator==0.1.1
615
+ - rfc3987-syntax==1.1.0
616
+ - rich==14.1.0
617
+ - rich-rst==1.3.1
618
+ - roifile==2025.5.10
619
+ - roman-numerals-py==3.1.0
620
+ - rsa==4.9.1
621
+ - ruamel-yaml==0.18.14
622
+ - ruamel-yaml-clib==0.2.12
623
+ - ruff==0.12.7
624
+ - s3fs==2025.7.0
625
+ - s3transfer==0.13.1
626
+ - safehttpx==0.1.6
627
+ - safetensors==0.5.3
628
+ - scanpy==1.11.4
629
+ - scholarly==1.7.11
630
+ - scikit-base==0.12.4
631
+ - scikit-bio==0.7.0
632
+ - scikit-image==0.25.2
633
+ - scikit-learn==1.7.1
634
+ - scikit-misc==0.5.1
635
+ - scipy==1.15.0
636
+ - scirpy==0.22.1
637
+ - scrublet==0.2.3
638
+ - scvelo==0.3.3
639
+ - scvi-tools==1.3.3
640
+ - sdjson==0.5.0
641
+ - seaborn==0.13.2
642
+ - segment-anything==1.0
643
+ - selenium==4.34.2
644
+ - semantic-version==2.10.0
645
+ - semver==3.0.4
646
+ - send2trash==1.8.3
647
+ - sentencepiece==0.2.0
648
+ - session-info==1.0.1
649
+ - session-info2==0.2
650
+ - sgmllib3k==1.0.0
651
+ - shapely==2.1.1
652
+ - shellingham==1.5.4
653
+ - simplejson==3.20.1
654
+ - slicerator==1.1.0
655
+ - sniffio==1.3.1
656
+ - snowballstemmer==3.0.1
657
+ - somacore==1.0.28
658
+ - sorted-nearest==0.0.39
659
+ - sortedcontainers==2.4.0
660
+ - soupsieve==2.7
661
+ - sparse==0.17.0
662
+ - sparsecca==0.3.1
663
+ - spatial-image==1.2.2
664
+ - spatialdata==0.4.0
665
+ - sphinx==8.2.3
666
+ - sphinx-rtd-theme==3.0.2
667
+ - sphinxcontrib-applehelp==2.0.0
668
+ - sphinxcontrib-devhelp==2.0.0
669
+ - sphinxcontrib-htmlhelp==2.1.0
670
+ - sphinxcontrib-jquery==4.1
671
+ - sphinxcontrib-jsmath==1.0.1
672
+ - sphinxcontrib-qthelp==2.0.0
673
+ - sphinxcontrib-serializinghtml==2.0.0
674
+ - sqlalchemy==2.0.42
675
+ - squarify==0.4.4
676
+ - squidpy==1.6.5
677
+ - sse-starlette==3.0.2
678
+ - starlette==0.47.2
679
+ - statsmodels==0.14.5
680
+ - stdlib-list==0.11.1
681
+ - swiglpk==5.0.12
682
+ - sympy==1.14.0
683
+ - tables==3.10.2
684
+ - tblib==3.1.0
685
+ - tensorboard==2.20.0
686
+ - tensorboard-data-server==0.7.2
687
+ - tensorstore==0.1.76
688
+ - terminado==0.18.1
689
+ - texttable==1.7.0
690
+ - theano-pymc==1.1.2
691
+ - threadpoolctl==3.6.0
692
+ - tifffile==2025.6.11
693
+ - tiktoken==0.9.0
694
+ - tiledbsoma==1.17.1
695
+ - tinycss2==1.4.0
696
+ - tokenizers==0.21.4
697
+ - tomlkit==0.13.3
698
+ - tooluniverse==0.2.0
699
+ - toolz==1.0.0
700
+ - torch==2.7.1
701
+ - torchmetrics==1.8.0
702
+ - torchvision==0.22.1
703
+ - tqdm==4.67.1
704
+ - trackpy==0.7
705
+ - transformers==4.54.1
706
+ - treescope==0.1.9
707
+ - trio==0.30.0
708
+ - trio-websocket==0.12.2
709
+ # - triton==3.3.1
710
+ - tskit==0.6.4
711
+ - typeguard==4.4.4
712
+ - typer==0.16.0
713
+ - types-python-dateutil==2.9.0.20250708
714
+ - typing-inspect==0.9.0
715
+ - typing-inspection==0.4.1
716
+ - umap-learn==0.5.9.post2
717
+ - uri-template==1.3.0
718
+ - uvicorn==0.35.0
719
+ - validators==0.35.0
720
+ - viennarna==2.7.0
721
+ - wadler-lindig==0.1.7
722
+ - webargs==8.7.0
723
+ - webcolors==24.11.1
724
+ - webencodings==0.5.1
725
+ - websocket-client==1.8.0
726
+ - websockets==15.0.1
727
+ - werkzeug==3.1.1
728
+ - widgetsnbextension==4.0.14
729
+ - wrapt==1.17.2
730
+ - wsproto==1.2.0
731
+ - xarray==2024.11.0
732
+ - xarray-dataclass==3.0.0
733
+ - xarray-dataclasses==1.9.1
734
+ - xarray-einstats==0.9.1
735
+ - xarray-schema==0.0.3
736
+ - xarray-spatial==0.4.0
737
+ - yamlordereddictloader==0.4.2
738
+ - yarl==1.20.1
739
+ - zarr==2.18.7
740
+ - zict==3.0.0
741
+ - zstandard==0.23.0
742
+ - mi-googlesearch-python==1.3.0.post1
biomni_env/install_cli_tools.sh ADDED
@@ -0,0 +1,912 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Script to install command-line bioinformatics tools
4
+
5
+ # Set up colors for output
6
+ GREEN='\033[0;32m'
7
+ RED='\033[0;31m'
8
+ YELLOW='\033[1;33m'
9
+ BLUE='\033[0;34m'
10
+ NC='\033[0m' # No Color
11
+
12
+ # Check for wget or fallback to curl
13
+ if command -v wget &> /dev/null; then
14
+ DOWNLOADER="wget -v -O"
15
+ elif command -v curl &> /dev/null; then
16
+ DOWNLOADER="curl -L -o"
17
+ echo -e "${YELLOW}Warning: wget not found, using curl instead.${NC}"
18
+ else
19
+ echo -e "${RED}Error: Neither wget nor curl is installed.${NC}"
20
+ echo -e "${YELLOW}Please install one of them (e.g., brew install wget on macOS).${NC}"
21
+ exit 1
22
+ fi
23
+
24
+ # Check if jq is installed
25
+ if ! command -v jq &> /dev/null; then
26
+ echo -e "${YELLOW}jq is not installed. Installing jq for JSON parsing...${NC}"
27
+ # Use conda to install jq (no sudo required)
28
+ conda install -y -c conda-forge jq
29
+
30
+ if [ $? -ne 0 ]; then
31
+ echo -e "${RED}Failed to install jq with conda. Please install it manually.${NC}"
32
+ echo "Visit: https://stedolan.github.io/jq/download/"
33
+ exit 1
34
+ fi
35
+ fi
36
+
37
+ # Function to clean up old installations
38
+ cleanup_old_installations() {
39
+ echo -e "${YELLOW}Checking for old installations...${NC}"
40
+
41
+ # Check for old installation in AFS
42
+ local old_afs_dir="/afs/cs.stanford.edu/u/$(whoami)/biomni_tools"
43
+ if [ -d "$old_afs_dir" ]; then
44
+ echo -e "${YELLOW}Found old installation in $old_afs_dir${NC}"
45
+ echo -e "${YELLOW}Removing old bin directory...${NC}"
46
+ rm -rf "$old_afs_dir/bin"
47
+ echo -e "${GREEN}Old bin directory removed.${NC}"
48
+ fi
49
+
50
+ # Check for old installation in HOME
51
+ local old_home_dir="$HOME/biomni_tools"
52
+ if [ -d "$old_home_dir" ] && [ "$old_home_dir" != "$TOOLS_DIR" ]; then
53
+ echo -e "${YELLOW}Found old installation in $old_home_dir${NC}"
54
+ echo -e "${YELLOW}Removing old bin directory...${NC}"
55
+ rm -rf "$old_home_dir/bin"
56
+ echo -e "${GREEN}Old bin directory removed.${NC}"
57
+ fi
58
+
59
+ # Clean up PATH
60
+ echo -e "${YELLOW}Cleaning up PATH...${NC}"
61
+ PATH=$(echo $PATH | tr ':' '\n' | grep -v "biomni_tools/bin" | tr '\n' ':' | sed 's/:$//')
62
+ export PATH="$TOOLS_DIR/bin:$PATH"
63
+ echo -e "${GREEN}PATH cleaned up.${NC}"
64
+ }
65
+
66
+ # Create a directory for CLI tools if it doesn't exist
67
+ if [ -n "$BIOMNI_TOOLS_DIR" ]; then
68
+ TOOLS_DIR="$BIOMNI_TOOLS_DIR"
69
+ else
70
+ TOOLS_DIR="$(pwd)/biomni_tools"
71
+ fi
72
+
73
+ # Clean up any existing installation if it exists
74
+ if [ -d "$TOOLS_DIR" ]; then
75
+ echo -e "${YELLOW}Cleaning up existing installation in $TOOLS_DIR...${NC}"
76
+ # Remove bin directory to clean up symlinks
77
+ rm -rf "$TOOLS_DIR/bin"
78
+ fi
79
+
80
+ # Clean up old installations
81
+ cleanup_old_installations
82
+
83
+ # Create fresh directories
84
+ mkdir -p "$TOOLS_DIR"
85
+ mkdir -p "$TOOLS_DIR/bin"
86
+
87
+ # Add the tools bin directory to PATH in the current session
88
+ # Remove any old paths first to avoid duplicates
89
+ PATH=$(echo $PATH | tr ':' '\n' | grep -v "biomni_tools/bin" | tr '\n' ':' | sed 's/:$//')
90
+ export PATH="$TOOLS_DIR/bin:$PATH"
91
+
92
+ # Clear the shell's command hash table to force it to re-search the PATH
93
+ hash -r 2>/dev/null || rehash 2>/dev/null || true
94
+
95
+ # Create a setup_path.sh file in the tools directory
96
+ echo "#!/bin/bash" > "$TOOLS_DIR/setup_path.sh"
97
+ echo "# Added by biomni setup" >> "$TOOLS_DIR/setup_path.sh"
98
+ echo "# Remove any old paths first to avoid duplicates" >> "$TOOLS_DIR/setup_path.sh"
99
+ echo "PATH=\$(echo \$PATH | tr ':' '\n' | grep -v \"biomni_tools/bin\" | tr '\n' ':' | sed 's/:$//')" >> "$TOOLS_DIR/setup_path.sh"
100
+ echo "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" >> "$TOOLS_DIR/setup_path.sh"
101
+ echo "# Clear the shell's command hash table to force it to re-search the PATH" >> "$TOOLS_DIR/setup_path.sh"
102
+ echo "hash -r 2>/dev/null || rehash 2>/dev/null || true" >> "$TOOLS_DIR/setup_path.sh"
103
+ chmod +x "$TOOLS_DIR/setup_path.sh"
104
+
105
+ # Config file path
106
+ CONFIG_FILE="cli_tools_config.json"
107
+
108
+ # Check if config file exists
109
+ if [ ! -f "$CONFIG_FILE" ]; then
110
+ echo -e "${RED}Configuration file $CONFIG_FILE not found.${NC}"
111
+ exit 1
112
+ fi
113
+
114
+ # Function to download and install a tool
115
+ install_tool() {
116
+ local tool_name=$1
117
+ local download_url=$2
118
+ local binary_path=$3
119
+ local version_cmd=$4
120
+ local tool_dir_name=$(echo "$tool_name" | tr '[:upper:]' '[:lower:]' | tr ' .' '_')
121
+ local binary_name=$(basename "$binary_path")
122
+
123
+ echo -e "\n${BLUE}=== Installing $tool_name ===${NC}"
124
+
125
+ # Check if the tool is already installed
126
+ if [ -f "$TOOLS_DIR/bin/$binary_name" ]; then
127
+ echo -e "${GREEN}$tool_name is already installed at $TOOLS_DIR/bin/$binary_name${NC}"
128
+ echo -e "${YELLOW}Testing installation...${NC}"
129
+
130
+ if [ -n "$version_cmd" ]; then
131
+ echo -e "${YELLOW}Running version command: $TOOLS_DIR/bin/$binary_name $version_cmd${NC}"
132
+ $TOOLS_DIR/bin/$binary_name $version_cmd
133
+ fi
134
+
135
+ echo -e "${GREEN}Skipping download and installation.${NC}"
136
+ return 0
137
+ fi
138
+
139
+ # Create directory for the tool
140
+ mkdir -p "$TOOLS_DIR/$tool_dir_name"
141
+
142
+ # Special handling for HOMER
143
+ if [ "$tool_name" = "HOMER" ]; then
144
+ echo -e "${YELLOW}Installing HOMER via Perl script...${NC}"
145
+
146
+ # Download the configuration script directly to the bin directory
147
+ $DOWNLOADER "$TOOLS_DIR/bin/configureHomer.pl" "$download_url"
148
+
149
+ if [ $? -ne 0 ]; then
150
+ echo -e "${RED}Failed to download HOMER configuration script from $download_url${NC}"
151
+ return 1
152
+ fi
153
+
154
+ # Make the script executable
155
+ chmod +x "$TOOLS_DIR/bin/configureHomer.pl"
156
+
157
+ # Create a HOMER installation directory
158
+ mkdir -p "$TOOLS_DIR/$tool_dir_name/homer"
159
+
160
+ # Run the configuration script
161
+ echo -e "${YELLOW}Running HOMER configuration script...${NC}"
162
+ echo -e "${YELLOW}This will install HOMER to $TOOLS_DIR/$tool_dir_name/homer${NC}"
163
+
164
+ # Install HOMER with batch mode ("-b" flag for basic installation)
165
+ # Use -local to specify the absolute installation directory
166
+ "$TOOLS_DIR/bin/configureHomer.pl" -install -local "$TOOLS_DIR/$tool_dir_name/homer" -b
167
+
168
+ if [ $? -ne 0 ]; then
169
+ echo -e "${RED}Failed to install HOMER.${NC}"
170
+ return 1
171
+ fi
172
+
173
+ # Create symlinks to HOMER binaries
174
+ echo -e "${YELLOW}Creating symlinks to HOMER binaries...${NC}"
175
+ for homer_bin in "$TOOLS_DIR/$tool_dir_name/homer/bin/"*; do
176
+ if [ -f "$homer_bin" ] && [ -x "$homer_bin" ]; then
177
+ ln -sf "$homer_bin" "$TOOLS_DIR/bin/$(basename "$homer_bin")"
178
+ fi
179
+ done
180
+
181
+ # Create a sourceable environment setup file
182
+ echo "#!/bin/bash" > "$TOOLS_DIR/$tool_dir_name/homer_env.sh"
183
+ echo "export PATH=\"$TOOLS_DIR/$tool_dir_name/homer/bin:\$PATH\"" >> "$TOOLS_DIR/$tool_dir_name/homer_env.sh"
184
+ echo "export HOMER=\"$TOOLS_DIR/$tool_dir_name/homer\"" >> "$TOOLS_DIR/$tool_dir_name/homer_env.sh"
185
+ chmod +x "$TOOLS_DIR/$tool_dir_name/homer_env.sh"
186
+
187
+ # Add the HOMER environment to the global setup_path.sh
188
+ echo "# HOMER environment" >> "$TOOLS_DIR/setup_path.sh"
189
+ echo "export PATH=\"$TOOLS_DIR/$tool_dir_name/homer/bin:\$PATH\"" >> "$TOOLS_DIR/setup_path.sh"
190
+ echo "export HOMER=\"$TOOLS_DIR/$tool_dir_name/homer\"" >> "$TOOLS_DIR/setup_path.sh"
191
+
192
+ echo -e "${GREEN}HOMER installed successfully!${NC}"
193
+ echo -e "${YELLOW}To use HOMER, you may need to source the environment file:${NC}"
194
+ echo -e "${GREEN}source $TOOLS_DIR/$tool_dir_name/homer_env.sh${NC}"
195
+ echo -e "${YELLOW}Or you can run configureHomer.pl directly from:${NC}"
196
+ echo -e "${GREEN}$TOOLS_DIR/bin/configureHomer.pl${NC}"
197
+ return 0
198
+
199
+ # Special handling for FastTree (requires compilation)
200
+ elif [ "$tool_name" = "FastTree" ]; then
201
+ echo -e "${YELLOW}Installing FastTree from source...${NC}"
202
+
203
+ # Download the source
204
+ $DOWNLOADER "$TOOLS_DIR/$tool_dir_name/FastTree.c" "$download_url"
205
+
206
+ if [ $? -ne 0 ]; then
207
+ echo -e "${RED}Failed to download FastTree source from $download_url${NC}"
208
+ return 1
209
+ fi
210
+
211
+ # Compile FastTree
212
+ echo -e "${YELLOW}Compiling FastTree...${NC}"
213
+ echo -e "${YELLOW}This may take a few minutes.${NC}"
214
+
215
+ # Try to compile with SSE support first
216
+ (cd "$TOOLS_DIR/$tool_dir_name" && gcc -O3 -finline-functions -funroll-loops -Wall -o FastTree FastTree.c -lm)
217
+
218
+ # If compilation fails, try without SSE support
219
+ if [ $? -ne 0 ]; then
220
+ echo -e "${YELLOW}Compilation with SSE failed, trying without SSE...${NC}"
221
+ (cd "$TOOLS_DIR/$tool_dir_name" && gcc -DNO_SSE -O3 -finline-functions -funroll-loops -Wall -o FastTree FastTree.c -lm)
222
+
223
+ if [ $? -ne 0 ]; then
224
+ echo -e "${RED}Failed to compile FastTree.${NC}"
225
+ return 1
226
+ fi
227
+ fi
228
+
229
+ # Create symlink
230
+ ln -sf "$TOOLS_DIR/$tool_dir_name/FastTree" "$TOOLS_DIR/bin/FastTree"
231
+
232
+ echo -e "${GREEN}FastTree installed successfully!${NC}"
233
+
234
+ # Test installation
235
+ if [ -f "$TOOLS_DIR/bin/FastTree" ]; then
236
+ echo -e "${GREEN}FastTree installed successfully!${NC}"
237
+ if [ -n "$version_cmd" ]; then
238
+ echo -e "${YELLOW}Running version command: $TOOLS_DIR/bin/FastTree $version_cmd | head -n 5${NC}"
239
+ "$TOOLS_DIR/bin/FastTree" $version_cmd | head -n 5
240
+ fi
241
+ else
242
+ echo -e "${RED}FastTree installation failed.${NC}"
243
+ echo -e "${YELLOW}Binary not found at: $TOOLS_DIR/bin/FastTree${NC}"
244
+ return 1
245
+ fi
246
+
247
+ return 0
248
+
249
+ # Special handling for BWA (requires compilation)
250
+ elif [ "$tool_name" = "BWA" ]; then
251
+ echo -e "${YELLOW}Installing BWA...${NC}"
252
+
253
+ # Check if git is installed
254
+ if ! command -v git &> /dev/null; then
255
+ echo -e "${RED}Git is not installed. Please install git to continue.${NC}"
256
+ return 1
257
+ fi
258
+
259
+ # Clone the repository
260
+ echo -e "${YELLOW}Cloning BWA repository from $download_url...${NC}"
261
+ mkdir -p "$TOOLS_DIR/$tool_dir_name"
262
+ git clone "$download_url" "$TOOLS_DIR/$tool_dir_name"
263
+
264
+ if [ $? -ne 0 ]; then
265
+ echo -e "${RED}Failed to clone BWA repository from $download_url${NC}"
266
+ return 1
267
+ fi
268
+
269
+ # Compile BWA
270
+ echo -e "${YELLOW}Compiling BWA...${NC}"
271
+ (cd "$TOOLS_DIR/$tool_dir_name" && make)
272
+
273
+ if [ $? -ne 0 ]; then
274
+ echo -e "${RED}Failed to compile BWA.${NC}"
275
+ return 1
276
+ fi
277
+
278
+ # Create symlink
279
+ ln -sf "$TOOLS_DIR/$tool_dir_name/bwa" "$TOOLS_DIR/bin/bwa"
280
+
281
+ # Test installation
282
+ if [ -f "$TOOLS_DIR/bin/bwa" ]; then
283
+ echo -e "${GREEN}BWA installed successfully!${NC}"
284
+ echo -e "${YELLOW}Running version check:${NC}"
285
+ "$TOOLS_DIR/bin/bwa" 2>&1 | head -n 3
286
+ else
287
+ echo -e "${RED}BWA installation failed.${NC}"
288
+ echo -e "${YELLOW}Binary not found at: $TOOLS_DIR/bin/bwa${NC}"
289
+ return 1
290
+ fi
291
+
292
+ return 0
293
+ fi
294
+
295
+ # Download the tool
296
+ echo -e "${YELLOW}Downloading $tool_name from: $download_url${NC}"
297
+
298
+ # Determine file extension
299
+ if [[ "$download_url" == *".zip" ]]; then
300
+ # Use verbose output to help diagnose issues
301
+ $DOWNLOADER "$TOOLS_DIR/$tool_dir_name.zip" "$download_url"
302
+
303
+ if [ $? -ne 0 ]; then
304
+ echo -e "${RED}Failed to download $tool_name from $download_url${NC}"
305
+ echo -e "${YELLOW}Please check your internet connection and try again.${NC}"
306
+ echo -e "${YELLOW}If the problem persists, the download URL may be incorrect or the server may be down.${NC}"
307
+ return 1
308
+ fi
309
+
310
+ echo -e "${YELLOW}Extracting $tool_name...${NC}"
311
+ unzip -q -o "$TOOLS_DIR/$tool_dir_name.zip" -d "$TOOLS_DIR/$tool_dir_name"
312
+
313
+ if [ $? -ne 0 ]; then
314
+ echo -e "${RED}Failed to extract $tool_name.${NC}"
315
+ return 1
316
+ fi
317
+
318
+ # Clean up
319
+ rm "$TOOLS_DIR/$tool_dir_name.zip"
320
+ elif [[ "$download_url" == *".tar.gz" ]]; then
321
+ # Use verbose output to help diagnose issues
322
+ $DOWNLOADER "$TOOLS_DIR/$tool_dir_name.tar.gz" "$download_url"
323
+
324
+ if [ $? -ne 0 ]; then
325
+ echo -e "${RED}Failed to download $tool_name from $download_url${NC}"
326
+ echo -e "${YELLOW}Please check your internet connection and try again.${NC}"
327
+ echo -e "${YELLOW}If the problem persists, the download URL may be incorrect or the server may be down.${NC}"
328
+ return 1
329
+ fi
330
+
331
+ echo -e "${YELLOW}Extracting $tool_name...${NC}"
332
+ mkdir -p "$TOOLS_DIR/$tool_dir_name"
333
+ tar -xzf "$TOOLS_DIR/$tool_dir_name.tar.gz" -C "$TOOLS_DIR/$tool_dir_name" --strip-components=1
334
+
335
+ if [ $? -ne 0 ]; then
336
+ echo -e "${RED}Failed to extract $tool_name.${NC}"
337
+ return 1
338
+ fi
339
+
340
+ # Clean up
341
+ rm "$TOOLS_DIR/$tool_dir_name.tar.gz"
342
+ # Handle executable files directly (for MUSCLE)
343
+ elif [[ "$tool_name" = "MUSCLE" ]]; then
344
+ echo -e "${YELLOW}Downloading $tool_name binary...${NC}"
345
+ $DOWNLOADER "$TOOLS_DIR/$tool_dir_name/$binary_name" "$download_url"
346
+
347
+ if [ $? -ne 0 ]; then
348
+ echo -e "${RED}Failed to download $tool_name from $download_url${NC}"
349
+ return 1
350
+ fi
351
+
352
+ # Make executable
353
+ chmod +x "$TOOLS_DIR/$tool_dir_name/$binary_name"
354
+
355
+ # Create symlink
356
+ ln -sf "$TOOLS_DIR/$tool_dir_name/$binary_name" "$TOOLS_DIR/bin/$binary_name"
357
+
358
+ # Test installation
359
+ if [ -f "$TOOLS_DIR/bin/$binary_name" ]; then
360
+ echo -e "${GREEN}$tool_name installed successfully!${NC}"
361
+ if [ -n "$version_cmd" ]; then
362
+ echo -e "${YELLOW}Running version command: $TOOLS_DIR/bin/$binary_name $version_cmd${NC}"
363
+ "$TOOLS_DIR/bin/$binary_name" $version_cmd
364
+ fi
365
+ else
366
+ echo -e "${RED}$tool_name installation failed.${NC}"
367
+ echo -e "${YELLOW}Binary not found at: $TOOLS_DIR/bin/$binary_name${NC}"
368
+ return 1
369
+ fi
370
+
371
+ return 0
372
+ else
373
+ echo -e "${RED}Unsupported file format for $tool_name.${NC}"
374
+ return 1
375
+ fi
376
+
377
+ # Find the binary path
378
+ local full_binary_path=$(find "$TOOLS_DIR/$tool_dir_name" -name "$(basename "$binary_path")" | head -n 1)
379
+
380
+ if [ -z "$full_binary_path" ]; then
381
+ echo -e "${RED}Could not find binary for $tool_name.${NC}"
382
+ echo -e "${YELLOW}Looking for binary at expected path: $TOOLS_DIR/$tool_dir_name/$binary_path${NC}"
383
+ full_binary_path="$TOOLS_DIR/$tool_dir_name/$binary_path"
384
+ fi
385
+
386
+ # Make the binary executable
387
+ chmod +x "$full_binary_path"
388
+
389
+ # Create symlink in bin directory
390
+ ln -sf "$full_binary_path" "$TOOLS_DIR/bin/$binary_name"
391
+
392
+ # Test installation
393
+ if [ -f "$TOOLS_DIR/bin/$binary_name" ]; then
394
+ echo -e "${GREEN}$tool_name installed successfully!${NC}"
395
+ if [ -n "$version_cmd" ]; then
396
+ echo -e "${YELLOW}Running version command: $TOOLS_DIR/bin/$binary_name $version_cmd${NC}"
397
+ "$TOOLS_DIR/bin/$binary_name" $version_cmd
398
+ fi
399
+ else
400
+ echo -e "${RED}$tool_name installation failed.${NC}"
401
+ echo -e "${YELLOW}Binary not found at: $TOOLS_DIR/bin/$binary_name${NC}"
402
+ return 1
403
+ fi
404
+
405
+ return 0
406
+ }
407
+
408
+ # Function to install a tool from the config
409
+ install_tool_from_config() {
410
+ local tool_index=$1
411
+ local auto_install=${2:-0}
412
+
413
+ # Get tool information from config
414
+ local tool_name=$(jq -r ".tools[$tool_index].name" "$CONFIG_FILE")
415
+ local tool_desc=$(jq -r ".tools[$tool_index].description" "$CONFIG_FILE")
416
+
417
+ # Determine the appropriate download URL based on the system
418
+ local download_url=""
419
+ if [[ "$(uname)" == "Darwin" ]]; then
420
+ if [[ "$(uname -m)" == "arm64" ]]; then
421
+ # macOS M1
422
+ download_url=$(jq -r ".tools[$tool_index].downloads.macos_arm64" "$CONFIG_FILE")
423
+ else
424
+ # macOS Intel
425
+ download_url=$(jq -r ".tools[$tool_index].downloads.macos_intel" "$CONFIG_FILE")
426
+ fi
427
+ else
428
+ # Linux
429
+ download_url=$(jq -r ".tools[$tool_index].downloads.linux" "$CONFIG_FILE")
430
+ fi
431
+
432
+ local binary_path=$(jq -r ".tools[$tool_index].binary_path" "$CONFIG_FILE")
433
+ local version_cmd=$(jq -r ".tools[$tool_index].version_command" "$CONFIG_FILE")
434
+
435
+ # Install the tool
436
+ echo -e "\n${YELLOW}$tool_name: $tool_desc${NC}"
437
+
438
+ # If auto_install is enabled, install without asking
439
+ if [ "$auto_install" -eq 1 ]; then
440
+ install_tool "$tool_name" "$download_url" "$binary_path" "$version_cmd"
441
+ return $?
442
+ fi
443
+
444
+ # Otherwise, ask for confirmation
445
+ read -p "Install $tool_name? (y/n) " -n 1 -r
446
+ echo
447
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
448
+ install_tool "$tool_name" "$download_url" "$binary_path" "$version_cmd"
449
+ return $?
450
+ fi
451
+
452
+ return 0
453
+ }
454
+
455
+ # Function to add a new tool to the config
456
+ add_new_tool() {
457
+ echo -e "\n${BLUE}=== Add a New Tool ===${NC}"
458
+
459
+ read -p "Tool name: " tool_name
460
+ read -p "Tool description: " tool_desc
461
+ read -p "Tool website: " tool_website
462
+ read -p "Linux download URL: " linux_url
463
+ read -p "macOS Intel download URL: " macos_intel_url
464
+ read -p "macOS ARM64 download URL: " macos_arm64_url
465
+ read -p "Binary path (relative to extraction): " binary_path
466
+ read -p "Version command (e.g., --version): " version_cmd
467
+
468
+ # Generate function name
469
+ function_name="install_$(echo "$tool_name" | tr '[:upper:]' '[:lower:]' | tr ' .' '_')"
470
+
471
+ # Create new tool JSON
472
+ new_tool=$(cat <<EOF
473
+ {
474
+ "name": "$tool_name",
475
+ "function_name": "$function_name",
476
+ "description": "$tool_desc",
477
+ "website": "$tool_website",
478
+ "downloads": {
479
+ "linux": "$linux_url",
480
+ "macos_intel": "$macos_intel_url",
481
+ "macos_arm64": "$macos_arm64_url"
482
+ },
483
+ "binary_path": "$binary_path",
484
+ "version_command": "$version_cmd"
485
+ }
486
+ EOF
487
+ )
488
+
489
+ # Add to config file
490
+ jq ".tools += [$new_tool]" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
491
+
492
+ echo -e "${GREEN}Tool added to configuration!${NC}"
493
+
494
+ # Ask if user wants to install the tool now
495
+ read -p "Install $tool_name now? (y/n) " -n 1 -r
496
+ echo
497
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
498
+ local tool_index=$(jq '.tools | length - 1' "$CONFIG_FILE")
499
+ install_tool_from_config "$tool_index"
500
+ fi
501
+ }
502
+
503
+ # Function to install all tools
504
+ install_all_tools() {
505
+ local auto_install=${1:-0}
506
+ local num_tools=$(jq '.tools | length' "$CONFIG_FILE")
507
+
508
+ echo -e "\n${BLUE}Installing all command-line tools...${NC}"
509
+
510
+ for (( i=0; i<$num_tools; i++ )); do
511
+ install_tool_from_config "$i" "$auto_install"
512
+ done
513
+
514
+ return 0
515
+ }
516
+
517
+ # Function to add PATH to shell profile
518
+ add_path_to_profile() {
519
+ local force_profile=${1:-""}
520
+
521
+ # Try to detect the shell profile file
522
+ local profile_file=""
523
+ local shell_name=$(basename "$SHELL")
524
+
525
+ # Create a sourceable file in the tools directory with PATH cleanup
526
+ echo "#!/bin/bash" > "$TOOLS_DIR/setup_path.sh"
527
+ echo "# Added by biomni setup" >> "$TOOLS_DIR/setup_path.sh"
528
+ echo "# Remove any old paths first to avoid duplicates" >> "$TOOLS_DIR/setup_path.sh"
529
+ echo "PATH=\$(echo \$PATH | tr ':' '\n' | grep -v \"biomni_tools/bin\" | tr '\n' ':' | sed 's/:$//')" >> "$TOOLS_DIR/setup_path.sh"
530
+ echo "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" >> "$TOOLS_DIR/setup_path.sh"
531
+ echo "# Clear the shell's command hash table to force it to re-search the PATH" >> "$TOOLS_DIR/setup_path.sh"
532
+ echo "hash -r 2>/dev/null || rehash 2>/dev/null || true" >> "$TOOLS_DIR/setup_path.sh"
533
+ chmod +x "$TOOLS_DIR/setup_path.sh"
534
+ echo -e "${GREEN}Created sourceable file at $TOOLS_DIR/setup_path.sh${NC}"
535
+ echo -e "${YELLOW}You can add this to your PATH by running:${NC}"
536
+ echo -e "${GREEN}source $TOOLS_DIR/setup_path.sh${NC}"
537
+
538
+ # If a specific profile is forced, use that
539
+ if [ -n "$force_profile" ]; then
540
+ profile_file="$HOME/$force_profile"
541
+ echo -e "${YELLOW}Using specified profile file: $profile_file${NC}"
542
+ else
543
+ # Auto-detect based on current shell
544
+ case "$shell_name" in
545
+ bash)
546
+ if [ -f "$HOME/.bash_profile" ]; then
547
+ profile_file="$HOME/.bash_profile"
548
+ elif [ -f "$HOME/.profile" ]; then
549
+ profile_file="$HOME/.profile"
550
+ elif [ -f "$HOME/.bashrc" ]; then
551
+ profile_file="$HOME/.bashrc"
552
+ fi
553
+ ;;
554
+ zsh)
555
+ profile_file="$HOME/.zshrc"
556
+ ;;
557
+ fish)
558
+ # Fish has a different configuration structure
559
+ mkdir -p "$HOME/.config/fish"
560
+ profile_file="$HOME/.config/fish/config.fish"
561
+ ;;
562
+ *)
563
+ # For other shells, try common profile files
564
+ if [ -f "$HOME/.profile" ]; then
565
+ profile_file="$HOME/.profile"
566
+ elif [ -f "$HOME/.bashrc" ]; then
567
+ profile_file="$HOME/.bashrc"
568
+ elif [ -f "$HOME/.zshrc" ]; then
569
+ profile_file="$HOME/.zshrc"
570
+ fi
571
+ ;;
572
+ esac
573
+ fi
574
+
575
+ # If we found a profile file and it's writable, add the PATH
576
+ if [ -n "$profile_file" ]; then
577
+ # Create the file if it doesn't exist
578
+ if [ ! -f "$profile_file" ]; then
579
+ echo -e "${YELLOW}Creating new profile file: $profile_file${NC}"
580
+ touch "$profile_file"
581
+ fi
582
+
583
+ if [ -w "$profile_file" ]; then
584
+ # Check if the PATH is already in the profile
585
+ if ! grep -q "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" "$profile_file"; then
586
+ # Remove any old biomni_tools paths first
587
+ if grep -q "biomni_tools/bin" "$profile_file"; then
588
+ echo -e "${YELLOW}Removing old biomni_tools paths from $profile_file...${NC}"
589
+ sed -i '/biomni_tools\/bin/d' "$profile_file"
590
+ fi
591
+
592
+ echo "" >> "$profile_file"
593
+ echo "# Added by biomni setup" >> "$profile_file"
594
+ echo "# Remove any old paths first to avoid duplicates" >> "$profile_file"
595
+ echo "PATH=\$(echo \$PATH | tr ':' '\n' | grep -v \"biomni_tools/bin\" | tr '\n' ':' | sed 's/:$//')" >> "$profile_file"
596
+
597
+ # Use the appropriate syntax for the shell
598
+ if [ "$shell_name" = "fish" ]; then
599
+ echo "set -gx PATH $TOOLS_DIR/bin \$PATH" >> "$profile_file"
600
+ else
601
+ echo "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" >> "$profile_file"
602
+ fi
603
+
604
+ echo -e "${GREEN}Added tools directory to PATH in $profile_file${NC}"
605
+ echo -e "${YELLOW}Note: You may need to restart your shell or run 'source $profile_file' for changes to take effect.${NC}"
606
+ else
607
+ echo -e "${GREEN}PATH already configured in $profile_file${NC}"
608
+ fi
609
+ else
610
+ echo -e "${RED}Profile file $profile_file is not writable.${NC}"
611
+ echo -e "${YELLOW}Please add the following line to your shell profile manually:${NC}"
612
+
613
+ if [ "$shell_name" = "fish" ]; then
614
+ echo -e "${GREEN}set -gx PATH $TOOLS_DIR/bin \$PATH${NC}"
615
+ else
616
+ echo -e "${GREEN}export PATH=\"$TOOLS_DIR/bin:\$PATH\"${NC}"
617
+ fi
618
+
619
+ echo -e "${YELLOW}Or source the setup file we created:${NC}"
620
+ echo -e "${GREEN}source $TOOLS_DIR/setup_path.sh${NC}"
621
+ fi
622
+ else
623
+ # If we couldn't find a profile file, just print instructions
624
+ echo -e "${YELLOW}Could not determine appropriate shell profile file.${NC}"
625
+ echo -e "${YELLOW}Please add the following line to your shell profile manually:${NC}"
626
+
627
+ if [ "$shell_name" = "fish" ]; then
628
+ echo -e "${GREEN}set -gx PATH $TOOLS_DIR/bin \$PATH${NC}"
629
+ else
630
+ echo -e "${GREEN}export PATH=\"$TOOLS_DIR/bin:\$PATH\"${NC}"
631
+ fi
632
+
633
+ echo -e "${YELLOW}Or source the setup file we created:${NC}"
634
+ echo -e "${GREEN}source $TOOLS_DIR/setup_path.sh${NC}"
635
+ fi
636
+
637
+ # Always export PATH for the current session
638
+ # Remove any old paths first to avoid duplicates
639
+ PATH=$(echo $PATH | tr ':' '\n' | grep -v "biomni_tools/bin" | tr '\n' ':' | sed 's/:$//')
640
+ export PATH="$TOOLS_DIR/bin:$PATH"
641
+
642
+ # Clear the shell's command hash table to force it to re-search the PATH
643
+ hash -r 2>/dev/null || rehash 2>/dev/null || true
644
+
645
+ # Create a test script to verify the tools are in the PATH
646
+ echo "#!/bin/bash" > "$TOOLS_DIR/test_tools.sh"
647
+ echo "echo \"Testing if tools are in the PATH...\"" >> "$TOOLS_DIR/test_tools.sh"
648
+ echo "echo \"Current PATH: \$PATH\"" >> "$TOOLS_DIR/test_tools.sh"
649
+ echo "echo \"\"" >> "$TOOLS_DIR/test_tools.sh"
650
+ echo "echo \"Looking for tools in: $TOOLS_DIR/bin\"" >> "$TOOLS_DIR/test_tools.sh"
651
+ echo "ls -la \"$TOOLS_DIR/bin\"" >> "$TOOLS_DIR/test_tools.sh"
652
+ echo "echo \"\"" >> "$TOOLS_DIR/test_tools.sh"
653
+ echo "echo \"Checking for path caching issues...\"" >> "$TOOLS_DIR/test_tools.sh"
654
+ echo "for tool in \$(ls \"$TOOLS_DIR/bin\"); do" >> "$TOOLS_DIR/test_tools.sh"
655
+ echo " which \$tool 2>/dev/null | grep -q \"/afs/cs.stanford.edu\" && {" >> "$TOOLS_DIR/test_tools.sh"
656
+ echo " echo \"WARNING: \$tool is still pointing to the old AFS location!\"" >> "$TOOLS_DIR/test_tools.sh"
657
+ echo " echo \"Run 'hash -r' (bash) or 'rehash' (zsh) to clear the command cache.\"" >> "$TOOLS_DIR/test_tools.sh"
658
+ echo " break" >> "$TOOLS_DIR/test_tools.sh"
659
+ echo " }" >> "$TOOLS_DIR/test_tools.sh"
660
+ echo "done" >> "$TOOLS_DIR/test_tools.sh"
661
+ echo "echo \"\"" >> "$TOOLS_DIR/test_tools.sh"
662
+ echo "for tool in \$(ls \"$TOOLS_DIR/bin\"); do" >> "$TOOLS_DIR/test_tools.sh"
663
+ echo " if command -v \$tool &> /dev/null; then" >> "$TOOLS_DIR/test_tools.sh"
664
+ echo " echo \"\$tool: \$(which \$tool)\"" >> "$TOOLS_DIR/test_tools.sh"
665
+ echo " else" >> "$TOOLS_DIR/test_tools.sh"
666
+ echo " echo \"\$tool: NOT FOUND IN PATH\"" >> "$TOOLS_DIR/test_tools.sh"
667
+ echo " fi" >> "$TOOLS_DIR/test_tools.sh"
668
+ echo "done" >> "$TOOLS_DIR/test_tools.sh"
669
+ chmod +x "$TOOLS_DIR/test_tools.sh"
670
+
671
+ echo -e "${YELLOW}Created test script at $TOOLS_DIR/test_tools.sh${NC}"
672
+ echo -e "${YELLOW}You can run it to verify the tools are in your PATH:${NC}"
673
+ echo -e "${GREEN}$TOOLS_DIR/test_tools.sh${NC}"
674
+
675
+ # Create a quick fix script for path caching issues
676
+ echo "#!/bin/bash" > "$TOOLS_DIR/fix_path.sh"
677
+ echo "# Script to fix path caching issues" >> "$TOOLS_DIR/fix_path.sh"
678
+ echo "echo \"Fixing path caching issues...\"" >> "$TOOLS_DIR/fix_path.sh"
679
+ echo "# Remove any old paths first to avoid duplicates" >> "$TOOLS_DIR/fix_path.sh"
680
+ echo "PATH=\$(echo \$PATH | tr ':' '\n' | grep -v \"biomni_tools/bin\" | tr '\n' ':' | sed 's/:$//')" >> "$TOOLS_DIR/fix_path.sh"
681
+ echo "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" >> "$TOOLS_DIR/fix_path.sh"
682
+ echo "# Clear the shell's command hash table to force it to re-search the PATH" >> "$TOOLS_DIR/fix_path.sh"
683
+ echo "hash -r 2>/dev/null || rehash 2>/dev/null || true" >> "$TOOLS_DIR/fix_path.sh"
684
+ echo "echo \"Path fixed. Try running your command again.\"" >> "$TOOLS_DIR/fix_path.sh"
685
+ chmod +x "$TOOLS_DIR/fix_path.sh"
686
+
687
+ echo -e "${YELLOW}Created fix script at $TOOLS_DIR/fix_path.sh${NC}"
688
+ echo -e "${YELLOW}If you encounter 'No such file or directory' errors, run:${NC}"
689
+ echo -e "${GREEN}source $TOOLS_DIR/fix_path.sh${NC}"
690
+ }
691
+
692
+ # Function to verify installation
693
+ verify_installation() {
694
+ echo -e "\n${BLUE}=== Verifying Installation ===${NC}"
695
+ echo -e "${YELLOW}Tools directory: $TOOLS_DIR${NC}"
696
+ echo -e "${YELLOW}Bin directory: $TOOLS_DIR/bin${NC}"
697
+
698
+ # Check if bin directory exists
699
+ if [ ! -d "$TOOLS_DIR/bin" ]; then
700
+ echo -e "${RED}Bin directory does not exist!${NC}"
701
+ return 1
702
+ fi
703
+
704
+ # Check if there are any tools in the bin directory
705
+ local tool_count=$(ls -1 "$TOOLS_DIR/bin" 2>/dev/null | wc -l)
706
+ if [ "$tool_count" -eq 0 ]; then
707
+ echo -e "${RED}No tools found in bin directory!${NC}"
708
+ return 1
709
+ fi
710
+
711
+ echo -e "${GREEN}Found $tool_count tools in bin directory.${NC}"
712
+
713
+ # List all tools
714
+ echo -e "${YELLOW}Installed tools:${NC}"
715
+ ls -la "$TOOLS_DIR/bin"
716
+
717
+ # Check if tools are in PATH
718
+ echo -e "\n${YELLOW}Checking if tools are in PATH...${NC}"
719
+ echo -e "${YELLOW}Current PATH: $PATH${NC}"
720
+
721
+ # Check if TOOLS_DIR/bin is in PATH
722
+ if [[ "$PATH" == *"$TOOLS_DIR/bin"* ]]; then
723
+ echo -e "${GREEN}Tools directory is in PATH.${NC}"
724
+ else
725
+ echo -e "${RED}Tools directory is NOT in PATH!${NC}"
726
+ echo -e "${YELLOW}Please run: source $TOOLS_DIR/setup_path.sh${NC}"
727
+ return 1
728
+ fi
729
+
730
+ # Check if each tool is accessible
731
+ echo -e "\n${YELLOW}Testing tool accessibility:${NC}"
732
+ for tool in $(ls "$TOOLS_DIR/bin"); do
733
+ if command -v "$tool" &> /dev/null; then
734
+ echo -e "${GREEN}$tool: $(which $tool)${NC}"
735
+ else
736
+ echo -e "${RED}$tool: NOT FOUND IN PATH${NC}"
737
+ fi
738
+ done
739
+
740
+ echo -e "\n${GREEN}Installation verification completed.${NC}"
741
+ echo -e "${YELLOW}If you encounter any issues, please run:${NC}"
742
+ echo -e "${GREEN}source $TOOLS_DIR/setup_path.sh${NC}"
743
+ echo -e "${YELLOW}And then run the test script:${NC}"
744
+ echo -e "${GREEN}$TOOLS_DIR/test_tools.sh${NC}"
745
+
746
+ echo -e "\n${YELLOW}IMPORTANT: If you see 'No such file or directory' errors when running tools,${NC}"
747
+ echo -e "${YELLOW}your shell may be using cached paths to old tool locations.${NC}"
748
+ echo -e "${YELLOW}To fix this, run:${NC}"
749
+ echo -e "${GREEN}source $TOOLS_DIR/fix_path.sh${NC}"
750
+ echo -e "${YELLOW}Or run one of these commands:${NC}"
751
+ echo -e "${GREEN}hash -r${NC} (for bash/sh)"
752
+ echo -e "${GREEN}rehash${NC} (for zsh/csh)"
753
+ echo -e "${YELLOW}Or simply start a new terminal session.${NC}"
754
+
755
+ return 0
756
+ }
757
+
758
+ # Main installation function
759
+ install_cli_tools() {
760
+ echo -e "${YELLOW}=== Installing Command-Line Bioinformatics Tools ===${NC}"
761
+
762
+ # Try to add the tools bin directory to PATH
763
+ add_path_to_profile
764
+
765
+ # Check if auto-install mode is enabled
766
+ if [ -n "$BIOMNI_AUTO_INSTALL" ]; then
767
+ install_all_tools 1
768
+
769
+ echo -e "\n${GREEN}CLI tools installation completed!${NC}"
770
+ echo -e "The tools are installed in: ${YELLOW}$TOOLS_DIR${NC}"
771
+ echo -e "Binaries are symlinked in: ${YELLOW}$TOOLS_DIR/bin${NC}"
772
+ echo -e "Tools have been added to your PATH for the current session."
773
+
774
+ # Verify installation
775
+ verify_installation
776
+
777
+ return 0
778
+ fi
779
+
780
+ # Get the number of tools in the config
781
+ local num_tools=$(jq '.tools | length' "$CONFIG_FILE")
782
+
783
+ # Display menu
784
+ echo -e "\n${BLUE}Available tools:${NC}"
785
+ for (( i=0; i<$num_tools; i++ )); do
786
+ local tool_name=$(jq -r ".tools[$i].name" "$CONFIG_FILE")
787
+ local tool_desc=$(jq -r ".tools[$i].description" "$CONFIG_FILE")
788
+ echo -e "${YELLOW}$((i+1)). $tool_name${NC} - $tool_desc"
789
+ done
790
+ echo -e "${YELLOW}$((num_tools+1)). Add a new tool${NC}"
791
+ echo -e "${YELLOW}$((num_tools+2)). Install all tools${NC}"
792
+ echo -e "${YELLOW}$((num_tools+3)). Add tools directory to shell profile${NC}"
793
+ echo -e "${YELLOW}0. Exit${NC}"
794
+
795
+ # Get user choice
796
+ read -p "Enter your choice (0-$((num_tools+3))): " choice
797
+
798
+ if [[ "$choice" -eq 0 ]]; then
799
+ echo -e "${YELLOW}Exiting...${NC}"
800
+ return 0
801
+ elif [[ "$choice" -eq $((num_tools+1)) ]]; then
802
+ add_new_tool
803
+ elif [[ "$choice" -eq $((num_tools+2)) ]]; then
804
+ # Install all tools
805
+ install_all_tools
806
+ elif [[ "$choice" -eq $((num_tools+3)) ]]; then
807
+ # Add to shell profile
808
+ echo -e "\n${BLUE}=== Adding to Shell Profile ===${NC}"
809
+ echo -e "1. Auto-detect profile (recommended)"
810
+ echo -e "2. Specify profile file"
811
+ read -p "Enter your choice (1-2): " profile_choice
812
+
813
+ if [[ "$profile_choice" -eq 1 ]]; then
814
+ add_path_to_profile
815
+ elif [[ "$profile_choice" -eq 2 ]]; then
816
+ echo -e "\nCommon profile files:"
817
+ echo -e "- .bash_profile (for Bash on macOS/login shells)"
818
+ echo -e "- .bashrc (for Bash on Linux/non-login shells)"
819
+ echo -e "- .zshrc (for Zsh)"
820
+ echo -e "- .config/fish/config.fish (for Fish)"
821
+ echo -e "- .profile (generic)"
822
+ read -p "Enter profile filename (without $HOME/ prefix): " profile_file
823
+ add_path_to_profile "$profile_file"
824
+ else
825
+ echo -e "${RED}Invalid choice.${NC}"
826
+ fi
827
+ elif [[ "$choice" -ge 1 ]] && [[ "$choice" -le "$num_tools" ]]; then
828
+ # Install selected tool
829
+ install_tool_from_config "$((choice-1))"
830
+ else
831
+ echo -e "${RED}Invalid choice.${NC}"
832
+ return 1
833
+ fi
834
+
835
+ echo -e "\n${GREEN}CLI tools installation completed!${NC}"
836
+ echo -e "The tools are installed in: ${YELLOW}$TOOLS_DIR${NC}"
837
+ echo -e "Binaries are symlinked in: ${YELLOW}$TOOLS_DIR/bin${NC}"
838
+ echo -e "Tools have been added to your PATH for the current session."
839
+
840
+ # Ask if user wants to install more tools or configure PATH
841
+ read -p "Install more tools or configure PATH? (y/n) " -n 1 -r
842
+ echo
843
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
844
+ install_cli_tools
845
+ fi
846
+ }
847
+
848
+ # Function to display help message
849
+ show_help() {
850
+ echo "Usage: $0 [OPTION]"
851
+ echo "Install command-line bioinformatics tools for BioAgentOS."
852
+ echo
853
+ echo "Options:"
854
+ echo " --auto Automatically install all tools without prompting"
855
+ echo " --profile PROFILE Add tools directory to the specified shell profile file"
856
+ echo " Example: $0 --profile .zshrc"
857
+ echo " --help Display this help message and exit"
858
+ echo
859
+ echo "Without options, the script runs in interactive mode."
860
+ }
861
+
862
+ # Check command-line arguments
863
+ if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
864
+ show_help
865
+ exit 0
866
+ elif [ "$1" = "--auto" ]; then
867
+ # Set BIOMNI_AUTO_INSTALL if it's not already set
868
+ export BIOMNI_AUTO_INSTALL=1
869
+
870
+ # Run in auto-install mode without showing the menu
871
+ install_all_tools 1
872
+
873
+ # Add to profile automatically
874
+ add_path_to_profile
875
+
876
+ # Verify installation
877
+ verify_installation
878
+
879
+ echo -e "\n${GREEN}CLI tools installation completed!${NC}"
880
+ echo -e "The tools are installed in: ${YELLOW}$TOOLS_DIR${NC}"
881
+ echo -e "Binaries are symlinked in: ${YELLOW}$TOOLS_DIR/bin${NC}"
882
+ echo -e "Tools have been added to your PATH for the current session."
883
+
884
+ exit 0
885
+ elif [ "$1" = "--profile" ] && [ -n "$2" ]; then
886
+ # Add to a specific profile file
887
+ add_path_to_profile "$2"
888
+ exit 0
889
+ elif [ -n "$BIOMNI_AUTO_INSTALL" ]; then
890
+ # Run in auto-install mode without showing the menu
891
+ install_all_tools 1
892
+
893
+ # Add to profile automatically
894
+ add_path_to_profile
895
+
896
+ # Verify installation
897
+ verify_installation
898
+
899
+ echo -e "\n${GREEN}CLI tools installation completed!${NC}"
900
+ echo -e "The tools are installed in: ${YELLOW}$TOOLS_DIR${NC}"
901
+ echo -e "Binaries are symlinked in: ${YELLOW}$TOOLS_DIR/bin${NC}"
902
+ echo -e "Tools have been added to your PATH for the current session."
903
+
904
+ exit 0
905
+ elif [ -n "$1" ]; then
906
+ echo -e "${RED}Unknown option: $1${NC}"
907
+ show_help
908
+ exit 1
909
+ else
910
+ # Run the interactive installation
911
+ install_cli_tools
912
+ fi
biomni_env/install_r_packages.R ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env Rscript
2
+
3
+ # Script to install R packages that might be missing from the conda environment
4
+ # Run this script after creating the conda environment if you encounter issues with R packages
5
+
6
+ # Set repository
7
+ options(repos = c(CRAN = "https://cran.rstudio.com/"))
8
+
9
+ # Function to install a package if it's not already installed
10
+ install_if_missing <- function(package_name, bioconductor = FALSE) {
11
+ if (!require(package_name, character.only = TRUE, quietly = TRUE)) {
12
+ cat(sprintf("Installing package: %s\n", package_name))
13
+
14
+ if (bioconductor) {
15
+ if (!require("BiocManager", quietly = TRUE)) {
16
+ install.packages("BiocManager", dependencies = TRUE)
17
+ }
18
+ BiocManager::install(package_name, update = FALSE, ask = FALSE, dependencies = TRUE)
19
+ } else {
20
+ install.packages(package_name, dependencies = TRUE)
21
+ }
22
+
23
+ # Check if installation was successful
24
+ if (require(package_name, character.only = TRUE, quietly = TRUE)) {
25
+ cat(sprintf("✓ Successfully installed %s\n", package_name))
26
+ } else {
27
+ cat(sprintf("✗ Failed to install %s\n", package_name))
28
+ }
29
+ } else {
30
+ cat(sprintf("✓ Package %s is already installed\n", package_name))
31
+ }
32
+ }
33
+
34
+ # Install BiocManager first with dependencies
35
+ cat("Installing BiocManager...\n")
36
+ if (!require("BiocManager", quietly = TRUE)) {
37
+ install.packages("BiocManager", dependencies = TRUE)
38
+ }
39
+
40
+ # Make sure BiocManager is up to date
41
+ BiocManager::install(version = BiocManager::version(), update = TRUE, ask = FALSE)
42
+
43
+ # Install system dependencies for Bioconductor packages
44
+ cat("\nInstalling system dependencies for Bioconductor packages...\n")
45
+
46
+ # Install CRAN packages
47
+ cran_packages <- c(
48
+ "ggplot2",
49
+ "lme4",
50
+ "dplyr",
51
+ "tidyr",
52
+ "readr",
53
+ "stringr",
54
+ "Matrix", # Required for Seurat
55
+ "Rcpp", # Required for many packages
56
+ "devtools", # Useful for package installation
57
+ "remotes", # Required for GitHub installations
58
+ "harmony"
59
+ )
60
+
61
+ # Install Bioconductor packages
62
+ bioc_packages <- c(
63
+ "DESeq2",
64
+ "dada2",
65
+ "xcms",
66
+ "flowCore",
67
+ "edgeR",
68
+ "limma"
69
+ )
70
+
71
+ # Install CRAN packages
72
+ cat("\nInstalling CRAN packages...\n")
73
+ for (pkg in cran_packages) {
74
+ install_if_missing(pkg)
75
+ }
76
+
77
+ # Install Bioconductor packages one by one with specific handling
78
+ cat("\nInstalling Bioconductor packages...\n")
79
+
80
+ # Install DESeq2 with dependencies
81
+ cat("\nInstalling DESeq2 and dependencies...\n")
82
+ if (!require("DESeq2", quietly = TRUE)) {
83
+ BiocManager::install("DESeq2", dependencies = TRUE, update = FALSE, ask = FALSE)
84
+ }
85
+
86
+ # Install WGCNA (from CRAN, not Bioconductor)
87
+ # Following official instructions from https://cran.r-project.org/web/packages/WGCNA/index.html
88
+ cat("\nInstalling WGCNA and dependencies...\n")
89
+ if (!require("WGCNA", quietly = TRUE)) {
90
+ # Install WGCNA dependencies first
91
+ wgcna_deps <- c("dynamicTreeCut", "fastcluster", "matrixStats", "Hmisc", "foreach", "doParallel")
92
+ for (dep in wgcna_deps) {
93
+ install_if_missing(dep)
94
+ }
95
+
96
+ # Install Bioconductor dependencies for WGCNA
97
+ bioc_deps <- c("impute", "preprocessCore", "GO.db", "AnnotationDbi")
98
+ for (dep in bioc_deps) {
99
+ if (!require(dep, character.only = TRUE, quietly = TRUE)) {
100
+ BiocManager::install(dep, update = FALSE, ask = FALSE)
101
+ }
102
+ }
103
+
104
+ # Install WGCNA from CRAN
105
+ install.packages("WGCNA", dependencies = TRUE)
106
+ }
107
+
108
+ # Install clusterProfiler
109
+ cat("\nInstalling clusterProfiler and dependencies...\n")
110
+ if (!require("clusterProfiler", quietly = TRUE)) {
111
+ BiocManager::install("clusterProfiler", dependencies = TRUE, update = FALSE, ask = FALSE)
112
+ }
113
+
114
+ # Install remaining Bioconductor packages
115
+ remaining_bioc <- setdiff(bioc_packages, c("DESeq2", "clusterProfiler"))
116
+ for (pkg in remaining_bioc) {
117
+ install_if_missing(pkg, bioconductor = TRUE)
118
+ }
119
+
120
+ # Verify installations
121
+ cat("\nVerifying installations...\n")
122
+ all_packages <- c(cran_packages, bioc_packages, "WGCNA")
123
+ for (pkg in unique(all_packages)) {
124
+ if (require(pkg, character.only = TRUE, quietly = TRUE)) {
125
+ cat(sprintf("✓ Package %s is successfully installed\n", pkg))
126
+ } else {
127
+ cat(sprintf("✗ Package %s is NOT installed\n", pkg))
128
+ }
129
+ }
130
+
131
+ cat("\nPackage installation completed!\n")
132
+ cat("If you still encounter issues with specific packages, please install them manually.\n")
biomni_env/new_software_v008.sh ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni v0.0.8 - Incremental Software Installation
2
+ # Add any new packages/software introduced in version 0.0.8 below
3
+ pip install transformers sentencepiece langchain-google-genai langchain_ollama mcp
4
+ pip install lazyslide
5
+ pip install "git+https://github.com/YosefLab/popV.git@refs/pull/100/head"
6
+ pip install uv
7
+ sudo apt-get install git-lfs # or brew install git-lfs if you are on macOS
8
+ git lfs install
9
+ pip install pybiomart
10
+ pip install fair-esm
11
+ pip install uv
12
+ uv pip install transcriptformer
13
+ pip install "zarr>=2.0,<3.0" #this resolved transcripformer download isses
14
+ uv tool install arc-state
15
+ pip install nnunet nibabel nilearn
16
+ pip install mi-googlesearch-python
17
+ pip install git+https://github.com/pylabrobot/pylabrobot.git
18
+ conda install weasyprint
biomni_env/r_packages.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ name: biomni_e1
2
+ channels:
3
+ - conda-forge
4
+ - defaults
5
+ dependencies:
6
+ - r-essentials
7
+ - r-base>=4.4
biomni_env/setup.sh ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # BioAgentOS - Biomni Environment Setup Script
4
+ # This script sets up a comprehensive bioinformatics environment with various tools and packages
5
+
6
+ # Set up colors for output
7
+ GREEN='\033[0;32m'
8
+ RED='\033[0;31m'
9
+ YELLOW='\033[1;33m'
10
+ BLUE='\033[0;34m'
11
+ NC='\033[0m' # No Color
12
+
13
+ # Default tools directory is the current directory
14
+ DEFAULT_TOOLS_DIR="$(pwd)/biomni_tools"
15
+ TOOLS_DIR=""
16
+
17
+ echo -e "${YELLOW}=== Biomni Environment Setup ===${NC}"
18
+ echo -e "${BLUE}This script will set up a comprehensive bioinformatics environment with various tools and packages.${NC}"
19
+
20
+ # Check if conda is installed
21
+ if ! command -v conda &> /dev/null && ! command -v micromamba &> /dev/null; then
22
+ echo -e "${RED}Error: Conda is not installed or not in PATH.${NC}"
23
+ echo "Please install Miniconda or Anaconda first."
24
+ echo "Visit: https://docs.conda.io/en/latest/miniconda.html"
25
+ exit 1
26
+ fi
27
+
28
+ # redirect to micromamba if needed
29
+ if ! command -v conda &> /dev/null && command -v micromamba &> /dev/null; then
30
+ conda() {
31
+ micromamba "$@"
32
+ }
33
+ export -f conda
34
+ fi
35
+
36
+ # Function to handle errors
37
+ handle_error() {
38
+ local exit_code=$1
39
+ local error_message=$2
40
+ local optional=${3:-false}
41
+
42
+ if [ $exit_code -ne 0 ]; then
43
+ echo -e "${RED}Error: $error_message${NC}"
44
+ if [ "$optional" = true ]; then
45
+ echo -e "${YELLOW}Continuing with setup as this component is optional.${NC}"
46
+ return 0
47
+ else
48
+ if [ -z "$NON_INTERACTIVE" ]; then
49
+ read -p "Continue with setup? (y/n) " -n 1 -r
50
+ echo
51
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
52
+ echo -e "${RED}Setup aborted.${NC}"
53
+ exit 1
54
+ fi
55
+ else
56
+ echo -e "${YELLOW}Non-interactive mode: continuing despite error.${NC}"
57
+ fi
58
+ fi
59
+ fi
60
+ return $exit_code
61
+ }
62
+
63
+ # Function to install a specific environment file
64
+ install_env_file() {
65
+ local env_file=$1
66
+ local description=$2
67
+ local optional=${3:-false}
68
+
69
+ echo -e "\n${BLUE}=== Installing $description ===${NC}"
70
+
71
+ if [ "$optional" = true ]; then
72
+ if [ -z "$NON_INTERACTIVE" ]; then
73
+ read -p "Do you want to install $description? (y/n) " -n 1 -r
74
+ echo
75
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
76
+ echo -e "${YELLOW}Skipping $description installation.${NC}"
77
+ return 0
78
+ fi
79
+ else
80
+ echo -e "${YELLOW}Non-interactive mode: automatically installing $description.${NC}"
81
+ fi
82
+ fi
83
+
84
+ echo -e "${YELLOW}Installing $description from $env_file...${NC}"
85
+ conda env update -f $env_file
86
+ handle_error $? "Failed to install $description." $optional
87
+
88
+ if [ $? -eq 0 ]; then
89
+ echo -e "${GREEN}Successfully installed $description!${NC}"
90
+ fi
91
+ }
92
+
93
+ # Function to install CLI tools
94
+ install_cli_tools() {
95
+ echo -e "\n${BLUE}=== Installing Command-Line Bioinformatics Tools ===${NC}"
96
+
97
+ # Ask user for the directory to install CLI tools
98
+ if [ -z "$NON_INTERACTIVE" ]; then
99
+ echo -e "${YELLOW}Where would you like to install the command-line tools?${NC}"
100
+ echo -e "${BLUE}Default: $DEFAULT_TOOLS_DIR${NC}"
101
+ read -p "Enter directory path (or press Enter for default): " user_tools_dir
102
+ else
103
+ user_tools_dir=""
104
+ echo -e "${YELLOW}Non-interactive mode: using default directory $DEFAULT_TOOLS_DIR for CLI tools.${NC}"
105
+ fi
106
+
107
+ if [ -z "$user_tools_dir" ]; then
108
+ TOOLS_DIR="$DEFAULT_TOOLS_DIR"
109
+ else
110
+ TOOLS_DIR="$user_tools_dir"
111
+ fi
112
+
113
+ # Export the tools directory for the CLI tools installer
114
+ export BIOMNI_TOOLS_DIR="$TOOLS_DIR"
115
+
116
+ echo -e "${YELLOW}Installing command-line tools (PLINK, IQ-TREE, GCTA, etc.) to $TOOLS_DIR...${NC}"
117
+
118
+ # Set environment variable to skip prompts in the CLI tools installer
119
+ export BIOMNI_AUTO_INSTALL=1
120
+
121
+ # Run the CLI tools installer
122
+ bash install_cli_tools.sh
123
+ handle_error $? "Failed to install CLI tools." true
124
+
125
+ if [ $? -eq 0 ]; then
126
+ echo -e "${GREEN}Successfully installed command-line tools!${NC}"
127
+
128
+ # Create a setup_path.sh file in the current directory
129
+ echo "#!/bin/bash" > setup_path.sh
130
+ echo "# Added by biomni setup" >> setup_path.sh
131
+ echo "# Remove any old paths first to avoid duplicates" >> setup_path.sh
132
+ echo "PATH=\$(echo \$PATH | tr ':' '\n' | grep -v \"biomni_tools/bin\" | tr '\n' ':' | sed 's/:$//')" >> setup_path.sh
133
+ echo "export PATH=\"$TOOLS_DIR/bin:\$PATH\"" >> setup_path.sh
134
+ chmod +x setup_path.sh
135
+
136
+ echo -e "${GREEN}Created setup_path.sh in the current directory.${NC}"
137
+ echo -e "${YELLOW}You can add the tools to your PATH by running:${NC}"
138
+ echo -e "${GREEN}source $(pwd)/setup_path.sh${NC}"
139
+
140
+ # Also add to the current session
141
+ # Remove any old paths first to avoid duplicates
142
+ PATH=$(echo $PATH | tr ':' '\n' | grep -v "biomni_tools/bin" | tr '\n' ':' | sed 's/:$//')
143
+ export PATH="$TOOLS_DIR/bin:$PATH"
144
+ fi
145
+
146
+ # Unset the environment variables
147
+ unset BIOMNI_AUTO_INSTALL
148
+ unset BIOMNI_TOOLS_DIR
149
+ }
150
+
151
+ # Main installation process
152
+ main() {
153
+ # Step 1: Create base conda environment
154
+ echo -e "\n${YELLOW}Step 1: Creating base environment from environment.yml...${NC}"
155
+ conda env create -n biomni_e1 -f environment.yml
156
+ handle_error $? "Failed to create base conda environment."
157
+
158
+ # Step 2: Activate the environment
159
+ echo -e "\n${YELLOW}Step 2: Activating conda environment...${NC}"
160
+ if command -v micromamba &> /dev/null; then
161
+ eval "$("$MAMBA_EXE" shell hook --shell bash)"
162
+ micromamba activate biomni_e1
163
+ else
164
+ eval "$(conda shell.bash hook)"
165
+ conda activate biomni_e1
166
+ fi
167
+ handle_error $? "Failed to activate biomni_e1 environment."
168
+
169
+ # Step 3: Install core bioinformatics tools (including QIIME2)
170
+ echo -e "\n${YELLOW}Step 3: Installing core bioinformatics tools (including QIIME2)...${NC}"
171
+ install_env_file "bio_env.yml" "core bioinformatics tools"
172
+
173
+ # Step 4: Install R packages
174
+ echo -e "\n${YELLOW}Step 4: Installing R packages...${NC}"
175
+ install_env_file "r_packages.yml" "core R packages"
176
+
177
+ # Step 5: Install additional R packages through R's package manager
178
+ echo -e "\n${YELLOW}Step 5: Installing additional R packages through R's package manager...${NC}"
179
+ Rscript install_r_packages.R
180
+ handle_error $? "Failed to install additional R packages." true
181
+
182
+ # Step 6: Install CLI tools
183
+ echo -e "\n${YELLOW}Step 6: Installing command-line bioinformatics tools...${NC}"
184
+ install_cli_tools
185
+
186
+ # Setup completed
187
+ echo -e "\n${GREEN}=== Biomni Environment Setup Completed! ===${NC}"
188
+ echo -e "You can now run the example analysis with: ${YELLOW}python bio_analysis_example.py${NC}"
189
+ echo -e "To activate this environment in the future, run: ${YELLOW}conda activate biomni_e1${NC}"
190
+ echo -e "To use BioAgentOS, navigate to the BioAgentOS directory and follow the instructions in the README."
191
+
192
+ # Display CLI tools setup instructions
193
+ if [ -n "$TOOLS_DIR" ]; then
194
+ echo -e "\n${BLUE}=== Command-Line Tools Setup ===${NC}"
195
+ echo -e "The command-line tools are installed in: ${YELLOW}$TOOLS_DIR${NC}"
196
+ echo -e "To add these tools to your PATH, run: ${YELLOW}source $(pwd)/setup_path.sh${NC}"
197
+ echo -e "You can also add this line to your shell profile for permanent access:"
198
+ echo -e "${GREEN}export PATH=\"$TOOLS_DIR/bin:\$PATH\"${NC}"
199
+
200
+ # Test if tools are accessible
201
+ echo -e "\n${BLUE}=== Testing CLI Tools ===${NC}"
202
+ if command -v plink2 &> /dev/null; then
203
+ echo -e "${GREEN}PLINK2 is accessible in the current PATH${NC}"
204
+ echo -e "PLINK2 location: $(which plink2)"
205
+ else
206
+ echo -e "${RED}PLINK2 is not accessible in the current PATH${NC}"
207
+ echo -e "Please run: ${YELLOW}source $(pwd)/setup_path.sh${NC} to update your PATH"
208
+ fi
209
+
210
+ if command -v gcta64 &> /dev/null; then
211
+ echo -e "${GREEN}GCTA is accessible in the current PATH${NC}"
212
+ echo -e "GCTA location: $(which gcta64)"
213
+ else
214
+ echo -e "${RED}GCTA is not accessible in the current PATH${NC}"
215
+ echo -e "Please run: ${YELLOW}source $(pwd)/setup_path.sh${NC} to update your PATH"
216
+ fi
217
+
218
+ if command -v iqtree2 &> /dev/null; then
219
+ echo -e "${GREEN}IQ-TREE is accessible in the current PATH${NC}"
220
+ echo -e "IQ-TREE location: $(which iqtree2)"
221
+ else
222
+ echo -e "${RED}IQ-TREE is not accessible in the current PATH${NC}"
223
+ echo -e "Please run: ${YELLOW}source $(pwd)/setup_path.sh${NC} to update your PATH"
224
+ fi
225
+ fi
226
+
227
+ PATH=$(echo $PATH | tr ':' '\n' | grep -v "biomni_tools/bin" | tr '\n' ':' | sed 's/:$//')
228
+ export PATH="$(pwd)/biomni_tools/bin:$PATH"
229
+ }
230
+
231
+ # Run the main installation process
232
+ main
docs/Makefile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?=
7
+ SPHINXBUILD ?= sphinx-build
8
+ SOURCEDIR = source
9
+ BUILDDIR = build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+
20
+ html:
21
+ @echo "Step 1: Generating API documentation..."
22
+ @python generate_api_docs.py
23
+
24
+ @echo "Step 2: Building HTML documentation with Sphinx..."
25
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
26
+
27
+ %: Makefile
28
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
docs/building_documentation.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Building Project Documentation
3
+
4
+ To build the project's documentation locally, use the `Makefile` located in the `docs` directory.
5
+
6
+ ## Prerequisites
7
+
8
+ First, ensure you have activated the `biomni_e1` conda environment. Then, install the required dependencies:
9
+
10
+ ```bash
11
+ pip install sphinx sphinx-rtd-theme
12
+ ```
13
+
14
+
15
+ ## Build the Documentation
16
+
17
+ 1. Navigate to the `docs` directory:
18
+
19
+ ```bash
20
+ cd docs
21
+ ```
22
+
23
+ 2. Run the `make html` command:
24
+
25
+ ```bash
26
+ make html
27
+ ```
28
+
29
+ This command will automatically generate the API documentation and build all HTML files.
30
+
31
+
32
+ ## View the Documentation
33
+
34
+ Once the build is complete, you can find the generated documentation in the `docs/build/html` directory.
35
+
36
+ Open the `index.html` file in your browser to view it.
docs/configuration.md ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni Configuration Guide
2
+
3
+ ## Quick Start
4
+
5
+ **Recommended approach**: Use environment variables or modify `default_config` for consistent behavior across your entire application.
6
+
7
+ ```python
8
+ from biomni.config import default_config
9
+ from biomni.agent import A1
10
+
11
+ # Option 1: Modify global defaults (affects everything)
12
+ default_config.llm = "gpt-4"
13
+ default_config.timeout_seconds = 1200
14
+
15
+ # Option 2: Use environment variables (set in .env file)
16
+ # BIOMNI_LLM=gpt-4
17
+ # BIOMNI_TIMEOUT_SECONDS=1200
18
+
19
+ agent = A1() # Uses your configuration
20
+ ```
21
+
22
+ ## Configuration Methods
23
+
24
+ ### 1. Environment Variables (Recommended for Production)
25
+
26
+ Create a `.env` file in your project:
27
+
28
+ ```bash
29
+ # Required API Keys (at least one)
30
+ ANTHROPIC_API_KEY=your_key
31
+ OPENAI_API_KEY=your_key
32
+
33
+ # Optional Settings
34
+ BIOMNI_LLM=claude-3-5-sonnet-20241022
35
+ BIOMNI_TIMEOUT_SECONDS=1200
36
+ BIOMNI_PATH=/path/to/data
37
+ ```
38
+
39
+ ### 2. Runtime Configuration (Recommended for Scripts)
40
+
41
+ ```python
42
+ from biomni.config import default_config
43
+
44
+ # Changes apply to all agents and database queries
45
+ default_config.llm = "gpt-4"
46
+ default_config.timeout_seconds = 1200
47
+ ```
48
+
49
+ ### 3. Direct Parameters (Use with Caution)
50
+
51
+ ```python
52
+ # ⚠️ Only affects this agent's reasoning, NOT database queries
53
+ agent = A1(llm="claude-3-5-sonnet-20241022")
54
+ ```
55
+
56
+ ## Common Examples
57
+
58
+ ### Using Different Models
59
+
60
+ ```python
61
+ # Use GPT-4 everywhere
62
+ default_config.llm = "gpt-4"
63
+ agent = A1()
64
+ ```
65
+
66
+ ### Cost Optimization (Different Models for Agent vs Database)
67
+
68
+ ```python
69
+ # Cheaper model for database queries
70
+ default_config.llm = "claude-3-5-haiku-20241022"
71
+
72
+ # More powerful model for agent reasoning
73
+ agent = A1(llm="claude-3-5-sonnet-20241022")
74
+ ```
75
+
76
+ ### Custom/Local Models
77
+
78
+ ```python
79
+ default_config.source = "Custom"
80
+ default_config.base_url = "http://localhost:8000/v1"
81
+ default_config.api_key = "local_key"
82
+ default_config.llm = "local-llama-70b"
83
+ ```
84
+
85
+ ## All Available Settings
86
+
87
+ ### Environment Variables
88
+
89
+ ```bash
90
+ # API Keys
91
+ ANTHROPIC_API_KEY=your_key
92
+ OPENAI_API_KEY=your_key
93
+ GEMINI_API_KEY=your_key
94
+ GROQ_API_KEY=your_key
95
+ AWS_BEARER_TOKEN_BEDROCK=your_key
96
+ AWS_REGION=us-east-1
97
+
98
+ # Azure OpenAI
99
+ OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
100
+
101
+ # Biomni Settings
102
+ BIOMNI_PATH=/path/to/data # Default: ./data
103
+ BIOMNI_TIMEOUT_SECONDS=1200 # Default: 600
104
+ BIOMNI_LLM=model_name # Default: claude-sonnet-4-20250514
105
+ BIOMNI_TEMPERATURE=0.7 # Default: 0.7
106
+ BIOMNI_USE_TOOL_RETRIEVER=true # Default: true
107
+ BIOMNI_SOURCE=Anthropic # Auto-detected if not set
108
+ BIOMNI_CUSTOM_BASE_URL=http://localhost:8000/v1
109
+ BIOMNI_CUSTOM_API_KEY=custom_key
110
+ ```
111
+
112
+ ### Python Configuration
113
+
114
+ ```python
115
+ from biomni.config import default_config
116
+
117
+ # All available settings
118
+ default_config.path = "./data"
119
+ default_config.timeout_seconds = 600
120
+ default_config.llm = "claude-sonnet-4-20250514"
121
+ default_config.temperature = 0.7
122
+ default_config.use_tool_retriever = True
123
+ default_config.source = None # Auto-detected
124
+ default_config.base_url = None # For custom models
125
+ default_config.api_key = None # For custom models
126
+ ```
127
+
128
+ ## Important Notes
129
+
130
+ - **For pip-installed packages**: You can't edit the package files, but you can still use environment variables or modify `default_config` at runtime
131
+ - **Configuration consistency**: Database queries always use `default_config`, regardless of agent parameters
132
+ - **Priority order**: Direct params > Runtime config > Env vars > Defaults
133
+
134
+ ## Troubleshooting
135
+
136
+ **API Key Not Found**:
137
+ - Check `.env` file exists in your working directory
138
+ - Verify with: `echo $ANTHROPIC_API_KEY`
139
+
140
+ **Configuration Not Applied**:
141
+ - Changes to `default_config` only affect agents created after the change
142
+ - Direct parameters only affect that specific agent, not database queries
143
+
144
+ **Model Not Found**:
145
+ - Check spelling of model name
146
+ - For Azure, prefix with "azure-" (e.g., "azure-gpt-4o")
147
+ - Ensure you have the right API key for that provider
docs/generate_api_docs.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # generate_api_docs.py
2
+
3
+ import os
4
+
5
+
6
+ def generate_api_docs(base_path, output_dir, package_name, exclude_list=None):
7
+ """
8
+ Automatically generates .rst files for Sphinx API documentation.
9
+
10
+ Args:
11
+ base_path (str): The root directory of the Python package (e.g., 'biomni').
12
+ output_dir (str): The directory where the .rst files will be generated (e.g., 'source/api').
13
+ package_name (str): The name of the Python package.
14
+ exclude_list (list): A list of paths (directories or files) to exclude from documentation.
15
+ Paths should be relative to the base_path.
16
+ """
17
+ if exclude_list is None:
18
+ exclude_list = []
19
+
20
+ if not os.path.exists(output_dir):
21
+ os.makedirs(output_dir)
22
+
23
+ # Walk through the package directory
24
+ for root, dirs, files in os.walk(base_path):
25
+ # Exclude directories if they are in the exclude list
26
+ dirs[:] = [d for d in dirs if os.path.join(os.path.relpath(root, base_path), d) not in exclude_list]
27
+
28
+ for file in files:
29
+ file_path_relative = os.path.relpath(os.path.join(root, file), base_path)
30
+
31
+ # Check if the file should be excluded
32
+ if file_path_relative in exclude_list:
33
+ print(f"Skipping excluded file: {file_path_relative}")
34
+ continue
35
+
36
+ if file.endswith(".py") and file != "__init__.py":
37
+ # Construct the full module name (e.g., biomni.agent.a1)
38
+ rel_path = os.path.relpath(root, base_path)
39
+ module_name = f"{package_name}.{rel_path.replace(os.path.sep, '.')}.{os.path.splitext(file)[0]}"
40
+
41
+ # Clean up the module name if it starts with the package name
42
+ if module_name.startswith(f"{package_name}.{package_name}."):
43
+ module_name = f"{package_name}.{module_name[len(f'{package_name}.{package_name}.') :]}"
44
+
45
+ # Create the directory structure in the output folder
46
+ output_path_dir = os.path.join(output_dir, rel_path)
47
+ if not os.path.exists(output_path_dir):
48
+ os.makedirs(output_path_dir)
49
+
50
+ # Define the output .rst file path
51
+ output_file = os.path.join(output_path_dir, f"{os.path.splitext(file)[0]}.rst")
52
+
53
+ # Generate the RST content
54
+ rst_content = f"""{module_name}
55
+ {"=" * len(module_name)}
56
+
57
+ .. automodule:: {module_name}
58
+ :members:
59
+ :undoc-members:
60
+ :show-inheritance:
61
+ """
62
+ # Write the content to the .rst file
63
+ with open(output_file, "w", encoding="utf-8") as f:
64
+ f.write(rst_content)
65
+ print(f"Generated {output_file}")
66
+
67
+
68
+ def create_index_rst(output_dir, package_name):
69
+ """
70
+ Creates or updates the main index file that lists all generated .rst files.
71
+ """
72
+ output_dir = os.path.join(output_dir, "../")
73
+ index_path = os.path.join(output_dir, "index.rst")
74
+ rst_files = []
75
+
76
+ # Find all generated rst files
77
+ for root, _dirs, files in os.walk(output_dir):
78
+ for file in files:
79
+ if file.endswith(".rst") and file != "index.rst":
80
+ rel_path = os.path.relpath(os.path.join(root, file), output_dir)
81
+ rst_files.append(os.path.splitext(rel_path)[0])
82
+
83
+ rst_files.sort()
84
+
85
+ index_content = f"""{package_name} API Reference
86
+ {"=" * (len(package_name) + len(" API Reference"))}
87
+
88
+ .. toctree::
89
+ :maxdepth: 2
90
+ :caption: API Contents:
91
+
92
+ """
93
+ for file in rst_files:
94
+ index_content += f" {file}\n"
95
+
96
+ with open(index_path, "w", encoding="utf-8") as f:
97
+ f.write(index_content)
98
+ print(f"Updated {index_path} with module links.")
99
+
100
+ # update ../index.rst to include api/index
101
+ # index_path = os.path.join(output_dir, '../index.rst')
102
+ # with open(index_path, 'w', encoding='utf-8') as f:
103
+
104
+
105
+ if __name__ == "__main__":
106
+ package_to_document = "../biomni"
107
+ api_docs_path = "./source/api"
108
+ package_name = "biomni"
109
+
110
+ os.makedirs(api_docs_path, exist_ok=True)
111
+
112
+ exclude_list = [
113
+ "llm.py",
114
+ "env_desc.py",
115
+ "version.py",
116
+ "tool/tool_description",
117
+ "tool/example_mcp_tools",
118
+ ]
119
+
120
+ generate_api_docs(package_to_document, api_docs_path, package_name, exclude_list)
121
+ create_index_rst(api_docs_path, package_name)
122
+
123
+ print("\nAPI documentation source files have been generated.")
124
+ print("Now run 'make html' from your project's root directory to build the documentation.")
docs/known_conflicts.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Known Package Conflicts in Biomni
2
+
3
+ This file lists Python packages that are known to have dependency conflicts with the default Biomni environment. These packages are not installed by default. If you require their functionality, you must install them manually and may need to uncomment relevant code in the codebase.
4
+
5
+ ## Packages
6
+
7
+ ### 1. hyperimpute
8
+ - Not installed by default due to dependency conflicts with the main environment.
9
+ - If you need imputation tools that require this package, install it manually in a separate environment or with caution.
10
+
11
+ ### 2. langchain_aws
12
+ - Needed for Amazon Bedrock support.
13
+ - Amazon Bedrock support is present in the codebase, but due to package dependency conflicts, you should install `langchain_aws` only when you need Bedrock support.
14
+ - You must also uncomment the relevant Bedrock support code sections in the codebase to enable this feature.
15
+
16
+ ### 3. cnvkit
17
+ - **Environment Requirement**: Requires Python 3.10 environment (`bio_env_py310.yml`)
18
+ - **Function**: Supports the `analyze_copy_number_purity_ploidy_and_focal_events` function
19
+ - **Why Separate Environment**: cnvkit has strict dependency requirements that conflict with newer Python versions and other packages in the main Biomni environment. Python 3.10 provides the optimal compatibility for cnvkit and its dependencies, ensuring reliable copy number variation analysis, purity estimation, ploidy detection, and focal event identification.
20
+ - **Installation**: Use `conda env create -f bio_env_py310.yml` to create the dedicated environment
21
+
22
+ ### 4. panhumanpy
23
+ - **Environment Requirement**: Requires its own dedicated environment due to specific dependency constraints
24
+ - **Function**: Supports the `annotate_celltype_with_panhumanpy` function for automated cell type annotation in scRNA-seq data
25
+ - **Why Separate Environment**: panhumanpy has strict version requirements including:
26
+ - TensorFlow 2.17 (specific version)
27
+ - scikit-learn 1.6.0 (specific version)
28
+ - Python >=3.9 but optimized for Python 3.12
29
+ - These version constraints conflict with the main Biomni environment and other packages
30
+ - **Installation**: Install in a separate conda environment with the exact versions specified in the package requirements
31
+
32
+ ---
33
+
34
+ If you encounter other package conflicts, please add them to this file or open an issue.
docs/make.bat ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO OFF
2
+
3
+ pushd %~dp0
4
+
5
+ REM Command file for Sphinx documentation
6
+
7
+ if "%SPHINXBUILD%" == "" (
8
+ set SPHINXBUILD=sphinx-build
9
+ )
10
+ set SOURCEDIR=source
11
+ set BUILDDIR=build
12
+
13
+ %SPHINXBUILD% >NUL 2>NUL
14
+ if errorlevel 9009 (
15
+ echo.
16
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17
+ echo.installed, then set the SPHINXBUILD environment variable to point
18
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
19
+ echo.may add the Sphinx directory to PATH.
20
+ echo.
21
+ echo.If you don't have Sphinx installed, grab it from
22
+ echo.https://www.sphinx-doc.org/
23
+ exit /b 1
24
+ )
25
+
26
+ if "%1" == "" goto help
27
+
28
+ %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29
+ goto end
30
+
31
+ :help
32
+ %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33
+
34
+ :end
35
+ popd
docs/mcp_integration.md ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MCP (Model Context Protocol) Integration in Biomni
2
+
3
+ Biomni provides comprehensive support for the Model Context Protocol (MCP), allowing you to both integrate external MCP servers and expose Biomni tools as MCP servers. This enables seamless interoperability with a wide ecosystem of AI tools and services.
4
+
5
+ ## Overview
6
+
7
+ MCP (Model Context Protocol) is a standard protocol for AI applications to communicate with external tools and services. Biomni supports two main MCP integration patterns:
8
+
9
+ 1. **Adding External MCP Servers**: Import tools from external MCP servers into Biomni
10
+ 2. **Exposing Biomni as MCP Server**: Make Biomni tools available to other MCP clients
11
+
12
+ ## Adding External MCP Servers to Biomni
13
+
14
+ ### Configuration File Format
15
+
16
+ MCP servers are configured using a YAML file that defines server connections and their tools. The `command` field specifies how to start the MCP server, which varies depending on how the server is packaged and distributed.
17
+
18
+ ```yaml
19
+ mcp_servers:
20
+ server_name:
21
+ enabled: true # Optional, defaults to true
22
+ command: ["docker", "run", "-i", "--rm", "-e", "ENV_VAR", "image:tag"] # Docker-based server
23
+ # OR
24
+ command: ["npx", "-y", "@modelcontextprotocol/server-name"] # NPM-based server
25
+ # OR
26
+ command: ["python", "-m", "my_mcp_server"] # Python-based server
27
+ # OR
28
+ command: ["./my-mcp-server", "--config", "config.json"] # Binary server
29
+ env:
30
+ API_KEY: "${OPENAI_API_KEY}" # Environment variable substitution
31
+ CUSTOM_VAR: "static_value"
32
+ ```
33
+
34
+ **Note**: The exact command format depends on the MCP server. Check the server's documentation for the correct command to use.
35
+
36
+ ### Example Configuration
37
+
38
+ ```yaml
39
+ mcp_servers:
40
+ # GitHub MCP Server (Docker approach - recommended)
41
+ github:
42
+ command: ["docker", "run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"]
43
+ enabled: true
44
+ description: "Official GitHub MCP server for repository and issue management"
45
+ env:
46
+ GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}"
47
+
48
+ # Custom Local MCP Server - Only add tools necessary
49
+ pubmed:
50
+ command: ["python", "-m", "biomni.tool.mcp_tools.pubmed_mcp"]
51
+ enabled: true
52
+ tools:
53
+ - biomni_name: search_pubmed
54
+ description: "Search PubMed"
55
+ parameters:
56
+ query: {type: str, required: true, description: "PubMed search term"}
57
+ max_results: {type: int, required: false, default: 10, description: "Maximum number of hits"}
58
+ - biomni_name: get_article_abstract
59
+ description: "Fetch PubMed abstract"
60
+ parameters:
61
+ pmid: {type: str, required: true, description: "PubMed ID"}
62
+
63
+ ```
64
+
65
+ You can also create custom MCP servers and define your own tools by implementing the MCP protocol and adding them to your configuration file.
66
+
67
+ ### Using the `add_mcp` Method
68
+
69
+ ```python
70
+ from biomni.agent import A1
71
+
72
+ # Initialize Biomni agent
73
+ agent = A1()
74
+
75
+ # Optional: Set email for PubMed
76
+ # import os
77
+ # os.environ.setdefault("NCBI_EMAIL", "Your email address")
78
+
79
+ # Add MCP servers from configuration file
80
+ agent.add_mcp(config_path="./mcp_config.yaml")
81
+
82
+ # Now you can use the MCP tools in your queries
83
+ result = agent.go("Please list all repositories in the github account of the user.")
84
+ ```
85
+
86
+ ### How It Works
87
+
88
+ 1. **Tool Discovery**: Biomni automatically discovers available tools from each MCP server
89
+ 2. **Async-to-Sync Wrapping**: MCP tools are wrapped to work with Biomni's synchronous execution model
90
+ 3. **Integration**: Tools are registered in Biomni's tool registry and made available for retrieval
91
+ 4. **Module Organization**: Each MCP server gets its own module namespace (e.g., `mcp_servers.github`)
92
+
93
+ ### Tool Registration Process
94
+
95
+ When you call `add_mcp()`, Biomni:
96
+
97
+ 1. Loads the configuration file
98
+ 2. For each enabled server:
99
+ - Establishes a connection to the MCP server
100
+ - Discovers available tools
101
+ - Creates synchronous wrapper functions
102
+ - Registers tools in the tool registry
103
+ - Adds tools to the module2api mapping
104
+ - Stores tools in custom functions registry
105
+
106
+ ### Environment Variable Substitution
107
+
108
+ The configuration supports environment variable substitution using `${VARIABLE_NAME}` syntax:
109
+
110
+ ```yaml
111
+ mcp_servers:
112
+ my_server:
113
+ command: ["my-server"]
114
+ env:
115
+ API_KEY: "${MY_API_KEY}" # Will be replaced with os.getenv("MY_API_KEY")
116
+ DEBUG: "true" # Static value
117
+ ```
118
+
119
+ You will need to properly set your environment variables (e.g., `GITHUB_TOKEN`) using a `.env` file or shell exports before running the agent.
120
+
121
+ ## Exposing Biomni Tools as MCP Server
122
+
123
+ ### Using the `create_mcp_server` Method
124
+
125
+ Biomni can expose its internal tools as an MCP server, making them available to other MCP clients:
126
+
127
+ ```python
128
+ from biomni.agent.a1 import A1
129
+
130
+ # Create the agent
131
+ agent = A1()
132
+
133
+ # Create the MCP server with specific modules
134
+ mcp = agent.create_mcp_server(tool_modules=["biomni.tool.database"])
135
+
136
+ if __name__ == "__main__":
137
+ # Run the server using stdio transport
138
+ print("Starting Biomni MCP server...")
139
+ mcp.run(transport="stdio")
140
+ ```
141
+
142
+ ### Server Configuration
143
+
144
+ The MCP server can be configured with various options:
145
+
146
+ ```python
147
+ # Create server with specific modules
148
+ mcp = agent.create_mcp_server(tool_modules=[
149
+ "biomni.tool.genetics",
150
+ "biomni.tool.database",
151
+ "biomni.tool.cell_biology"
152
+ ])
153
+
154
+ # The server will expose all tools from these modules
155
+ # Tools are automatically wrapped with proper parameter validation
156
+ ```
157
+
158
+ ## Best Practices
159
+
160
+ ### Configuration Management
161
+
162
+ 1. **Environment Variables**: Use environment variables for sensitive data like API keys
163
+ 2. **Docker Images**: Use official Docker images for MCP servers when available
164
+ 3. **Server Validation**: Test server connections before adding them to production
165
+ 4. **Tool Naming**: Ensure unique tool names across different servers to avoid conflicts
166
+
167
+ ### Performance Considerations
168
+
169
+ 1. **Connection Management**: MCP servers are created on-demand for each tool call
170
+ 2. **Tool Discovery**: Tool discovery happens once during `add_mcp()` call
171
+ 3. **Error Handling**: Failed tool calls are properly handled and reported
172
+ 4. **Docker Overhead**: Containerized servers may have additional startup time
173
+
174
+ ### Security
175
+
176
+ 1. **Environment Variables**: Never hardcode sensitive information in configuration files
177
+ 2. **Docker Security**: Use trusted Docker images and consider security implications
178
+ 3. **Server Permissions**: Ensure MCP servers have appropriate permissions for their operations
179
+ 4. **Network Access**: Be aware of network access requirements for external MCP servers
180
+
181
+ ## Troubleshooting
182
+
183
+ ### Common Issues
184
+
185
+ 1. **Docker Not Running**: Ensure Docker is running for containerized MCP servers
186
+ 2. **Environment Variables**: Verify that required environment variables are set (e.g., `GITHUB_TOKEN`, `NCBI_EMAIL`)
187
+ 3. **Tool Discovery Failures**: Check server logs for connection or authentication issues
188
+ 4. **Naming Conflicts**: Tools with duplicate names will be skipped with a warning
189
+
190
+ ### Debugging
191
+
192
+ ```python
193
+ # Test MCP connections without adding tools
194
+ test_results = agent.test_mcp_connection("./mcp_config.yaml")
195
+ print(test_results)
196
+
197
+ # List all MCP servers and their tools
198
+ servers = agent.list_mcp_servers()
199
+ print(servers)
200
+
201
+ # Remove specific MCP tools
202
+ agent.remove_mcp_tools("server_name")
203
+ ```
204
+
205
+ ### Testing MCP Server
206
+
207
+ Use the provided test script to verify your MCP server is working:
208
+
209
+ ```bash
210
+ cd tutorials/examples/expose_biomni_server
211
+ python test_mcp_server.py
212
+ ```
213
+
214
+ The test script allows you to:
215
+ - Test individual tools with custom parameters
216
+ - View available tools and their schemas
217
+ - Debug connection issues
218
+ - Validate tool responses
219
+
220
+ ## Example Use Cases
221
+
222
+ ### Adding External MCP Servers
223
+
224
+ ```python
225
+ from biomni.agent import A1
226
+
227
+ # Initialize agent and add MCP servers
228
+ agent = A1()
229
+ agent.add_mcp(config_path="./mcp_config.yaml")
230
+
231
+ # Use GitHub tools alongside Biomni tools
232
+ result = agent.go("""
233
+ Please list all repositories in the github account of the user.
234
+ Then use Biomni's genetics tools to analyze any bioinformatics repositories found.
235
+ """)
236
+ ```
237
+
238
+ ### Exposing Biomni as MCP Server
239
+
240
+ ```python
241
+ from biomni.agent.a1 import A1
242
+
243
+ # Create agent and MCP server
244
+ agent = A1()
245
+ mcp = agent.create_mcp_server(tool_modules=["biomni.tool.database"])
246
+
247
+ # Run the server
248
+ if __name__ == "__main__":
249
+ print("Starting Biomni MCP server...")
250
+ mcp.run(transport="stdio")
251
+ ```
252
+
253
+ ## Integration with Other Systems
254
+
255
+ ### Available Examples
256
+
257
+ The Biomni repository includes complete examples in the `tutorials/examples/` directory:
258
+
259
+ #### Adding MCP Servers
260
+ - **Location**: `tutorials/examples/add_mcp_server/`
261
+ - **Files**:
262
+ - `mcp_config.yaml` - Example configuration file
263
+ - `mcp_example.ipynb` - Jupyter notebook demonstrating usage
264
+
265
+ #### Exposing Biomni as MCP Server
266
+ - **Location**: `tutorials/examples/expose_biomni_server/`
267
+ - **Files**:
268
+ - `run_mcp_server.py` - Script to run Biomni MCP server
269
+ - `test_mcp_server.py` - Comprehensive test script for MCP tools
270
+
271
+ To run the examples:
272
+
273
+ ```bash
274
+ # Add MCP servers
275
+ cd tutorials/examples/add_mcp_server
276
+ jupyter notebook mcp_example.ipynb
277
+
278
+ # Expose Biomni as MCP server
279
+ cd tutorials/examples/expose_biomni_server
280
+ python run_mcp_server.py
281
+
282
+ # Test the server
283
+ python test_mcp_server.py
284
+ ```
285
+
286
+ ### Testing MCP Server
287
+
288
+ You can test the Biomni MCP server using the provided test script:
289
+
290
+ ```python
291
+ #!/usr/bin/env python3
292
+ """
293
+ Simple test script for testing a single Biomni MCP tool.
294
+ """
295
+
296
+ import asyncio
297
+ import json
298
+ import sys
299
+
300
+ from mcp import ClientSession
301
+ from mcp.client.stdio import StdioServerParameters, stdio_client
302
+
303
+ # Configuration - Change these to test different tools
304
+ TOOL_TO_TEST = "query_uniprot" # Change this to the tool you want to test
305
+ TEST_ARGS = {"prompt": "Find information about human insulin protein"}
306
+
307
+ async def test_single_tool():
308
+ """Test a single tool in the Biomni MCP server."""
309
+
310
+ # Set up the server parameters
311
+ import os
312
+ current_dir = os.path.dirname(os.path.abspath(__file__))
313
+ server_script = os.path.join(current_dir, "run_mcp_server.py")
314
+ server_params = StdioServerParameters(command="python", args=[server_script])
315
+
316
+ try:
317
+ print("🔌 Connecting to MCP server...")
318
+
319
+ # Connect to the server
320
+ async with stdio_client(server_params) as (reader, writer):
321
+ async with ClientSession(reader, writer) as session:
322
+ # Initialize the session
323
+ await session.initialize()
324
+ print("✅ Connected to MCP server")
325
+
326
+ # List available tools
327
+ response = await session.list_tools()
328
+ tools = response.tools
329
+ print(f"✅ Found {len(tools)} tools")
330
+
331
+ # Test the tool
332
+ result = await session.call_tool(TOOL_TO_TEST, TEST_ARGS)
333
+ print("✅ Tool call successful!")
334
+ print(f"📄 Result: {result.content[0].text}")
335
+
336
+ except Exception as e:
337
+ print(f"❌ Failed to connect to MCP server: {e}")
338
+ return False
339
+
340
+ if __name__ == "__main__":
341
+ success = asyncio.run(test_single_tool())
342
+ sys.exit(0 if success else 1)
343
+ ```
344
+
345
+ ## Conclusion
346
+
347
+ Biomni's MCP integration provides a powerful way to extend its capabilities with external tools and services, while also making Biomni tools available to the broader MCP ecosystem. This enables seamless interoperability and allows you to build sophisticated AI workflows that combine the best of multiple tool ecosystems.
experiments/bioagent_bench/README.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni MCP Tool Scaling Experiment
2
+
3
+ This experiment studies how Biomni behaves when the MCP tool background grows from
4
+ about 100 tools to about 500, 1000, 1500, and 2000 tools on bioagent-bench.
5
+
6
+ ## Generated Configs
7
+
8
+ The configs are generated from `/225040511/project/Biomni/mcp_generated` and can be
9
+ registered directly with Biomni:
10
+
11
+ ```python
12
+ from biomni.agent import A1
13
+
14
+ agent = A1(use_tool_retriever=True)
15
+ agent.add_mcp(
16
+ "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_100.yaml"
17
+ )
18
+ ```
19
+
20
+ Generated files:
21
+
22
+ - `configs/mcp_scale_100.yaml`: 10 MCP servers, 107 tools
23
+ - `configs/mcp_scale_500.yaml`: 96 MCP servers, 504 tools
24
+ - `configs/mcp_scale_1000.yaml`: 276 MCP servers, 1003 tools
25
+ - `configs/mcp_scale_1500.yaml`: 378 MCP servers, 1527 tools
26
+ - `configs/mcp_scale_2000.yaml`: 526 MCP servers, 2000 tools
27
+ - `configs/mcp_scale_manifest.json`: exact server/tool counts and selected server order
28
+ - `configs/mcp_server_inventory.json`: parseable MCP inventory
29
+
30
+ The generator writes explicit `tools` metadata into YAML, so `A1.add_mcp()` does
31
+ not have to start every server just to discover schemas. The runtime command for
32
+ each tool still points to the corresponding generated MCP server file.
33
+
34
+ ## Regenerate
35
+
36
+ ```bash
37
+ cd /225040511/project/Biomni
38
+ python experiments/bioagent_bench/scripts/generate_mcp_scale_configs.py
39
+ ```
40
+
41
+ The generator prioritizes benchmark-relevant tools first, then fills each tier by
42
+ whole MCP server units until the target tool count is reached.
43
+
44
+ ## Benchmark Design
45
+
46
+ Use the tasks in `/225040511/project/bioagent-bench/src/task_metadata.json`.
47
+ For each task, run Biomni once per tool tier:
48
+
49
+ 1. Create a fresh run directory named like:
50
+ `runs/<tier>/<task_id>_<timestamp>/`
51
+ 2. Initialize Biomni with `use_tool_retriever=True`.
52
+ 3. Register exactly one tier config with `agent.add_mcp(config_path=...)`.
53
+ 4. Run the bioagent-bench task prompt with the same data policy used in your
54
+ existing runs.
55
+ 5. Save `retrieval_plan.json`, `execution_log.json`, `execution_log.txt`,
56
+ `run_metadata.json`, `run_summary.json`, `output_validation.json`, and final
57
+ deliverables in the run directory.
58
+
59
+ Use identical model, decoding settings, timeout, conda environment, and benchmark
60
+ data policy across tiers. Repeat each task at least 3 times if you want confidence
61
+ intervals, because LLM tool retrieval and planning can vary.
62
+
63
+ ## Metrics
64
+
65
+ Metric definitions used here:
66
+
67
+ - `Retrieval Recall@k`: fraction of task gold tools/servers present in the first
68
+ `k` retrieved candidates from `retrieval_plan.json`. The table export uses a
69
+ primary `k` value, defaulting to `10`.
70
+ - `Workflow Validity`: whether observed tool/log evidence covers the task's gold
71
+ workflow stages in order.
72
+ - `Execution Success Rate`: whether benchmark output validation or required
73
+ output paths indicate the pipeline completed.
74
+ - `Context Tokens`: rough planning-context token count from
75
+ `retrieval_plan.json/planning_context_text`.
76
+ - `Planning Latency`: retrieval/planning latency recorded by the agent during
77
+ tool selection.
78
+ - `Hallucinated Tool Rate`: fraction of executed MCP tools mentioned in the
79
+ execution trace that were not present in the retrieved candidate tool set
80
+ persisted in `retrieval_plan.json`.
81
+ - `Data-Type Mismatch Rate`: heuristic rate of execution attempts that emitted
82
+ argument/schema/type mismatch errors, estimated from execution-log patterns.
83
+ - `Biological Constraint Error Rate`: heuristic rate of execution attempts that
84
+ violated benchmark biological/data constraints, such as sibling-task data
85
+ access, truth/results access, or external installs/downloads.
86
+
87
+ Gold tool and workflow definitions are in `gold_tools.json`. They are deliberately
88
+ pattern based, because MCP wrapper names differ across generated servers.
89
+
90
+ ## Aggregate Existing Runs
91
+
92
+ ```bash
93
+ python experiments/bioagent_bench/scripts/evaluate_mcp_scaling_runs.py \
94
+ --runs-root /path/to/runs/scale_100 \
95
+ --out experiments/bioagent_bench/results/scale_100_metrics.json
96
+ ```
97
+
98
+ Run this once per tier, then compare the aggregate blocks or the exported table:
99
+
100
+ - `results/scale_<N>_mcp_metrics.json`
101
+ - `results/experiment1_scaling_table.json`
102
+ - `results/experiment1_scaling_table.csv`
experiments/bioagent_bench/README_RUN.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # BioAgent Bench MCP Scaling 新手运行指南
2
+
3
+ 这份说明用于继续运行 Biomni 在 BioAgent Bench 上的 MCP tool scale 实验。当前实验目录是:
4
+
5
+ ```bash
6
+ /225040511/project/Biomni/experiments/bioagent_bench
7
+ ```
8
+
9
+ ## 1. 进入项目目录
10
+
11
+ ```bash
12
+ cd /225040511/project/Biomni
13
+ ```
14
+
15
+ ## 2. 准备 LLM API Key
16
+
17
+ 实验需要至少设置下面任意一种 key:
18
+
19
+ ```bash
20
+ export ANTHROPIC_API_KEY="你的 Anthropic key"
21
+ ```
22
+
23
+ 或者:
24
+
25
+ ```bash
26
+ export OPENAI_API_KEY="你的 OpenAI key"
27
+ ```
28
+
29
+ 或者使用 DeepSeek:
30
+
31
+ ```bash
32
+ export DEEPSEEK_API_KEY="你的 DeepSeek key"
33
+ export DEEPSEEK_MODEL_NAME="deepseek-chat"
34
+ export DEEPSEEK_BASE_URL="https://api.deepseek.com/v1"
35
+ ```
36
+
37
+ 脚本会自动把 `DEEPSEEK_API_KEY` 转成 Biomni 使用的 `BIOMNI_CUSTOM_API_KEY`。
38
+
39
+ 可以用下面命令确认 key 是否已经设置,不会打印 key 内容:
40
+
41
+ ```bash
42
+ for k in ANTHROPIC_API_KEY OPENAI_API_KEY BIOMNI_CUSTOM_API_KEY DEEPSEEK_API_KEY; do
43
+ if [ -n "${!k:-}" ]; then echo "$k=set"; else echo "$k=unset"; fi
44
+ done
45
+ ```
46
+
47
+ ## 3. 检查 MCP 配置
48
+
49
+ 新增 scale 的配置已经生成在:
50
+
51
+ ```bash
52
+ ls -lh experiments/bioagent_bench/configs/mcp_scale_1500.yaml
53
+ ls -lh experiments/bioagent_bench/configs/mcp_scale_2000.yaml
54
+ ```
55
+
56
+ 当前 manifest 在:
57
+
58
+ ```bash
59
+ experiments/bioagent_bench/configs/mcp_scale_manifest.json
60
+ ```
61
+
62
+ 其中:
63
+
64
+ - `scale_1500`: 378 个 MCP servers,1527 个 tools
65
+ - `scale_2000`: 526 个 MCP servers,2000 个 tools
66
+
67
+ 如果以后需要重新生成配置:
68
+
69
+ ```bash
70
+ /225040511/miniconda3/envs/biomni_e1/bin/python \
71
+ experiments/bioagent_bench/scripts/generate_mcp_scale_configs.py
72
+ ```
73
+
74
+ ## 4. 后台运行 1500 和 2000 scale
75
+
76
+ 推荐后台跑,因为完整实验可能需要很久:
77
+
78
+ ```bash
79
+ experiments/bioagent_bench/scripts/run_biomni_scaling_experiment.sh \
80
+ --background \
81
+ --scale 1500 \
82
+ --scale 2000
83
+ ```
84
+
85
+ 脚本启动后会打印:
86
+
87
+ - `PID`: 后台进程号
88
+ - `Log`: 日志文件路径
89
+ - `Monitor with`: 查看日志的命令
90
+
91
+ ## 5. 查看运行日志
92
+
93
+ 把下面的路径替换成脚本启动时打印的 log 路径:
94
+
95
+ ```bash
96
+ tail -f experiments/bioagent_bench/results/logs/run_biomni_scaling_experiment_YYYYMMDD_HHMMSS.log
97
+ ```
98
+
99
+ 如果日志里出现:
100
+
101
+ ```text
102
+ No LLM API key found.
103
+ ```
104
+
105
+ 说明第 2 步的 API key 没有在当前 shell 里生效,需要重新 `export` 后再启动。
106
+
107
+ ## 6. 结果输出位置
108
+
109
+ 每个 scale 的原始运行结果会写到:
110
+
111
+ ```bash
112
+ experiments/bioagent_bench/runs/scale_1500
113
+ experiments/bioagent_bench/runs/scale_2000
114
+ ```
115
+
116
+ 每个 task 会生成自己的子目录,例如:
117
+
118
+ ```bash
119
+ experiments/bioagent_bench/runs/scale_1500/alzheimer-mouse_YYYYMMDD_HHMMSS
120
+ ```
121
+
122
+ 常见文件包括:
123
+
124
+ - `run_metadata.json`
125
+ - `retrieval_plan.json`
126
+ - `execution_log.json`
127
+ - `execution_log.txt`
128
+ - `output_validation.json`
129
+ - `final_answer.txt`
130
+ - task 对应的最终输出文件
131
+
132
+ 汇总指标会写到:
133
+
134
+ ```bash
135
+ experiments/bioagent_bench/results/scale_1500_mcp_metrics.json
136
+ experiments/bioagent_bench/results/scale_1500_task_metrics.json
137
+ experiments/bioagent_bench/results/scale_1500_task_metrics.csv
138
+ experiments/bioagent_bench/results/scale_1500_bioagent_bench_rule_eval.json
139
+
140
+ experiments/bioagent_bench/results/scale_2000_mcp_metrics.json
141
+ experiments/bioagent_bench/results/scale_2000_task_metrics.json
142
+ experiments/bioagent_bench/results/scale_2000_task_metrics.csv
143
+ experiments/bioagent_bench/results/scale_2000_bioagent_bench_rule_eval.json
144
+ ```
145
+
146
+ 总表会更新到:
147
+
148
+ ```bash
149
+ experiments/bioagent_bench/results/experiment1_scaling_table.json
150
+ experiments/bioagent_bench/results/experiment1_scaling_table.csv
151
+ ```
152
+
153
+ ## 7. 断点续跑
154
+
155
+ runner 默认带 `--skip-completed`,所以中途失败后可以直接重新运行同一条命令。已经完成的 task 会被跳过,未完成的 task 会继续跑。
156
+
157
+ ```bash
158
+ experiments/bioagent_bench/scripts/run_biomni_scaling_experiment.sh \
159
+ --background \
160
+ --scale 1500 \
161
+ --scale 2000
162
+ ```
163
+
164
+ ## 8. 只跑单个 scale
165
+
166
+ 例如只跑 1500:
167
+
168
+ ```bash
169
+ experiments/bioagent_bench/scripts/run_biomni_scaling_experiment.sh \
170
+ --background \
171
+ --scale 1500
172
+ ```
173
+
174
+ 只跑 2000:
175
+
176
+ ```bash
177
+ experiments/bioagent_bench/scripts/run_biomni_scaling_experiment.sh \
178
+ --background \
179
+ --scale 2000
180
+ ```
181
+
182
+ ## 9. 前台调试
183
+
184
+ 如果想直接在终端里看报错,可以用前台模式:
185
+
186
+ ```bash
187
+ experiments/bioagent_bench/scripts/run_biomni_scaling_experiment.sh \
188
+ --foreground \
189
+ --scale 1500
190
+ ```
191
+
192
+ 前台模式会占住当前终端,适合调试,不适合长时间完整实验。
193
+
194
+ ## 10. 快速检查是否完成
195
+
196
+ 查看每个 scale 是否有 batch summary:
197
+
198
+ ```bash
199
+ ls -lh experiments/bioagent_bench/runs/scale_1500/latest_batch_summary.json
200
+ ls -lh experiments/bioagent_bench/runs/scale_2000/latest_batch_summary.json
201
+ ```
202
+
203
+ 查看汇总表是否更新:
204
+
205
+ ```bash
206
+ ls -lh experiments/bioagent_bench/results/experiment1_scaling_table.csv
207
+ ```
208
+
experiments/bioagent_bench/configs/mcp_scale_100.yaml ADDED
The diff for this file is too large to render. See raw diff
 
experiments/bioagent_bench/configs/mcp_scale_manifest.json ADDED
@@ -0,0 +1,1329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mcp_root": "/225040511/project/Biomni/mcp_generated",
3
+ "execution_python": "/225040511/miniconda3/envs/biomni_e1/bin/python",
4
+ "available_server_count": 793,
5
+ "available_tool_count": 3189,
6
+ "targets": {
7
+ "100": {
8
+ "config": "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_100.yaml",
9
+ "server_count": 10,
10
+ "tool_count": 107,
11
+ "servers": [
12
+ "mcp_kallisto",
13
+ "mcp_kraken2",
14
+ "mcp_csvtk",
15
+ "mcp_megahit",
16
+ "mcp_kaiju",
17
+ "mcp_fastp",
18
+ "mcp_spades",
19
+ "mcp_scanpy",
20
+ "mcp_scanpy-cli",
21
+ "mcp_scanpy-scripts"
22
+ ]
23
+ },
24
+ "500": {
25
+ "config": "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_500.yaml",
26
+ "server_count": 96,
27
+ "tool_count": 504,
28
+ "servers": [
29
+ "mcp_kallisto",
30
+ "mcp_kraken2",
31
+ "mcp_csvtk",
32
+ "mcp_megahit",
33
+ "mcp_kaiju",
34
+ "mcp_fastp",
35
+ "mcp_spades",
36
+ "mcp_scanpy",
37
+ "mcp_scanpy-cli",
38
+ "mcp_scanpy-scripts",
39
+ "mcp_seurat-scripts",
40
+ "mcp_bioconductor-deseq2",
41
+ "mcp_bioconductor-edger",
42
+ "mcp_bioconductor-limma",
43
+ "mcp_bioconductor-clusterprofiler",
44
+ "mcp_star",
45
+ "mcp_hisat2",
46
+ "mcp_subread",
47
+ "mcp_bcftools",
48
+ "mcp_bwa",
49
+ "mcp_gatk4",
50
+ "mcp_gatk",
51
+ "mcp_snpeff",
52
+ "mcp_freebayes",
53
+ "mcp_orthofinder",
54
+ "mcp_blast",
55
+ "mcp_blast-legacy",
56
+ "mcp_diamond",
57
+ "mcp_gffread",
58
+ "mcp_seqkit",
59
+ "mcp_multiqc",
60
+ "mcp_fastqc",
61
+ "mcp_trimmomatic",
62
+ "mcp_bioconductor-keggrest",
63
+ "mcp_snpsift",
64
+ "mcp_bioconductor-rsubread",
65
+ "mcp_bioconductor-tximport",
66
+ "mcp_bioconductor-variantannotation",
67
+ "mcp_celltypist",
68
+ "mcp_abnumber",
69
+ "mcp_abricate",
70
+ "mcp_abundancebin",
71
+ "mcp_abyss",
72
+ "mcp_adapterremoval",
73
+ "mcp_alfred",
74
+ "mcp_anansescanpy",
75
+ "mcp_anarci",
76
+ "mcp_aragorn",
77
+ "mcp_aria2",
78
+ "mcp_ariba",
79
+ "mcp_arriba",
80
+ "mcp_art",
81
+ "mcp_arvados-cwl-runner",
82
+ "mcp_arvados-python-client",
83
+ "mcp_atropos",
84
+ "mcp_augustus",
85
+ "mcp_auspice",
86
+ "mcp_bactopia",
87
+ "mcp_bamtools",
88
+ "mcp_barrnap",
89
+ "mcp_bbmap",
90
+ "mcp_bcbio-gff",
91
+ "mcp_bcbio-nextgen",
92
+ "mcp_bedops",
93
+ "mcp_bedtools",
94
+ "mcp_bin2cell",
95
+ "mcp_bioawk",
96
+ "mcp_biobambam",
97
+ "mcp_bioconductor-affy",
98
+ "mcp_bioconductor-affyio",
99
+ "mcp_bioconductor-alabaster",
100
+ "mcp_bioconductor-alabaster.sfe",
101
+ "mcp_bioconductor-alabaster.spatial",
102
+ "mcp_bioconductor-annotate",
103
+ "mcp_bioconductor-annotationdbi",
104
+ "mcp_bioconductor-annotationfilter",
105
+ "mcp_bioconductor-apeglm",
106
+ "mcp_bioconductor-apl",
107
+ "mcp_bioconductor-awaggregator",
108
+ "mcp_bioconductor-banksy",
109
+ "mcp_bioconductor-beachmat",
110
+ "mcp_bioconductor-benchdamic",
111
+ "mcp_bioconductor-biobase",
112
+ "mcp_bioconductor-biocbaseutils",
113
+ "mcp_bioconductor-biocfilecache",
114
+ "mcp_bioconductor-biocgenerics",
115
+ "mcp_bioconductor-biocio",
116
+ "mcp_bioconductor-biocneighbors",
117
+ "mcp_bioconductor-biocparallel",
118
+ "mcp_bioconductor-biocsingular",
119
+ "mcp_bioconductor-biomart",
120
+ "mcp_bioconductor-biomformat",
121
+ "mcp_bioconductor-biostrings",
122
+ "mcp_bioconductor-biovizbase",
123
+ "mcp_bioconductor-blase",
124
+ "mcp_bioconductor-bluster"
125
+ ]
126
+ },
127
+ "1000": {
128
+ "config": "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_1000.yaml",
129
+ "server_count": 276,
130
+ "tool_count": 1003,
131
+ "servers": [
132
+ "mcp_kallisto",
133
+ "mcp_kraken2",
134
+ "mcp_csvtk",
135
+ "mcp_megahit",
136
+ "mcp_kaiju",
137
+ "mcp_fastp",
138
+ "mcp_spades",
139
+ "mcp_scanpy",
140
+ "mcp_scanpy-cli",
141
+ "mcp_scanpy-scripts",
142
+ "mcp_seurat-scripts",
143
+ "mcp_bioconductor-deseq2",
144
+ "mcp_bioconductor-edger",
145
+ "mcp_bioconductor-limma",
146
+ "mcp_bioconductor-clusterprofiler",
147
+ "mcp_star",
148
+ "mcp_hisat2",
149
+ "mcp_subread",
150
+ "mcp_bcftools",
151
+ "mcp_bwa",
152
+ "mcp_gatk4",
153
+ "mcp_gatk",
154
+ "mcp_snpeff",
155
+ "mcp_freebayes",
156
+ "mcp_orthofinder",
157
+ "mcp_blast",
158
+ "mcp_blast-legacy",
159
+ "mcp_diamond",
160
+ "mcp_gffread",
161
+ "mcp_seqkit",
162
+ "mcp_multiqc",
163
+ "mcp_fastqc",
164
+ "mcp_trimmomatic",
165
+ "mcp_bioconductor-keggrest",
166
+ "mcp_snpsift",
167
+ "mcp_bioconductor-rsubread",
168
+ "mcp_bioconductor-tximport",
169
+ "mcp_bioconductor-variantannotation",
170
+ "mcp_celltypist",
171
+ "mcp_abnumber",
172
+ "mcp_abricate",
173
+ "mcp_abundancebin",
174
+ "mcp_abyss",
175
+ "mcp_adapterremoval",
176
+ "mcp_alfred",
177
+ "mcp_anansescanpy",
178
+ "mcp_anarci",
179
+ "mcp_aragorn",
180
+ "mcp_aria2",
181
+ "mcp_ariba",
182
+ "mcp_arriba",
183
+ "mcp_art",
184
+ "mcp_arvados-cwl-runner",
185
+ "mcp_arvados-python-client",
186
+ "mcp_atropos",
187
+ "mcp_augustus",
188
+ "mcp_auspice",
189
+ "mcp_bactopia",
190
+ "mcp_bamtools",
191
+ "mcp_barrnap",
192
+ "mcp_bbmap",
193
+ "mcp_bcbio-gff",
194
+ "mcp_bcbio-nextgen",
195
+ "mcp_bedops",
196
+ "mcp_bedtools",
197
+ "mcp_bin2cell",
198
+ "mcp_bioawk",
199
+ "mcp_biobambam",
200
+ "mcp_bioconductor-affy",
201
+ "mcp_bioconductor-affyio",
202
+ "mcp_bioconductor-alabaster",
203
+ "mcp_bioconductor-alabaster.sfe",
204
+ "mcp_bioconductor-alabaster.spatial",
205
+ "mcp_bioconductor-annotate",
206
+ "mcp_bioconductor-annotationdbi",
207
+ "mcp_bioconductor-annotationfilter",
208
+ "mcp_bioconductor-apeglm",
209
+ "mcp_bioconductor-apl",
210
+ "mcp_bioconductor-awaggregator",
211
+ "mcp_bioconductor-banksy",
212
+ "mcp_bioconductor-beachmat",
213
+ "mcp_bioconductor-benchdamic",
214
+ "mcp_bioconductor-biobase",
215
+ "mcp_bioconductor-biocbaseutils",
216
+ "mcp_bioconductor-biocfilecache",
217
+ "mcp_bioconductor-biocgenerics",
218
+ "mcp_bioconductor-biocio",
219
+ "mcp_bioconductor-biocneighbors",
220
+ "mcp_bioconductor-biocparallel",
221
+ "mcp_bioconductor-biocsingular",
222
+ "mcp_bioconductor-biomart",
223
+ "mcp_bioconductor-biomformat",
224
+ "mcp_bioconductor-biostrings",
225
+ "mcp_bioconductor-biovizbase",
226
+ "mcp_bioconductor-blase",
227
+ "mcp_bioconductor-bluster",
228
+ "mcp_bioconductor-bulksignalr",
229
+ "mcp_bioconductor-cardspa",
230
+ "mcp_bioconductor-catscradle",
231
+ "mcp_bioconductor-cdi",
232
+ "mcp_bioconductor-cellhashr",
233
+ "mcp_bioconductor-cellid",
234
+ "mcp_bioconductor-cellmigration",
235
+ "mcp_bioconductor-clusterfoldsimilarity",
236
+ "mcp_bioconductor-clustifyr",
237
+ "mcp_bioconductor-clustsignal",
238
+ "mcp_bioconductor-complexheatmap",
239
+ "mcp_bioconductor-concordexr",
240
+ "mcp_bioconductor-cotan",
241
+ "mcp_bioconductor-csoa",
242
+ "mcp_bioconductor-ctsv",
243
+ "mcp_bioconductor-curatedatlasqueryr",
244
+ "mcp_bioconductor-cytomapper",
245
+ "mcp_bioconductor-dada2",
246
+ "mcp_bioconductor-data-packages",
247
+ "mcp_bioconductor-decipher",
248
+ "mcp_bioconductor-decontam",
249
+ "mcp_bioconductor-decontx",
250
+ "mcp_bioconductor-deconvobuddies",
251
+ "mcp_bioconductor-delayedmatrixstats",
252
+ "mcp_bioconductor-despace",
253
+ "mcp_bioconductor-diffbind",
254
+ "mcp_bioconductor-dino",
255
+ "mcp_bioconductor-dirichletmultinomial",
256
+ "mcp_bioconductor-dnacopy",
257
+ "mcp_bioconductor-dose",
258
+ "mcp_bioconductor-ensembldb",
259
+ "mcp_bioconductor-erma",
260
+ "mcp_bioconductor-escher",
261
+ "mcp_bioconductor-experimentsubset",
262
+ "mcp_bioconductor-fgsea",
263
+ "mcp_bioconductor-genefilter",
264
+ "mcp_bioconductor-geneplotter",
265
+ "mcp_bioconductor-genomeinfodb",
266
+ "mcp_bioconductor-genomeinfodbdata",
267
+ "mcp_bioconductor-genomicalignments",
268
+ "mcp_bioconductor-genomicfeatures",
269
+ "mcp_bioconductor-genomicranges",
270
+ "mcp_bioconductor-geomxtools",
271
+ "mcp_bioconductor-ggsc",
272
+ "mcp_bioconductor-ggspavis",
273
+ "mcp_bioconductor-ggtree",
274
+ "mcp_bioconductor-glmgampoi",
275
+ "mcp_bioconductor-go.db",
276
+ "mcp_bioconductor-gosemsim",
277
+ "mcp_bioconductor-graph",
278
+ "mcp_bioconductor-gsva",
279
+ "mcp_bioconductor-hcatonsildata",
280
+ "mcp_bioconductor-hdf5array",
281
+ "mcp_bioconductor-hoodscanr",
282
+ "mcp_bioconductor-humanhippocampus2024",
283
+ "mcp_bioconductor-imcdatasets",
284
+ "mcp_bioconductor-imcrtools",
285
+ "mcp_bioconductor-impute",
286
+ "mcp_bioconductor-infercnv",
287
+ "mcp_bioconductor-interactivedisplaybase",
288
+ "mcp_bioconductor-iranges",
289
+ "mcp_bioconductor-irisfgm",
290
+ "mcp_bioconductor-jazzpanda",
291
+ "mcp_bioconductor-lisaclust",
292
+ "mcp_bioconductor-mastr",
293
+ "mcp_bioconductor-matrixgenerics",
294
+ "mcp_bioconductor-merfishdata",
295
+ "mcp_bioconductor-metabolomicsworkbenchr",
296
+ "mcp_bioconductor-metapod",
297
+ "mcp_bioconductor-mia",
298
+ "mcp_bioconductor-moleculeexperiment",
299
+ "mcp_bioconductor-mosim",
300
+ "mcp_bioconductor-mousegastrulationdata",
301
+ "mcp_bioconductor-mspurity",
302
+ "mcp_bioconductor-multiassayexperiment",
303
+ "mcp_bioconductor-multtest",
304
+ "mcp_bioconductor-nebulosa",
305
+ "mcp_bioconductor-nnsvg",
306
+ "mcp_bioconductor-noiseq",
307
+ "mcp_bioconductor-orfhunter",
308
+ "mcp_bioconductor-org.ce.eg.db",
309
+ "mcp_bioconductor-org.hs.eg.db",
310
+ "mcp_bioconductor-org.mm.eg.db",
311
+ "mcp_bioconductor-partcnv",
312
+ "mcp_bioconductor-phemd",
313
+ "mcp_bioconductor-phyloseq",
314
+ "mcp_bioconductor-pipecomp",
315
+ "mcp_bioconductor-poem",
316
+ "mcp_bioconductor-preprocesscore",
317
+ "mcp_bioconductor-proteomicsannotationhubdata",
318
+ "mcp_bioconductor-protgenerics",
319
+ "mcp_bioconductor-qvalue",
320
+ "mcp_bioconductor-rbgl",
321
+ "mcp_bioconductor-reactomegsa.data",
322
+ "mcp_bioconductor-regionalst",
323
+ "mcp_bioconductor-rforproteomics",
324
+ "mcp_bioconductor-rgraphviz",
325
+ "mcp_bioconductor-rhdf5",
326
+ "mcp_bioconductor-rhdf5filters",
327
+ "mcp_bioconductor-rhdf5lib",
328
+ "mcp_bioconductor-rhtslib",
329
+ "mcp_bioconductor-rsamtools",
330
+ "mcp_bioconductor-scaledmatrix",
331
+ "mcp_bioconductor-scalign",
332
+ "mcp_bioconductor-scarray.sat",
333
+ "mcp_bioconductor-scbfa",
334
+ "mcp_bioconductor-scbubbletree",
335
+ "mcp_bioconductor-sccb2",
336
+ "mcp_bioconductor-scclassifr",
337
+ "mcp_bioconductor-scdataviz",
338
+ "mcp_bioconductor-scdotplot",
339
+ "mcp_bioconductor-scfeatures",
340
+ "mcp_bioconductor-scider",
341
+ "mcp_bioconductor-scmageck",
342
+ "mcp_bioconductor-scqtltools",
343
+ "mcp_bioconductor-scran",
344
+ "mcp_bioconductor-screpertoire",
345
+ "mcp_bioconductor-scrnaseq",
346
+ "mcp_bioconductor-sctreeviz",
347
+ "mcp_bioconductor-scuttle",
348
+ "mcp_bioconductor-scvir",
349
+ "mcp_bioconductor-seraster",
350
+ "mcp_bioconductor-shortread",
351
+ "mcp_bioconductor-signifinder",
352
+ "mcp_bioconductor-singlecellmultimodal",
353
+ "mcp_bioconductor-smoothclust",
354
+ "mcp_bioconductor-smoppix",
355
+ "mcp_bioconductor-sosta",
356
+ "mcp_bioconductor-spacetrooper",
357
+ "mcp_bioconductor-spaniel",
358
+ "mcp_bioconductor-spari",
359
+ "mcp_bioconductor-sparsematrixstats",
360
+ "mcp_bioconductor-spatialcpie",
361
+ "mcp_bioconductor-spatialdatasets",
362
+ "mcp_bioconductor-spatialdecon",
363
+ "mcp_bioconductor-spatialdmelxsim",
364
+ "mcp_bioconductor-spatialexperiment",
365
+ "mcp_bioconductor-spatialexperimentio",
366
+ "mcp_bioconductor-spatialfda",
367
+ "mcp_bioconductor-spatialfeatureexperiment",
368
+ "mcp_bioconductor-spatialheatmap",
369
+ "mcp_bioconductor-speckle",
370
+ "mcp_bioconductor-spoon",
371
+ "mcp_bioconductor-spotclean",
372
+ "mcp_bioconductor-spotsweeper",
373
+ "mcp_bioconductor-standr",
374
+ "mcp_bioconductor-statial",
375
+ "mcp_bioconductor-stjoincount",
376
+ "mcp_bioconductor-summarizedexperiment",
377
+ "mcp_bioconductor-svp",
378
+ "mcp_bioconductor-tenxxeniumdata",
379
+ "mcp_bioconductor-tidyomics",
380
+ "mcp_bioconductor-tidyspatialexperiment",
381
+ "mcp_bioconductor-treesummarizedexperiment",
382
+ "mcp_bioconductor-vectrapolarisdata",
383
+ "mcp_bioconductor-visiumio",
384
+ "mcp_bioconductor-weberdivechalcdata",
385
+ "mcp_bioconductor-xcms",
386
+ "mcp_bioconductor-xenlite",
387
+ "mcp_bioconductor-xvector",
388
+ "mcp_bioconductor-zlibbioc",
389
+ "mcp_biopython",
390
+ "mcp_bowtie",
391
+ "mcp_bowtie2",
392
+ "mcp_bpipe",
393
+ "mcp_brooklyn_plot",
394
+ "mcp_busco",
395
+ "mcp_bx-python",
396
+ "mcp_c-ares",
397
+ "mcp_cascade-reg",
398
+ "mcp_cd-hit",
399
+ "mcp_cdbtools",
400
+ "mcp_cell2cell",
401
+ "mcp_cellitac",
402
+ "mcp_cellqc",
403
+ "mcp_cellrank",
404
+ "mcp_cellsnake",
405
+ "mcp_celltypist-so",
406
+ "mcp_checkatlas",
407
+ "mcp_checkm-genome"
408
+ ]
409
+ },
410
+ "1500": {
411
+ "config": "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_1500.yaml",
412
+ "server_count": 378,
413
+ "tool_count": 1527,
414
+ "servers": [
415
+ "mcp_kallisto",
416
+ "mcp_kraken2",
417
+ "mcp_csvtk",
418
+ "mcp_megahit",
419
+ "mcp_kaiju",
420
+ "mcp_fastp",
421
+ "mcp_spades",
422
+ "mcp_scanpy",
423
+ "mcp_scanpy-cli",
424
+ "mcp_scanpy-scripts",
425
+ "mcp_seurat-scripts",
426
+ "mcp_bioconductor-deseq2",
427
+ "mcp_bioconductor-edger",
428
+ "mcp_bioconductor-limma",
429
+ "mcp_bioconductor-clusterprofiler",
430
+ "mcp_star",
431
+ "mcp_hisat2",
432
+ "mcp_subread",
433
+ "mcp_bcftools",
434
+ "mcp_bwa",
435
+ "mcp_gatk4",
436
+ "mcp_gatk",
437
+ "mcp_snpeff",
438
+ "mcp_freebayes",
439
+ "mcp_orthofinder",
440
+ "mcp_blast",
441
+ "mcp_blast-legacy",
442
+ "mcp_diamond",
443
+ "mcp_gffread",
444
+ "mcp_seqkit",
445
+ "mcp_multiqc",
446
+ "mcp_fastqc",
447
+ "mcp_trimmomatic",
448
+ "mcp_bioconductor-keggrest",
449
+ "mcp_snpsift",
450
+ "mcp_bioconductor-rsubread",
451
+ "mcp_bioconductor-tximport",
452
+ "mcp_bioconductor-variantannotation",
453
+ "mcp_celltypist",
454
+ "mcp_abnumber",
455
+ "mcp_abricate",
456
+ "mcp_abundancebin",
457
+ "mcp_abyss",
458
+ "mcp_adapterremoval",
459
+ "mcp_alfred",
460
+ "mcp_anansescanpy",
461
+ "mcp_anarci",
462
+ "mcp_aragorn",
463
+ "mcp_aria2",
464
+ "mcp_ariba",
465
+ "mcp_arriba",
466
+ "mcp_art",
467
+ "mcp_arvados-cwl-runner",
468
+ "mcp_arvados-python-client",
469
+ "mcp_atropos",
470
+ "mcp_augustus",
471
+ "mcp_auspice",
472
+ "mcp_bactopia",
473
+ "mcp_bamtools",
474
+ "mcp_barrnap",
475
+ "mcp_bbmap",
476
+ "mcp_bcbio-gff",
477
+ "mcp_bcbio-nextgen",
478
+ "mcp_bedops",
479
+ "mcp_bedtools",
480
+ "mcp_bin2cell",
481
+ "mcp_bioawk",
482
+ "mcp_biobambam",
483
+ "mcp_bioconductor-affy",
484
+ "mcp_bioconductor-affyio",
485
+ "mcp_bioconductor-alabaster",
486
+ "mcp_bioconductor-alabaster.sfe",
487
+ "mcp_bioconductor-alabaster.spatial",
488
+ "mcp_bioconductor-annotate",
489
+ "mcp_bioconductor-annotationdbi",
490
+ "mcp_bioconductor-annotationfilter",
491
+ "mcp_bioconductor-apeglm",
492
+ "mcp_bioconductor-apl",
493
+ "mcp_bioconductor-awaggregator",
494
+ "mcp_bioconductor-banksy",
495
+ "mcp_bioconductor-beachmat",
496
+ "mcp_bioconductor-benchdamic",
497
+ "mcp_bioconductor-biobase",
498
+ "mcp_bioconductor-biocbaseutils",
499
+ "mcp_bioconductor-biocfilecache",
500
+ "mcp_bioconductor-biocgenerics",
501
+ "mcp_bioconductor-biocio",
502
+ "mcp_bioconductor-biocneighbors",
503
+ "mcp_bioconductor-biocparallel",
504
+ "mcp_bioconductor-biocsingular",
505
+ "mcp_bioconductor-biomart",
506
+ "mcp_bioconductor-biomformat",
507
+ "mcp_bioconductor-biostrings",
508
+ "mcp_bioconductor-biovizbase",
509
+ "mcp_bioconductor-blase",
510
+ "mcp_bioconductor-bluster",
511
+ "mcp_bioconductor-bulksignalr",
512
+ "mcp_bioconductor-cardspa",
513
+ "mcp_bioconductor-catscradle",
514
+ "mcp_bioconductor-cdi",
515
+ "mcp_bioconductor-cellhashr",
516
+ "mcp_bioconductor-cellid",
517
+ "mcp_bioconductor-cellmigration",
518
+ "mcp_bioconductor-clusterfoldsimilarity",
519
+ "mcp_bioconductor-clustifyr",
520
+ "mcp_bioconductor-clustsignal",
521
+ "mcp_bioconductor-complexheatmap",
522
+ "mcp_bioconductor-concordexr",
523
+ "mcp_bioconductor-cotan",
524
+ "mcp_bioconductor-csoa",
525
+ "mcp_bioconductor-ctsv",
526
+ "mcp_bioconductor-curatedatlasqueryr",
527
+ "mcp_bioconductor-cytomapper",
528
+ "mcp_bioconductor-dada2",
529
+ "mcp_bioconductor-data-packages",
530
+ "mcp_bioconductor-decipher",
531
+ "mcp_bioconductor-decontam",
532
+ "mcp_bioconductor-decontx",
533
+ "mcp_bioconductor-deconvobuddies",
534
+ "mcp_bioconductor-delayedmatrixstats",
535
+ "mcp_bioconductor-despace",
536
+ "mcp_bioconductor-diffbind",
537
+ "mcp_bioconductor-dino",
538
+ "mcp_bioconductor-dirichletmultinomial",
539
+ "mcp_bioconductor-dnacopy",
540
+ "mcp_bioconductor-dose",
541
+ "mcp_bioconductor-ensembldb",
542
+ "mcp_bioconductor-erma",
543
+ "mcp_bioconductor-escher",
544
+ "mcp_bioconductor-experimentsubset",
545
+ "mcp_bioconductor-fgsea",
546
+ "mcp_bioconductor-genefilter",
547
+ "mcp_bioconductor-geneplotter",
548
+ "mcp_bioconductor-genomeinfodb",
549
+ "mcp_bioconductor-genomeinfodbdata",
550
+ "mcp_bioconductor-genomicalignments",
551
+ "mcp_bioconductor-genomicfeatures",
552
+ "mcp_bioconductor-genomicranges",
553
+ "mcp_bioconductor-geomxtools",
554
+ "mcp_bioconductor-ggsc",
555
+ "mcp_bioconductor-ggspavis",
556
+ "mcp_bioconductor-ggtree",
557
+ "mcp_bioconductor-glmgampoi",
558
+ "mcp_bioconductor-go.db",
559
+ "mcp_bioconductor-gosemsim",
560
+ "mcp_bioconductor-graph",
561
+ "mcp_bioconductor-gsva",
562
+ "mcp_bioconductor-hcatonsildata",
563
+ "mcp_bioconductor-hdf5array",
564
+ "mcp_bioconductor-hoodscanr",
565
+ "mcp_bioconductor-humanhippocampus2024",
566
+ "mcp_bioconductor-imcdatasets",
567
+ "mcp_bioconductor-imcrtools",
568
+ "mcp_bioconductor-impute",
569
+ "mcp_bioconductor-infercnv",
570
+ "mcp_bioconductor-interactivedisplaybase",
571
+ "mcp_bioconductor-iranges",
572
+ "mcp_bioconductor-irisfgm",
573
+ "mcp_bioconductor-jazzpanda",
574
+ "mcp_bioconductor-lisaclust",
575
+ "mcp_bioconductor-mastr",
576
+ "mcp_bioconductor-matrixgenerics",
577
+ "mcp_bioconductor-merfishdata",
578
+ "mcp_bioconductor-metabolomicsworkbenchr",
579
+ "mcp_bioconductor-metapod",
580
+ "mcp_bioconductor-mia",
581
+ "mcp_bioconductor-moleculeexperiment",
582
+ "mcp_bioconductor-mosim",
583
+ "mcp_bioconductor-mousegastrulationdata",
584
+ "mcp_bioconductor-mspurity",
585
+ "mcp_bioconductor-multiassayexperiment",
586
+ "mcp_bioconductor-multtest",
587
+ "mcp_bioconductor-nebulosa",
588
+ "mcp_bioconductor-nnsvg",
589
+ "mcp_bioconductor-noiseq",
590
+ "mcp_bioconductor-orfhunter",
591
+ "mcp_bioconductor-org.ce.eg.db",
592
+ "mcp_bioconductor-org.hs.eg.db",
593
+ "mcp_bioconductor-org.mm.eg.db",
594
+ "mcp_bioconductor-partcnv",
595
+ "mcp_bioconductor-phemd",
596
+ "mcp_bioconductor-phyloseq",
597
+ "mcp_bioconductor-pipecomp",
598
+ "mcp_bioconductor-poem",
599
+ "mcp_bioconductor-preprocesscore",
600
+ "mcp_bioconductor-proteomicsannotationhubdata",
601
+ "mcp_bioconductor-protgenerics",
602
+ "mcp_bioconductor-qvalue",
603
+ "mcp_bioconductor-rbgl",
604
+ "mcp_bioconductor-reactomegsa.data",
605
+ "mcp_bioconductor-regionalst",
606
+ "mcp_bioconductor-rforproteomics",
607
+ "mcp_bioconductor-rgraphviz",
608
+ "mcp_bioconductor-rhdf5",
609
+ "mcp_bioconductor-rhdf5filters",
610
+ "mcp_bioconductor-rhdf5lib",
611
+ "mcp_bioconductor-rhtslib",
612
+ "mcp_bioconductor-rsamtools",
613
+ "mcp_bioconductor-scaledmatrix",
614
+ "mcp_bioconductor-scalign",
615
+ "mcp_bioconductor-scarray.sat",
616
+ "mcp_bioconductor-scbfa",
617
+ "mcp_bioconductor-scbubbletree",
618
+ "mcp_bioconductor-sccb2",
619
+ "mcp_bioconductor-scclassifr",
620
+ "mcp_bioconductor-scdataviz",
621
+ "mcp_bioconductor-scdotplot",
622
+ "mcp_bioconductor-scfeatures",
623
+ "mcp_bioconductor-scider",
624
+ "mcp_bioconductor-scmageck",
625
+ "mcp_bioconductor-scqtltools",
626
+ "mcp_bioconductor-scran",
627
+ "mcp_bioconductor-screpertoire",
628
+ "mcp_bioconductor-scrnaseq",
629
+ "mcp_bioconductor-sctreeviz",
630
+ "mcp_bioconductor-scuttle",
631
+ "mcp_bioconductor-scvir",
632
+ "mcp_bioconductor-seraster",
633
+ "mcp_bioconductor-shortread",
634
+ "mcp_bioconductor-signifinder",
635
+ "mcp_bioconductor-singlecellmultimodal",
636
+ "mcp_bioconductor-smoothclust",
637
+ "mcp_bioconductor-smoppix",
638
+ "mcp_bioconductor-sosta",
639
+ "mcp_bioconductor-spacetrooper",
640
+ "mcp_bioconductor-spaniel",
641
+ "mcp_bioconductor-spari",
642
+ "mcp_bioconductor-sparsematrixstats",
643
+ "mcp_bioconductor-spatialcpie",
644
+ "mcp_bioconductor-spatialdatasets",
645
+ "mcp_bioconductor-spatialdecon",
646
+ "mcp_bioconductor-spatialdmelxsim",
647
+ "mcp_bioconductor-spatialexperiment",
648
+ "mcp_bioconductor-spatialexperimentio",
649
+ "mcp_bioconductor-spatialfda",
650
+ "mcp_bioconductor-spatialfeatureexperiment",
651
+ "mcp_bioconductor-spatialheatmap",
652
+ "mcp_bioconductor-speckle",
653
+ "mcp_bioconductor-spoon",
654
+ "mcp_bioconductor-spotclean",
655
+ "mcp_bioconductor-spotsweeper",
656
+ "mcp_bioconductor-standr",
657
+ "mcp_bioconductor-statial",
658
+ "mcp_bioconductor-stjoincount",
659
+ "mcp_bioconductor-summarizedexperiment",
660
+ "mcp_bioconductor-svp",
661
+ "mcp_bioconductor-tenxxeniumdata",
662
+ "mcp_bioconductor-tidyomics",
663
+ "mcp_bioconductor-tidyspatialexperiment",
664
+ "mcp_bioconductor-treesummarizedexperiment",
665
+ "mcp_bioconductor-vectrapolarisdata",
666
+ "mcp_bioconductor-visiumio",
667
+ "mcp_bioconductor-weberdivechalcdata",
668
+ "mcp_bioconductor-xcms",
669
+ "mcp_bioconductor-xenlite",
670
+ "mcp_bioconductor-xvector",
671
+ "mcp_bioconductor-zlibbioc",
672
+ "mcp_biopython",
673
+ "mcp_bowtie",
674
+ "mcp_bowtie2",
675
+ "mcp_bpipe",
676
+ "mcp_brooklyn_plot",
677
+ "mcp_busco",
678
+ "mcp_bx-python",
679
+ "mcp_c-ares",
680
+ "mcp_cascade-reg",
681
+ "mcp_cd-hit",
682
+ "mcp_cdbtools",
683
+ "mcp_cell2cell",
684
+ "mcp_cellitac",
685
+ "mcp_cellqc",
686
+ "mcp_cellrank",
687
+ "mcp_cellsnake",
688
+ "mcp_celltypist-so",
689
+ "mcp_checkatlas",
690
+ "mcp_checkm-genome",
691
+ "mcp_circexplorer2",
692
+ "mcp_circos",
693
+ "mcp_clustalw",
694
+ "mcp_cmappy",
695
+ "mcp_cnmf",
696
+ "mcp_cnvkit",
697
+ "mcp_comebin",
698
+ "mcp_comet-ms",
699
+ "mcp_constellations",
700
+ "mcp_cooler",
701
+ "mcp_cooltools",
702
+ "mcp_coreutils",
703
+ "mcp_cosg",
704
+ "mcp_cospar",
705
+ "mcp_crispresso2",
706
+ "mcp_crispritz",
707
+ "mcp_crisprme",
708
+ "mcp_cromwell",
709
+ "mcp_cutadapt",
710
+ "mcp_cwltool",
711
+ "mcp_cytoscape",
712
+ "mcp_cytotrace2-python",
713
+ "mcp_cyvcf2",
714
+ "mcp_ddocent",
715
+ "mcp_deblur",
716
+ "mcp_decoupler",
717
+ "mcp_deeptools",
718
+ "mcp_deeptoolsintervals",
719
+ "mcp_delly",
720
+ "mcp_dendropy",
721
+ "mcp_dnaio",
722
+ "mcp_doubletdetection",
723
+ "mcp_dropkick",
724
+ "mcp_dsh-bio",
725
+ "mcp_dxpy",
726
+ "mcp_easy_vitessce",
727
+ "mcp_echidna",
728
+ "mcp_emboss",
729
+ "mcp_ena-webin-cli",
730
+ "mcp_ensembl-vep",
731
+ "mcp_entrez-direct",
732
+ "mcp_epic",
733
+ "mcp_episcanpy",
734
+ "mcp_eva-sub-cli",
735
+ "mcp_f5c",
736
+ "mcp_famsa",
737
+ "mcp_fast5",
738
+ "mcp_fastani",
739
+ "mcp_fastdtw",
740
+ "mcp_fasttree",
741
+ "mcp_fermi2",
742
+ "mcp_fgbio",
743
+ "mcp_filechunkio",
744
+ "mcp_flagx",
745
+ "mcp_flye",
746
+ "mcp_ftputil",
747
+ "mcp_fwdpy11",
748
+ "mcp_galaxy-lib",
749
+ "mcp_gatk4-spark",
750
+ "mcp_gdk-pixbuf",
751
+ "mcp_gecode",
752
+ "mcp_gene-trajectory-python",
753
+ "mcp_genecircuitry",
754
+ "mcp_geneimpacts",
755
+ "mcp_genoboo",
756
+ "mcp_genomepy",
757
+ "mcp_genometools-genometools",
758
+ "mcp_gffutils",
759
+ "mcp_ghostscript",
760
+ "mcp_gimmemotifs",
761
+ "mcp_glimmerhmm",
762
+ "mcp_gmap",
763
+ "mcp_gneiss",
764
+ "mcp_gnuplot",
765
+ "mcp_gofasta",
766
+ "mcp_goleft",
767
+ "mcp_gromacs",
768
+ "mcp_gseapy",
769
+ "mcp_gsmap",
770
+ "mcp_gtdbtk",
771
+ "mcp_harpy",
772
+ "mcp_hicexplorer",
773
+ "mcp_hifiasm",
774
+ "mcp_hmmer",
775
+ "mcp_htseq",
776
+ "mcp_humann2",
777
+ "mcp_igv",
778
+ "mcp_igv-reports",
779
+ "mcp_illumina-interop",
780
+ "mcp_infernal",
781
+ "mcp_intarna",
782
+ "mcp_involucro",
783
+ "mcp_iow",
784
+ "mcp_ipyrad",
785
+ "mcp_ipython-cluster-helper",
786
+ "mcp_iqtree",
787
+ "mcp_itsxpress",
788
+ "mcp_ivar",
789
+ "mcp_jalview",
790
+ "mcp_java-jdk",
791
+ "mcp_jbrowse2",
792
+ "mcp_jcvi"
793
+ ]
794
+ },
795
+ "2000": {
796
+ "config": "/225040511/project/Biomni/experiments/bioagent_bench/configs/mcp_scale_2000.yaml",
797
+ "server_count": 526,
798
+ "tool_count": 2000,
799
+ "servers": [
800
+ "mcp_kallisto",
801
+ "mcp_kraken2",
802
+ "mcp_csvtk",
803
+ "mcp_megahit",
804
+ "mcp_kaiju",
805
+ "mcp_fastp",
806
+ "mcp_spades",
807
+ "mcp_scanpy",
808
+ "mcp_scanpy-cli",
809
+ "mcp_scanpy-scripts",
810
+ "mcp_seurat-scripts",
811
+ "mcp_bioconductor-deseq2",
812
+ "mcp_bioconductor-edger",
813
+ "mcp_bioconductor-limma",
814
+ "mcp_bioconductor-clusterprofiler",
815
+ "mcp_star",
816
+ "mcp_hisat2",
817
+ "mcp_subread",
818
+ "mcp_bcftools",
819
+ "mcp_bwa",
820
+ "mcp_gatk4",
821
+ "mcp_gatk",
822
+ "mcp_snpeff",
823
+ "mcp_freebayes",
824
+ "mcp_orthofinder",
825
+ "mcp_blast",
826
+ "mcp_blast-legacy",
827
+ "mcp_diamond",
828
+ "mcp_gffread",
829
+ "mcp_seqkit",
830
+ "mcp_multiqc",
831
+ "mcp_fastqc",
832
+ "mcp_trimmomatic",
833
+ "mcp_bioconductor-keggrest",
834
+ "mcp_snpsift",
835
+ "mcp_bioconductor-rsubread",
836
+ "mcp_bioconductor-tximport",
837
+ "mcp_bioconductor-variantannotation",
838
+ "mcp_celltypist",
839
+ "mcp_abnumber",
840
+ "mcp_abricate",
841
+ "mcp_abundancebin",
842
+ "mcp_abyss",
843
+ "mcp_adapterremoval",
844
+ "mcp_alfred",
845
+ "mcp_anansescanpy",
846
+ "mcp_anarci",
847
+ "mcp_aragorn",
848
+ "mcp_aria2",
849
+ "mcp_ariba",
850
+ "mcp_arriba",
851
+ "mcp_art",
852
+ "mcp_arvados-cwl-runner",
853
+ "mcp_arvados-python-client",
854
+ "mcp_atropos",
855
+ "mcp_augustus",
856
+ "mcp_auspice",
857
+ "mcp_bactopia",
858
+ "mcp_bamtools",
859
+ "mcp_barrnap",
860
+ "mcp_bbmap",
861
+ "mcp_bcbio-gff",
862
+ "mcp_bcbio-nextgen",
863
+ "mcp_bedops",
864
+ "mcp_bedtools",
865
+ "mcp_bin2cell",
866
+ "mcp_bioawk",
867
+ "mcp_biobambam",
868
+ "mcp_bioconductor-affy",
869
+ "mcp_bioconductor-affyio",
870
+ "mcp_bioconductor-alabaster",
871
+ "mcp_bioconductor-alabaster.sfe",
872
+ "mcp_bioconductor-alabaster.spatial",
873
+ "mcp_bioconductor-annotate",
874
+ "mcp_bioconductor-annotationdbi",
875
+ "mcp_bioconductor-annotationfilter",
876
+ "mcp_bioconductor-apeglm",
877
+ "mcp_bioconductor-apl",
878
+ "mcp_bioconductor-awaggregator",
879
+ "mcp_bioconductor-banksy",
880
+ "mcp_bioconductor-beachmat",
881
+ "mcp_bioconductor-benchdamic",
882
+ "mcp_bioconductor-biobase",
883
+ "mcp_bioconductor-biocbaseutils",
884
+ "mcp_bioconductor-biocfilecache",
885
+ "mcp_bioconductor-biocgenerics",
886
+ "mcp_bioconductor-biocio",
887
+ "mcp_bioconductor-biocneighbors",
888
+ "mcp_bioconductor-biocparallel",
889
+ "mcp_bioconductor-biocsingular",
890
+ "mcp_bioconductor-biomart",
891
+ "mcp_bioconductor-biomformat",
892
+ "mcp_bioconductor-biostrings",
893
+ "mcp_bioconductor-biovizbase",
894
+ "mcp_bioconductor-blase",
895
+ "mcp_bioconductor-bluster",
896
+ "mcp_bioconductor-bulksignalr",
897
+ "mcp_bioconductor-cardspa",
898
+ "mcp_bioconductor-catscradle",
899
+ "mcp_bioconductor-cdi",
900
+ "mcp_bioconductor-cellhashr",
901
+ "mcp_bioconductor-cellid",
902
+ "mcp_bioconductor-cellmigration",
903
+ "mcp_bioconductor-clusterfoldsimilarity",
904
+ "mcp_bioconductor-clustifyr",
905
+ "mcp_bioconductor-clustsignal",
906
+ "mcp_bioconductor-complexheatmap",
907
+ "mcp_bioconductor-concordexr",
908
+ "mcp_bioconductor-cotan",
909
+ "mcp_bioconductor-csoa",
910
+ "mcp_bioconductor-ctsv",
911
+ "mcp_bioconductor-curatedatlasqueryr",
912
+ "mcp_bioconductor-cytomapper",
913
+ "mcp_bioconductor-dada2",
914
+ "mcp_bioconductor-data-packages",
915
+ "mcp_bioconductor-decipher",
916
+ "mcp_bioconductor-decontam",
917
+ "mcp_bioconductor-decontx",
918
+ "mcp_bioconductor-deconvobuddies",
919
+ "mcp_bioconductor-delayedmatrixstats",
920
+ "mcp_bioconductor-despace",
921
+ "mcp_bioconductor-diffbind",
922
+ "mcp_bioconductor-dino",
923
+ "mcp_bioconductor-dirichletmultinomial",
924
+ "mcp_bioconductor-dnacopy",
925
+ "mcp_bioconductor-dose",
926
+ "mcp_bioconductor-ensembldb",
927
+ "mcp_bioconductor-erma",
928
+ "mcp_bioconductor-escher",
929
+ "mcp_bioconductor-experimentsubset",
930
+ "mcp_bioconductor-fgsea",
931
+ "mcp_bioconductor-genefilter",
932
+ "mcp_bioconductor-geneplotter",
933
+ "mcp_bioconductor-genomeinfodb",
934
+ "mcp_bioconductor-genomeinfodbdata",
935
+ "mcp_bioconductor-genomicalignments",
936
+ "mcp_bioconductor-genomicfeatures",
937
+ "mcp_bioconductor-genomicranges",
938
+ "mcp_bioconductor-geomxtools",
939
+ "mcp_bioconductor-ggsc",
940
+ "mcp_bioconductor-ggspavis",
941
+ "mcp_bioconductor-ggtree",
942
+ "mcp_bioconductor-glmgampoi",
943
+ "mcp_bioconductor-go.db",
944
+ "mcp_bioconductor-gosemsim",
945
+ "mcp_bioconductor-graph",
946
+ "mcp_bioconductor-gsva",
947
+ "mcp_bioconductor-hcatonsildata",
948
+ "mcp_bioconductor-hdf5array",
949
+ "mcp_bioconductor-hoodscanr",
950
+ "mcp_bioconductor-humanhippocampus2024",
951
+ "mcp_bioconductor-imcdatasets",
952
+ "mcp_bioconductor-imcrtools",
953
+ "mcp_bioconductor-impute",
954
+ "mcp_bioconductor-infercnv",
955
+ "mcp_bioconductor-interactivedisplaybase",
956
+ "mcp_bioconductor-iranges",
957
+ "mcp_bioconductor-irisfgm",
958
+ "mcp_bioconductor-jazzpanda",
959
+ "mcp_bioconductor-lisaclust",
960
+ "mcp_bioconductor-mastr",
961
+ "mcp_bioconductor-matrixgenerics",
962
+ "mcp_bioconductor-merfishdata",
963
+ "mcp_bioconductor-metabolomicsworkbenchr",
964
+ "mcp_bioconductor-metapod",
965
+ "mcp_bioconductor-mia",
966
+ "mcp_bioconductor-moleculeexperiment",
967
+ "mcp_bioconductor-mosim",
968
+ "mcp_bioconductor-mousegastrulationdata",
969
+ "mcp_bioconductor-mspurity",
970
+ "mcp_bioconductor-multiassayexperiment",
971
+ "mcp_bioconductor-multtest",
972
+ "mcp_bioconductor-nebulosa",
973
+ "mcp_bioconductor-nnsvg",
974
+ "mcp_bioconductor-noiseq",
975
+ "mcp_bioconductor-orfhunter",
976
+ "mcp_bioconductor-org.ce.eg.db",
977
+ "mcp_bioconductor-org.hs.eg.db",
978
+ "mcp_bioconductor-org.mm.eg.db",
979
+ "mcp_bioconductor-partcnv",
980
+ "mcp_bioconductor-phemd",
981
+ "mcp_bioconductor-phyloseq",
982
+ "mcp_bioconductor-pipecomp",
983
+ "mcp_bioconductor-poem",
984
+ "mcp_bioconductor-preprocesscore",
985
+ "mcp_bioconductor-proteomicsannotationhubdata",
986
+ "mcp_bioconductor-protgenerics",
987
+ "mcp_bioconductor-qvalue",
988
+ "mcp_bioconductor-rbgl",
989
+ "mcp_bioconductor-reactomegsa.data",
990
+ "mcp_bioconductor-regionalst",
991
+ "mcp_bioconductor-rforproteomics",
992
+ "mcp_bioconductor-rgraphviz",
993
+ "mcp_bioconductor-rhdf5",
994
+ "mcp_bioconductor-rhdf5filters",
995
+ "mcp_bioconductor-rhdf5lib",
996
+ "mcp_bioconductor-rhtslib",
997
+ "mcp_bioconductor-rsamtools",
998
+ "mcp_bioconductor-scaledmatrix",
999
+ "mcp_bioconductor-scalign",
1000
+ "mcp_bioconductor-scarray.sat",
1001
+ "mcp_bioconductor-scbfa",
1002
+ "mcp_bioconductor-scbubbletree",
1003
+ "mcp_bioconductor-sccb2",
1004
+ "mcp_bioconductor-scclassifr",
1005
+ "mcp_bioconductor-scdataviz",
1006
+ "mcp_bioconductor-scdotplot",
1007
+ "mcp_bioconductor-scfeatures",
1008
+ "mcp_bioconductor-scider",
1009
+ "mcp_bioconductor-scmageck",
1010
+ "mcp_bioconductor-scqtltools",
1011
+ "mcp_bioconductor-scran",
1012
+ "mcp_bioconductor-screpertoire",
1013
+ "mcp_bioconductor-scrnaseq",
1014
+ "mcp_bioconductor-sctreeviz",
1015
+ "mcp_bioconductor-scuttle",
1016
+ "mcp_bioconductor-scvir",
1017
+ "mcp_bioconductor-seraster",
1018
+ "mcp_bioconductor-shortread",
1019
+ "mcp_bioconductor-signifinder",
1020
+ "mcp_bioconductor-singlecellmultimodal",
1021
+ "mcp_bioconductor-smoothclust",
1022
+ "mcp_bioconductor-smoppix",
1023
+ "mcp_bioconductor-sosta",
1024
+ "mcp_bioconductor-spacetrooper",
1025
+ "mcp_bioconductor-spaniel",
1026
+ "mcp_bioconductor-spari",
1027
+ "mcp_bioconductor-sparsematrixstats",
1028
+ "mcp_bioconductor-spatialcpie",
1029
+ "mcp_bioconductor-spatialdatasets",
1030
+ "mcp_bioconductor-spatialdecon",
1031
+ "mcp_bioconductor-spatialdmelxsim",
1032
+ "mcp_bioconductor-spatialexperiment",
1033
+ "mcp_bioconductor-spatialexperimentio",
1034
+ "mcp_bioconductor-spatialfda",
1035
+ "mcp_bioconductor-spatialfeatureexperiment",
1036
+ "mcp_bioconductor-spatialheatmap",
1037
+ "mcp_bioconductor-speckle",
1038
+ "mcp_bioconductor-spoon",
1039
+ "mcp_bioconductor-spotclean",
1040
+ "mcp_bioconductor-spotsweeper",
1041
+ "mcp_bioconductor-standr",
1042
+ "mcp_bioconductor-statial",
1043
+ "mcp_bioconductor-stjoincount",
1044
+ "mcp_bioconductor-summarizedexperiment",
1045
+ "mcp_bioconductor-svp",
1046
+ "mcp_bioconductor-tenxxeniumdata",
1047
+ "mcp_bioconductor-tidyomics",
1048
+ "mcp_bioconductor-tidyspatialexperiment",
1049
+ "mcp_bioconductor-treesummarizedexperiment",
1050
+ "mcp_bioconductor-vectrapolarisdata",
1051
+ "mcp_bioconductor-visiumio",
1052
+ "mcp_bioconductor-weberdivechalcdata",
1053
+ "mcp_bioconductor-xcms",
1054
+ "mcp_bioconductor-xenlite",
1055
+ "mcp_bioconductor-xvector",
1056
+ "mcp_bioconductor-zlibbioc",
1057
+ "mcp_biopython",
1058
+ "mcp_bowtie",
1059
+ "mcp_bowtie2",
1060
+ "mcp_bpipe",
1061
+ "mcp_brooklyn_plot",
1062
+ "mcp_busco",
1063
+ "mcp_bx-python",
1064
+ "mcp_c-ares",
1065
+ "mcp_cascade-reg",
1066
+ "mcp_cd-hit",
1067
+ "mcp_cdbtools",
1068
+ "mcp_cell2cell",
1069
+ "mcp_cellitac",
1070
+ "mcp_cellqc",
1071
+ "mcp_cellrank",
1072
+ "mcp_cellsnake",
1073
+ "mcp_celltypist-so",
1074
+ "mcp_checkatlas",
1075
+ "mcp_checkm-genome",
1076
+ "mcp_circexplorer2",
1077
+ "mcp_circos",
1078
+ "mcp_clustalw",
1079
+ "mcp_cmappy",
1080
+ "mcp_cnmf",
1081
+ "mcp_cnvkit",
1082
+ "mcp_comebin",
1083
+ "mcp_comet-ms",
1084
+ "mcp_constellations",
1085
+ "mcp_cooler",
1086
+ "mcp_cooltools",
1087
+ "mcp_coreutils",
1088
+ "mcp_cosg",
1089
+ "mcp_cospar",
1090
+ "mcp_crispresso2",
1091
+ "mcp_crispritz",
1092
+ "mcp_crisprme",
1093
+ "mcp_cromwell",
1094
+ "mcp_cutadapt",
1095
+ "mcp_cwltool",
1096
+ "mcp_cytoscape",
1097
+ "mcp_cytotrace2-python",
1098
+ "mcp_cyvcf2",
1099
+ "mcp_ddocent",
1100
+ "mcp_deblur",
1101
+ "mcp_decoupler",
1102
+ "mcp_deeptools",
1103
+ "mcp_deeptoolsintervals",
1104
+ "mcp_delly",
1105
+ "mcp_dendropy",
1106
+ "mcp_dnaio",
1107
+ "mcp_doubletdetection",
1108
+ "mcp_dropkick",
1109
+ "mcp_dsh-bio",
1110
+ "mcp_dxpy",
1111
+ "mcp_easy_vitessce",
1112
+ "mcp_echidna",
1113
+ "mcp_emboss",
1114
+ "mcp_ena-webin-cli",
1115
+ "mcp_ensembl-vep",
1116
+ "mcp_entrez-direct",
1117
+ "mcp_epic",
1118
+ "mcp_episcanpy",
1119
+ "mcp_eva-sub-cli",
1120
+ "mcp_f5c",
1121
+ "mcp_famsa",
1122
+ "mcp_fast5",
1123
+ "mcp_fastani",
1124
+ "mcp_fastdtw",
1125
+ "mcp_fasttree",
1126
+ "mcp_fermi2",
1127
+ "mcp_fgbio",
1128
+ "mcp_filechunkio",
1129
+ "mcp_flagx",
1130
+ "mcp_flye",
1131
+ "mcp_ftputil",
1132
+ "mcp_fwdpy11",
1133
+ "mcp_galaxy-lib",
1134
+ "mcp_gatk4-spark",
1135
+ "mcp_gdk-pixbuf",
1136
+ "mcp_gecode",
1137
+ "mcp_gene-trajectory-python",
1138
+ "mcp_genecircuitry",
1139
+ "mcp_geneimpacts",
1140
+ "mcp_genoboo",
1141
+ "mcp_genomepy",
1142
+ "mcp_genometools-genometools",
1143
+ "mcp_gffutils",
1144
+ "mcp_ghostscript",
1145
+ "mcp_gimmemotifs",
1146
+ "mcp_glimmerhmm",
1147
+ "mcp_gmap",
1148
+ "mcp_gneiss",
1149
+ "mcp_gnuplot",
1150
+ "mcp_gofasta",
1151
+ "mcp_goleft",
1152
+ "mcp_gromacs",
1153
+ "mcp_gseapy",
1154
+ "mcp_gsmap",
1155
+ "mcp_gtdbtk",
1156
+ "mcp_harpy",
1157
+ "mcp_hicexplorer",
1158
+ "mcp_hifiasm",
1159
+ "mcp_hmmer",
1160
+ "mcp_htseq",
1161
+ "mcp_humann2",
1162
+ "mcp_igv",
1163
+ "mcp_igv-reports",
1164
+ "mcp_illumina-interop",
1165
+ "mcp_infernal",
1166
+ "mcp_intarna",
1167
+ "mcp_involucro",
1168
+ "mcp_iow",
1169
+ "mcp_ipyrad",
1170
+ "mcp_ipython-cluster-helper",
1171
+ "mcp_iqtree",
1172
+ "mcp_itsxpress",
1173
+ "mcp_ivar",
1174
+ "mcp_jalview",
1175
+ "mcp_java-jdk",
1176
+ "mcp_jbrowse2",
1177
+ "mcp_jcvi",
1178
+ "mcp_jellyfish",
1179
+ "mcp_jq",
1180
+ "mcp_k8",
1181
+ "mcp_kalign2",
1182
+ "mcp_kb-python",
1183
+ "mcp_khipu-metabolomics",
1184
+ "mcp_kmer-jellyfish",
1185
+ "mcp_krona",
1186
+ "mcp_last",
1187
+ "mcp_lastz",
1188
+ "mcp_libcifpp",
1189
+ "mcp_libdeflate",
1190
+ "mcp_libsequence",
1191
+ "mcp_locarna",
1192
+ "mcp_macs2",
1193
+ "mcp_mafft",
1194
+ "mcp_mageck",
1195
+ "mcp_mameshiba",
1196
+ "mcp_mappy",
1197
+ "mcp_markerrepo",
1198
+ "mcp_mash",
1199
+ "mcp_maxquant",
1200
+ "mcp_medaka",
1201
+ "mcp_meme",
1202
+ "mcp_mentalist",
1203
+ "mcp_metagenome-atlas",
1204
+ "mcp_metaphlan",
1205
+ "mcp_metaphlan2",
1206
+ "mcp_mikado",
1207
+ "mcp_minced",
1208
+ "mcp_miniasm",
1209
+ "mcp_minimap2",
1210
+ "mcp_minvar",
1211
+ "mcp_mitos",
1212
+ "mcp_mmtf-python",
1213
+ "mcp_mobivision-m",
1214
+ "mcp_moments",
1215
+ "mcp_mosdepth",
1216
+ "mcp_mothur",
1217
+ "mcp_msisensor-pro",
1218
+ "mcp_msproteomicstools",
1219
+ "mcp_msstitch",
1220
+ "mcp_multiqc-bcbio",
1221
+ "mcp_multiqc-xenium-extra",
1222
+ "mcp_multivelo",
1223
+ "mcp_mummer",
1224
+ "mcp_munkres",
1225
+ "mcp_muscle",
1226
+ "mcp_mysql-connector-c",
1227
+ "mcp_mztosqlite",
1228
+ "mcp_nanocomp",
1229
+ "mcp_nanoget",
1230
+ "mcp_nanomath",
1231
+ "mcp_nanopolish",
1232
+ "mcp_ncbi-datasets-pylib",
1233
+ "mcp_ncbi-genome-download",
1234
+ "mcp_ncbi-ngs-sdk",
1235
+ "mcp_ncbi-vdb",
1236
+ "mcp_ncls",
1237
+ "mcp_nextalign",
1238
+ "mcp_nextclade",
1239
+ "mcp_nextflow",
1240
+ "mcp_nf-core",
1241
+ "mcp_nglview",
1242
+ "mcp_ngmlr",
1243
+ "mcp_ngs-smap",
1244
+ "mcp_ngs-tools",
1245
+ "mcp_novae",
1246
+ "mcp_novoalign",
1247
+ "mcp_ont-fast5-api",
1248
+ "mcp_openms-thirdparty",
1249
+ "mcp_pairix",
1250
+ "mcp_palantir",
1251
+ "mcp_paml",
1252
+ "mcp_pango-designation",
1253
+ "mcp_pangolearn",
1254
+ "mcp_parasail-python",
1255
+ "mcp_pasta",
1256
+ "mcp_paste-bio",
1257
+ "mcp_pcdl",
1258
+ "mcp_peakqc",
1259
+ "mcp_peptide-shaker",
1260
+ "mcp_perl-aceperl",
1261
+ "mcp_perl-algorithm-diff",
1262
+ "mcp_perl-alien-libxml2",
1263
+ "mcp_perl-app-cpanminus",
1264
+ "mcp_perl-appconfig",
1265
+ "mcp_perl-archive-tar",
1266
+ "mcp_perl-autoloader",
1267
+ "mcp_perl-base",
1268
+ "mcp_perl-bio-asn1-entrezgene",
1269
+ "mcp_perl-bio-coordinate",
1270
+ "mcp_perl-bio-featureio",
1271
+ "mcp_perl-bio-samtools",
1272
+ "mcp_perl-bio-searchio-hmmer",
1273
+ "mcp_perl-bio-tools-phylo-paml",
1274
+ "mcp_perl-bio-tools-run-alignment-clustalw",
1275
+ "mcp_perl-bioperl-core",
1276
+ "mcp_perl-business-isbn",
1277
+ "mcp_perl-business-isbn-data",
1278
+ "mcp_perl-capture-tiny",
1279
+ "mcp_perl-cgi",
1280
+ "mcp_perl-class-inspector",
1281
+ "mcp_perl-common-sense",
1282
+ "mcp_perl-compress-raw-zlib",
1283
+ "mcp_perl-config-general",
1284
+ "mcp_perl-constant",
1285
+ "mcp_perl-convert-binary-c",
1286
+ "mcp_perl-convert-binhex",
1287
+ "mcp_perl-cpan-meta-requirements",
1288
+ "mcp_perl-crypt-rc4",
1289
+ "mcp_perl-data-dumper",
1290
+ "mcp_perl-data-optlist",
1291
+ "mcp_perl-date-format",
1292
+ "mcp_perl-dbi",
1293
+ "mcp_perl-devel-overloadinfo",
1294
+ "mcp_perl-devel-stacktrace",
1295
+ "mcp_perl-digest-md5",
1296
+ "mcp_perl-digest-perl-md5",
1297
+ "mcp_perl-digest-sha1",
1298
+ "mcp_perl-encode-locale",
1299
+ "mcp_perl-exception-class",
1300
+ "mcp_perl-exporter",
1301
+ "mcp_perl-extutils-cbuilder",
1302
+ "mcp_perl-extutils-makemaker",
1303
+ "mcp_perl-ffi-checklib",
1304
+ "mcp_perl-file-sort",
1305
+ "mcp_perl-file-spec",
1306
+ "mcp_perl-file-which",
1307
+ "mcp_perl-font-afm",
1308
+ "mcp_perl-getopt-long",
1309
+ "mcp_perl-graph",
1310
+ "mcp_perl-graphviz",
1311
+ "mcp_perl-html-element-extended",
1312
+ "mcp_perl-html-formatter",
1313
+ "mcp_perl-html-parser",
1314
+ "mcp_perl-html-tableextract",
1315
+ "mcp_perl-html-tagset",
1316
+ "mcp_perl-html-tree",
1317
+ "mcp_perl-html-treebuilder-xpath",
1318
+ "mcp_perl-http-cookies",
1319
+ "mcp_perl-http-daemon",
1320
+ "mcp_perl-http-date",
1321
+ "mcp_perl-http-negotiate",
1322
+ "mcp_perl-image-info",
1323
+ "mcp_perl-image-size",
1324
+ "mcp_perl-importer",
1325
+ "mcp_perl-io-compress"
1326
+ ]
1327
+ }
1328
+ }
1329
+ }
experiments/bioagent_bench/evaluate_bioagent_bench.py ADDED
@@ -0,0 +1,1198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Evaluate Hypo_Bio_OS BioAgent Bench runs.
3
+
4
+ This evaluator follows the BioAgent Bench paper's grader design:
5
+ evaluate each trial as a pipeline execution, prioritize demonstrable
6
+ pipeline completion over exact numeric agreement, and return an
7
+ EvaluationResults-style JSON object.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import csv
14
+ import gzip
15
+ import json
16
+ import os
17
+ import re
18
+ from bisect import bisect_right
19
+ from datetime import timezone, datetime
20
+ from difflib import SequenceMatcher
21
+ from pathlib import Path
22
+ from statistics import mean
23
+ from typing import Any
24
+
25
+
26
+ DATASET_ROOT = Path("/225040511/project/bioagent-bench/dataset")
27
+ METADATA_PATH = Path("/225040511/project/bioagent-bench/src/task_metadata.json")
28
+ DEFAULT_RUNS_ROOT = Path("/225040511/project/Hypo_Bio_OS/bioagent-bench-runs")
29
+
30
+
31
+ FLEXIBLE_TABLE_MATCH_CONFIGS: dict[tuple[str, str], dict[str, Any]] = {
32
+ (
33
+ "alzheimer-mouse",
34
+ "pathway_comparison.csv",
35
+ ): {
36
+ "soft_key_columns": ["Pathway"],
37
+ "threshold": 0.62,
38
+ "text_weight": 0.9,
39
+ "numeric_weight": 0.1,
40
+ },
41
+ (
42
+ "comparative-genomics",
43
+ "cluster_annotation_mapping.csv",
44
+ ): {
45
+ "soft_key_columns": ["consensus_annotation"],
46
+ "threshold": 0.68,
47
+ },
48
+ (
49
+ "metagenomics",
50
+ "phylum_relative_abundances.csv",
51
+ ): {
52
+ "soft_key_columns": ["Phylum"],
53
+ "threshold": 0.9,
54
+ "text_weight": 1.0,
55
+ "numeric_weight": 0.0,
56
+ },
57
+ (
58
+ "single-cell",
59
+ "all_clusters_de_genes.csv",
60
+ ): {
61
+ "soft_key_columns": ["gene_name", "cluster_id"],
62
+ "threshold": 0.78,
63
+ "text_weight": 0.85,
64
+ "numeric_weight": 0.15,
65
+ },
66
+ (
67
+ "viral-metagenomics",
68
+ "taxonomy.csv",
69
+ ): {
70
+ "soft_key_columns": ["domain", "species"],
71
+ "threshold": 0.72,
72
+ "text_weight": 0.85,
73
+ "numeric_weight": 0.15,
74
+ },
75
+ }
76
+
77
+
78
+ TASK_CONFIGS: dict[str, dict[str, Any]] = {
79
+ "alzheimer-mouse": {
80
+ "truth_files": ["pathway_comparison.csv"],
81
+ "result_files": ["pathway_comparison.csv"],
82
+ "key_columns": ["Pathway"],
83
+ "numeric_columns": ["5xFAD_pvalue", "3xTG_AD_pvalue", "PS3O1S_pvalue"],
84
+ "pipeline_steps": [
85
+ "inspect mouse count/DEA inputs",
86
+ "prepare metadata and count matrices for 5xFAD and 3xTG-AD",
87
+ "perform differential expression for 5xFAD",
88
+ "perform differential expression for 3xTG-AD",
89
+ "use provided PS3O1S differential expression results",
90
+ "run pathway enrichment per model",
91
+ "merge shared/comparative pathway p-values into final CSV",
92
+ ],
93
+ "results_match_guidance": (
94
+ "Treat mouse mmu/Mus musculus pathway labels as semantically compatible with hsa/Homo sapiens labels "
95
+ "when the pathway identity is the same. Do not require exact p-value equality if the pipeline is plausible."
96
+ ),
97
+ },
98
+ "comparative-genomics": {
99
+ "truth_files": ["cluster_annotation_mapping.csv"],
100
+ "result_files": ["cluster_annotation_mapping.csv"],
101
+ "key_columns": ["cluster_number", "consensus_annotation"],
102
+ "numeric_columns": [],
103
+ "pipeline_steps": [
104
+ "inspect Micrococcus FASTA/GFF/reference inputs",
105
+ "predict or extract protein-coding genes",
106
+ "identify orthologous/co-evolving clusters across genomes",
107
+ "filter clusters present across intended genomes and coding-only",
108
+ "assign high-confidence consensus annotations",
109
+ "write cluster_number,consensus_annotation CSV",
110
+ ],
111
+ "results_match_guidance": (
112
+ "Prioritize whether the output represents conserved annotated coding clusters. Exact cluster numbering "
113
+ "can vary by method, but placeholder annotations or unrelated organisms should not match."
114
+ ),
115
+ },
116
+ "cystic-fibrosis": {
117
+ "truth_files": ["cf_variants.csv"],
118
+ "result_files": ["cf_variants.csv"],
119
+ "key_columns": ["chromosome", "position", "reference", "alternate"],
120
+ "numeric_columns": [],
121
+ "verifiable": True,
122
+ "pipeline_steps": [
123
+ "inspect family description and variant VCF",
124
+ "filter variants by recessive inheritance in affected siblings",
125
+ "exclude variants inconsistent with unaffected relatives/parents",
126
+ "annotate candidate variant with ClinVar/reference metadata",
127
+ "write the requested causal-variant CSV schema",
128
+ ],
129
+ "results_match_guidance": (
130
+ "The result should identify the CFTR pathogenic recessive variant. Exact textual disease lists may differ, "
131
+ "but chromosome, position, ref/alt, gene, and clinical interpretation must be consistent."
132
+ ),
133
+ },
134
+ "deseq": {
135
+ "truth_files": ["up_regulated_genes.csv"],
136
+ "result_files": ["up_regulated_genes.csv"],
137
+ "key_columns": ["gene_id"],
138
+ "numeric_columns": ["log2FoldChange", "pvalue", "padj"],
139
+ "pipeline_steps": [
140
+ "inspect RNA-seq reads and Candida reference files",
141
+ "prepare genome annotation/index",
142
+ "align reads or otherwise quantify genes",
143
+ "count reads per gene",
144
+ "construct biofilm/planktonic sample metadata",
145
+ "run differential expression",
146
+ "filter up-regulated significant genes and write final CSV",
147
+ ],
148
+ "results_match_guidance": (
149
+ "Prioritize evidence of a complete RNA-seq DE pipeline and a plausible up-regulated gene table. "
150
+ "Do not require exact equality for all p-values/log fold changes."
151
+ ),
152
+ },
153
+ "evolution": {
154
+ "truth_files": ["variants_shared.csv", "gene_annotations.csv"],
155
+ "result_files": ["variants_shared.csv", "gene_annotations.csv"],
156
+ "key_columns": {
157
+ "variants_shared.csv": ["CHROM", "POS", "REF", "ALT"],
158
+ "gene_annotations.csv": ["Gene_Name"],
159
+ },
160
+ "numeric_columns": {"variants_shared.csv": [], "gene_annotations.csv": []},
161
+ "pipeline_steps": [
162
+ "inspect ancestor/evolved-line reads",
163
+ "prepare or identify valid E. coli reference/assembly",
164
+ "align ancestor and evolved reads",
165
+ "call variants for all samples",
166
+ "identify variants shared by evolved lines and absent from ancestor",
167
+ "annotate variant/gene effects",
168
+ "write variants_shared.csv and gene_annotations.csv",
169
+ ],
170
+ "results_match_guidance": (
171
+ "Reward a biologically coherent shared-variant workflow. Penalize using forbidden external/sibling references "
172
+ "or hallucinated annotations even if the final schema exists."
173
+ ),
174
+ },
175
+ "giab": {
176
+ "truth_files": ["HG001_GRCh38_1_22_v4.2.1_benchmark.vcf.gz"],
177
+ "result_files": ["predicted.vcf.gz"],
178
+ "vcf": True,
179
+ "verifiable": True,
180
+ "pipeline_steps": [
181
+ "inspect paired reads, BED targets, and GRCh38 reference",
182
+ "align reads to reference",
183
+ "sort/index BAM",
184
+ "mark duplicates or prepare analysis-ready BAM",
185
+ "call variants",
186
+ "compress/index final VCF if needed",
187
+ "write predicted.vcf.gz",
188
+ ],
189
+ "results_match_guidance": (
190
+ "Use GIAB variant concordance as the correctness signal. The f1_score field should reflect variant-level "
191
+ "overlap where available."
192
+ ),
193
+ },
194
+ "metagenomics": {
195
+ "truth_files": ["phylum_relative_abundances.csv"],
196
+ "result_files": ["phylum_relative_abundances.csv"],
197
+ "key_columns": ["OTU"],
198
+ "numeric_columns": ["JP4D", "JC1A"],
199
+ "pipeline_steps": [
200
+ "inspect paired metagenomic reads and reference database",
201
+ "classify reads taxonomically",
202
+ "aggregate classifications at bacterial phylum level",
203
+ "normalize relative abundances for JP4D and JC1A",
204
+ "write OTU,Kingdom,Phylum,JP4D,JC1A CSV",
205
+ ],
206
+ "results_match_guidance": (
207
+ "Prioritize correct use of the bacterial/metagenomic reference and plausible phylum-level abundance table. "
208
+ "Small abundance differences are acceptable."
209
+ ),
210
+ },
211
+ "single-cell": {
212
+ "truth_files": ["all_clusters_de_genes.csv"],
213
+ "result_files": ["all_clusters_de_genes.csv"],
214
+ "key_columns": ["cluster_id", "gene_name"],
215
+ "numeric_columns": ["logfoldchanges", "pvals", "pvals_adj", "abs_logfc"],
216
+ "pipeline_steps": [
217
+ "load 10X matrices and metadata",
218
+ "perform QC/normalization",
219
+ "cluster cells and preserve cluster IDs",
220
+ "annotate cell types using marker evidence",
221
+ "compare pre/post exercise within cell types or clusters",
222
+ "write all significant DE genes in requested schema",
223
+ ],
224
+ "results_match_guidance": (
225
+ "Cell-type labels and cluster identities may vary, so prioritize marker-supported annotation, DE evidence, "
226
+ "and schema correctness over exact row equality."
227
+ ),
228
+ },
229
+ "transcript-quant": {
230
+ "truth_files": ["truth.tsv"],
231
+ "result_files": ["truth.tsv"],
232
+ "tsv_no_header": True,
233
+ "key_columns": ["transcript_id"],
234
+ "numeric_columns": ["count"],
235
+ "verifiable": True,
236
+ "pipeline_steps": [
237
+ "inspect paired RNA-seq reads and transcriptome reference",
238
+ "build transcriptome index",
239
+ "quantify transcript abundance/counts",
240
+ "extract transcript_id and count columns",
241
+ "write no-header two-column TSV",
242
+ ],
243
+ "results_match_guidance": (
244
+ "Because the data is simulated, counts should closely reproduce the truth. Headerless two-column TSV format "
245
+ "is required."
246
+ ),
247
+ },
248
+ "viral-metagenomics": {
249
+ "truth_files": ["taxonomy.csv"],
250
+ "result_files": ["taxonomy.csv"],
251
+ "key_columns": ["domain", "species"],
252
+ "numeric_columns": ["contig_count"],
253
+ "verifiable": True,
254
+ "pipeline_steps": [
255
+ "inspect dolphin metagenomic reads and viral reference resources",
256
+ "assemble reads into contigs",
257
+ "classify contigs against compatible viral reference resources",
258
+ "aggregate contig counts by domain/species",
259
+ "write contig_count,domain,species CSV",
260
+ ],
261
+ "results_match_guidance": (
262
+ "Prioritize using this task's viral reference resources and correctly identifying viral species. "
263
+ "Unclassified contigs are allowed when supported by classification output."
264
+ ),
265
+ },
266
+ }
267
+
268
+
269
+ def utc_timestamp() -> str:
270
+ return datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
271
+
272
+
273
+ def normalize_text(value: Any) -> str:
274
+ return " ".join(str(value).strip().split()).lower()
275
+
276
+
277
+ def normalize_for_soft_match(value: Any) -> str:
278
+ text = normalize_text(value)
279
+ text = re.sub(r"\b(homo sapiens|mus musculus|human|mouse)\b", " ", text)
280
+ text = re.sub(r"\b(hsa|mmu)(?=\d)", " ", text)
281
+ text = re.sub(r"[^a-z0-9]+", " ", text)
282
+ return " ".join(text.split())
283
+
284
+
285
+ def normalize_chrom(value: Any) -> str:
286
+ chrom = normalize_text(value)
287
+ if chrom.startswith("chr"):
288
+ chrom = chrom[3:]
289
+ return {"m": "mt", "mitochondria": "mt"}.get(chrom, chrom)
290
+
291
+
292
+ def maybe_float(value: Any) -> float | None:
293
+ try:
294
+ return float(str(value).strip())
295
+ except Exception:
296
+ return None
297
+
298
+
299
+ def get_row_value(row: dict, column: str, default: str = ""):
300
+ if column in row:
301
+ return row.get(column, default)
302
+ target = normalize_text(column)
303
+ for key, value in row.items():
304
+ if normalize_text(key) == target:
305
+ return value
306
+ return default
307
+
308
+
309
+ def token_similarity(left: str, right: str) -> float:
310
+ left_tokens = set(left.split())
311
+ right_tokens = set(right.split())
312
+ if not left_tokens and not right_tokens:
313
+ return 1.0
314
+ if not left_tokens or not right_tokens:
315
+ return 0.0
316
+ return len(left_tokens & right_tokens) / len(left_tokens | right_tokens)
317
+
318
+
319
+ def text_similarity(left: Any, right: Any) -> float:
320
+ left_norm = normalize_for_soft_match(left)
321
+ right_norm = normalize_for_soft_match(right)
322
+ if left_norm == right_norm:
323
+ return 1.0
324
+ if not left_norm or not right_norm:
325
+ return 0.0
326
+ sequence_score = SequenceMatcher(None, left_norm, right_norm).ratio()
327
+ token_score = token_similarity(left_norm, right_norm)
328
+ return max(sequence_score, token_score)
329
+
330
+
331
+ def numeric_similarity(left: Any, right: Any, rel_scale: float = 0.1) -> float | None:
332
+ left_value = maybe_float(left)
333
+ right_value = maybe_float(right)
334
+ if left_value is None or right_value is None:
335
+ return None
336
+ if left_value == right_value:
337
+ return 1.0
338
+ scale = max(abs(left_value), abs(right_value), 1e-12)
339
+ relative_error = abs(left_value - right_value) / scale
340
+ return max(0.0, 1.0 - (relative_error / rel_scale))
341
+
342
+
343
+ def load_task_metadata(metadata_path: Path) -> dict[str, dict]:
344
+ if not metadata_path.exists():
345
+ return {}
346
+ payload = json.loads(metadata_path.read_text(encoding="utf-8"))
347
+ return {item["task_id"]: item for item in payload}
348
+
349
+
350
+ def load_run_metadata(run_dir: Path) -> dict:
351
+ path = run_dir / "run_metadata.json"
352
+ if not path.exists():
353
+ return {}
354
+ try:
355
+ return json.loads(path.read_text(encoding="utf-8"))
356
+ except json.JSONDecodeError:
357
+ return {}
358
+
359
+
360
+ def load_csv_rows(path: Path) -> list[dict]:
361
+ with path.open("r", encoding="utf-8", errors="ignore", newline="") as handle:
362
+ return list(csv.DictReader(handle))
363
+
364
+
365
+ def load_tsv_no_header(path: Path) -> list[dict]:
366
+ rows = []
367
+ with path.open("r", encoding="utf-8", errors="ignore") as handle:
368
+ for line in handle:
369
+ line = line.rstrip("\n")
370
+ if not line:
371
+ continue
372
+ parts = line.split("\t")
373
+ if len(parts) >= 2:
374
+ rows.append({"transcript_id": parts[0], "count": parts[1]})
375
+ return rows
376
+
377
+
378
+ def read_text_preview(path: Path, max_lines: int = 80, max_chars: int = 24000) -> str:
379
+ opener = gzip.open if path.suffix == ".gz" else open
380
+ preview_lines = []
381
+ total_chars = 0
382
+ with opener(path, "rt", encoding="utf-8", errors="ignore") as handle:
383
+ for i, line in enumerate(handle):
384
+ if i >= max_lines:
385
+ preview_lines.append("... [truncated]")
386
+ break
387
+ preview_lines.append(line.rstrip("\n"))
388
+ total_chars += len(line)
389
+ if total_chars >= max_chars:
390
+ preview_lines.append("... [truncated]")
391
+ break
392
+ return "\n".join(preview_lines)
393
+
394
+
395
+ def load_prediction_rows(task_id: str, file_name: str, prediction_path: Path) -> list[dict]:
396
+ if TASK_CONFIGS[task_id].get("tsv_no_header"):
397
+ return load_tsv_no_header(prediction_path)
398
+ return load_csv_rows(prediction_path)
399
+
400
+
401
+ def open_text_auto(path: Path):
402
+ return gzip.open(path, "rt", encoding="utf-8", errors="ignore") if path.suffix == ".gz" else path.open(
403
+ "r", encoding="utf-8", errors="ignore"
404
+ )
405
+
406
+
407
+ def parse_bed_intervals(path: Path) -> dict[str, list[tuple[int, int]]]:
408
+ intervals: dict[str, list[tuple[int, int]]] = {}
409
+ if not path.exists():
410
+ return intervals
411
+ with open_text_auto(path) as handle:
412
+ for line in handle:
413
+ if not line.strip() or line.startswith(("#", "track", "browser")):
414
+ continue
415
+ fields = line.rstrip("\n").split("\t")
416
+ if len(fields) < 3:
417
+ continue
418
+ try:
419
+ start = int(fields[1])
420
+ end = int(fields[2])
421
+ except ValueError:
422
+ continue
423
+ chrom = normalize_chrom(fields[0])
424
+ intervals.setdefault(chrom, []).append((start, end))
425
+ for chrom in intervals:
426
+ intervals[chrom].sort()
427
+ return intervals
428
+
429
+
430
+ def position_in_intervals(chrom: str, pos: str, intervals: dict[str, list[tuple[int, int]]] | None) -> bool:
431
+ if not intervals:
432
+ return True
433
+ try:
434
+ pos_1_based = int(pos)
435
+ except ValueError:
436
+ return False
437
+ pos_0_based = pos_1_based - 1
438
+ chrom_intervals = intervals.get(normalize_chrom(chrom), [])
439
+ interval_index = bisect_right(chrom_intervals, (pos_0_based, 10**18)) - 1
440
+ if interval_index < 0:
441
+ return False
442
+ start, end = chrom_intervals[interval_index]
443
+ return start <= pos_0_based < end
444
+
445
+
446
+ def load_vcf_keys(path: Path, intervals: dict[str, list[tuple[int, int]]] | None = None) -> set[tuple[str, str, str, str]]:
447
+ keys = set()
448
+ with open_text_auto(path) as handle:
449
+ for line in handle:
450
+ if not line or line.startswith("#"):
451
+ continue
452
+ fields = line.rstrip("\n").split("\t")
453
+ if len(fields) < 5:
454
+ continue
455
+ chrom, pos, _vid, ref, alt = fields[:5]
456
+ if not position_in_intervals(chrom, pos, intervals):
457
+ continue
458
+ for alt_item in alt.split(","):
459
+ keys.add((normalize_chrom(chrom), normalize_text(pos), normalize_text(ref), normalize_text(alt_item)))
460
+ return keys
461
+
462
+
463
+ def f1_from_counts(tp: int, pred_count: int, truth_count: int) -> dict[str, float]:
464
+ precision = tp / pred_count if pred_count else 0.0
465
+ recall = tp / truth_count if truth_count else 0.0
466
+ f1 = 0.0 if precision + recall == 0 else (2 * precision * recall) / (precision + recall)
467
+ return {"precision": precision, "recall": recall, "f1": f1}
468
+
469
+
470
+ def compare_variant_sets(pred_keys: set[tuple[str, str, str, str]], truth_keys: set[tuple[str, str, str, str]]) -> dict:
471
+ tp = len(pred_keys & truth_keys)
472
+ metrics = f1_from_counts(tp, len(pred_keys), len(truth_keys))
473
+ return {
474
+ "pred_variant_count": len(pred_keys),
475
+ "truth_variant_count": len(truth_keys),
476
+ "shared_variant_count": tp,
477
+ "precision": metrics["precision"],
478
+ "recall": metrics["recall"],
479
+ "f1": metrics["f1"],
480
+ }
481
+
482
+
483
+ def compare_vcf(prediction_path: Path, truth_path: Path, task_id: str, dataset_root: Path) -> dict:
484
+ bed_candidates = [
485
+ dataset_root / task_id / "data" / "Agilent_v7.chr.bed",
486
+ dataset_root / task_id / "results" / "HG001_GRCh38_1_22_v4.2.1_benchmark.bed",
487
+ ]
488
+ bed_path = next((path for path in bed_candidates if path.exists()), None)
489
+ if not bed_path:
490
+ unfiltered_pred_keys = load_vcf_keys(prediction_path)
491
+ unfiltered_truth_keys = load_vcf_keys(truth_path)
492
+ unfiltered = compare_variant_sets(unfiltered_pred_keys, unfiltered_truth_keys)
493
+ return {
494
+ **unfiltered,
495
+ "truth_scope": "all_truth_variants",
496
+ "unfiltered_f1": unfiltered["f1"],
497
+ "note": "Approximate VCF comparison by normalized CHROM,POS,REF,ALT; no BED scope was available.",
498
+ }
499
+
500
+ intervals = parse_bed_intervals(bed_path)
501
+ scoped_pred_keys = load_vcf_keys(prediction_path, intervals=intervals)
502
+ scoped_truth_keys = load_vcf_keys(truth_path, intervals=intervals)
503
+ scoped = compare_variant_sets(scoped_pred_keys, scoped_truth_keys)
504
+ return {
505
+ **scoped,
506
+ "truth_scope": "target_bed",
507
+ "target_bed": str(bed_path),
508
+ "unfiltered_f1": None,
509
+ "note": "Primary F1 is restricted to the task target BED and uses normalized CHROM,POS,REF,ALT.",
510
+ }
511
+
512
+
513
+ def row_similarity(
514
+ pred: dict,
515
+ truth: dict,
516
+ soft_key_columns: list[str],
517
+ numeric_columns: list[str],
518
+ text_weight: float,
519
+ numeric_weight: float,
520
+ ) -> float:
521
+ text_scores = [
522
+ text_similarity(get_row_value(pred, column, ""), get_row_value(truth, column, ""))
523
+ for column in soft_key_columns
524
+ ]
525
+ text_score = sum(text_scores) / len(text_scores) if text_scores else 0.0
526
+
527
+ numeric_scores = [
528
+ score
529
+ for column in numeric_columns
530
+ if (score := numeric_similarity(get_row_value(pred, column, ""), get_row_value(truth, column, ""))) is not None
531
+ ]
532
+ if not numeric_scores or numeric_weight <= 0:
533
+ return text_score
534
+ numeric_score = sum(numeric_scores) / len(numeric_scores)
535
+ total_weight = text_weight + numeric_weight
536
+ return ((text_score * text_weight) + (numeric_score * numeric_weight)) / total_weight
537
+
538
+
539
+ def greedy_soft_row_match(
540
+ pred_rows: list[dict],
541
+ truth_rows: list[dict],
542
+ soft_key_columns: list[str],
543
+ numeric_columns: list[str],
544
+ threshold: float,
545
+ text_weight: float,
546
+ numeric_weight: float,
547
+ ) -> tuple[list[tuple[int, int, float]], list[int], list[int]]:
548
+ candidates = []
549
+ first_soft_column = soft_key_columns[0] if soft_key_columns else None
550
+ truth_buckets: dict[str, list[tuple[int, dict]]] = {}
551
+ use_bucketed_candidates = first_soft_column is not None and len(pred_rows) * len(truth_rows) > 200_000
552
+ if use_bucketed_candidates:
553
+ for truth_index, truth in enumerate(truth_rows):
554
+ bucket_key = normalize_for_soft_match(get_row_value(truth, first_soft_column, ""))
555
+ truth_buckets.setdefault(bucket_key, []).append((truth_index, truth))
556
+
557
+ for pred_index, pred in enumerate(pred_rows):
558
+ if use_bucketed_candidates:
559
+ bucket_key = normalize_for_soft_match(get_row_value(pred, first_soft_column, ""))
560
+ candidate_truth_rows = truth_buckets.get(bucket_key, [])
561
+ else:
562
+ candidate_truth_rows = list(enumerate(truth_rows))
563
+ for truth_index, truth in candidate_truth_rows:
564
+ score = row_similarity(pred, truth, soft_key_columns, numeric_columns, text_weight, numeric_weight)
565
+ if score >= threshold:
566
+ candidates.append((score, pred_index, truth_index))
567
+
568
+ candidates.sort(reverse=True)
569
+ used_pred = set()
570
+ used_truth = set()
571
+ matches = []
572
+ for score, pred_index, truth_index in candidates:
573
+ if pred_index in used_pred or truth_index in used_truth:
574
+ continue
575
+ used_pred.add(pred_index)
576
+ used_truth.add(truth_index)
577
+ matches.append((pred_index, truth_index, score))
578
+ unmatched_pred = [index for index in range(len(pred_rows)) if index not in used_pred]
579
+ unmatched_truth = [index for index in range(len(truth_rows)) if index not in used_truth]
580
+ return matches, unmatched_pred, unmatched_truth
581
+
582
+
583
+ def compare_table_rows(task_id: str, file_name: str, pred_rows: list[dict], truth_rows: list[dict]) -> dict:
584
+ config = TASK_CONFIGS[task_id]
585
+ key_columns = config["key_columns"][file_name] if isinstance(config["key_columns"], dict) else config["key_columns"]
586
+ numeric_columns = (
587
+ config["numeric_columns"][file_name] if isinstance(config["numeric_columns"], dict) else config["numeric_columns"]
588
+ )
589
+
590
+ pred_map = {
591
+ tuple(normalize_text(get_row_value(row, col, "")) for col in key_columns): row
592
+ for row in pred_rows
593
+ }
594
+ truth_map = {
595
+ tuple(normalize_text(get_row_value(row, col, "")) for col in key_columns): row
596
+ for row in truth_rows
597
+ }
598
+
599
+ pred_keys = set(pred_map)
600
+ truth_keys = set(truth_map)
601
+ shared_keys = pred_keys & truth_keys
602
+ metrics = f1_from_counts(len(shared_keys), len(pred_keys), len(truth_keys))
603
+
604
+ numeric_diffs = {col: [] for col in numeric_columns}
605
+ for key in shared_keys:
606
+ pred = pred_map[key]
607
+ truth = truth_map[key]
608
+ for col in numeric_columns:
609
+ pred_value = maybe_float(get_row_value(pred, col, ""))
610
+ truth_value = maybe_float(get_row_value(truth, col, ""))
611
+ if pred_value is not None and truth_value is not None:
612
+ numeric_diffs[col].append(abs(pred_value - truth_value))
613
+
614
+ summary = {
615
+ "pred_row_count": len(pred_rows),
616
+ "truth_row_count": len(truth_rows),
617
+ "shared_key_count": len(shared_keys),
618
+ "exact_key_precision": metrics["precision"],
619
+ "exact_key_recall": metrics["recall"],
620
+ "exact_key_f1": metrics["f1"],
621
+ "key_precision": metrics["precision"],
622
+ "key_recall": metrics["recall"],
623
+ "key_f1": metrics["f1"],
624
+ "match_strategy": "exact_key",
625
+ "match_precision": metrics["precision"],
626
+ "match_recall": metrics["recall"],
627
+ "match_f1": metrics["f1"],
628
+ }
629
+
630
+ flexible_config = FLEXIBLE_TABLE_MATCH_CONFIGS.get((task_id, file_name))
631
+ if flexible_config:
632
+ soft_key_columns = flexible_config.get("soft_key_columns", key_columns)
633
+ threshold = flexible_config.get("threshold", 0.75)
634
+ text_weight = flexible_config.get("text_weight", 1.0)
635
+ numeric_weight = flexible_config.get("numeric_weight", 0.0)
636
+ matches, unmatched_pred, unmatched_truth = greedy_soft_row_match(
637
+ pred_rows,
638
+ truth_rows,
639
+ soft_key_columns,
640
+ numeric_columns,
641
+ threshold,
642
+ text_weight,
643
+ numeric_weight,
644
+ )
645
+ soft_metrics = f1_from_counts(len(matches), len(pred_rows), len(truth_rows))
646
+ summary.update(
647
+ {
648
+ "match_strategy": "soft_row_similarity",
649
+ "soft_key_columns": soft_key_columns,
650
+ "match_threshold": threshold,
651
+ "soft_match_count": len(matches),
652
+ "match_precision": soft_metrics["precision"],
653
+ "match_recall": soft_metrics["recall"],
654
+ "match_f1": soft_metrics["f1"],
655
+ "mean_match_score": mean([score for *_unused, score in matches]) if matches else 0.0,
656
+ "unmatched_prediction_examples": [
657
+ {column: get_row_value(pred_rows[index], column, "") for column in soft_key_columns}
658
+ for index in unmatched_pred[:5]
659
+ ],
660
+ "unmatched_truth_examples": [
661
+ {column: get_row_value(truth_rows[index], column, "") for column in soft_key_columns}
662
+ for index in unmatched_truth[:5]
663
+ ],
664
+ }
665
+ )
666
+ if numeric_columns:
667
+ summary["numeric_mae"] = {
668
+ col: (sum(values) / len(values) if values else None) for col, values in numeric_diffs.items()
669
+ }
670
+ return summary
671
+
672
+
673
+ def summarize_file_for_judge(task_id: str, path: Path) -> dict:
674
+ config = TASK_CONFIGS[task_id]
675
+ if not path.exists():
676
+ return {"path": str(path), "exists": False}
677
+ if config.get("vcf"):
678
+ return {
679
+ "path": str(path),
680
+ "exists": True,
681
+ "type": "vcf.gz",
682
+ "size_bytes": path.stat().st_size,
683
+ "preview": read_text_preview(path, max_lines=60, max_chars=20000),
684
+ }
685
+ if path.suffix.lower() == ".tsv" and config.get("tsv_no_header"):
686
+ rows = load_tsv_no_header(path)
687
+ return {
688
+ "path": str(path),
689
+ "exists": True,
690
+ "type": "tsv",
691
+ "row_count": len(rows),
692
+ "columns": ["transcript_id", "count"],
693
+ "preview": read_text_preview(path, max_lines=80, max_chars=22000),
694
+ }
695
+ rows = load_csv_rows(path)
696
+ return {
697
+ "path": str(path),
698
+ "exists": True,
699
+ "type": path.suffix.lower().lstrip(".") or "text",
700
+ "row_count": len(rows),
701
+ "columns": list(rows[0].keys()) if rows else [],
702
+ "preview": read_text_preview(path, max_lines=80, max_chars=22000),
703
+ }
704
+
705
+
706
+ def locate_prediction_file(task_id: str, run_dir: Path, file_name: str) -> Path | None:
707
+ candidate = run_dir / file_name
708
+ if candidate.exists():
709
+ return candidate
710
+ matches = sorted(run_dir.rglob(file_name))
711
+ return matches[0] if matches else None
712
+
713
+
714
+ def infer_latest_run_dir(task_id: str, runs_root: Path) -> Path | None:
715
+ candidates = sorted(path for path in runs_root.glob(f"{task_id}_*") if path.is_dir())
716
+ return candidates[-1] if candidates else None
717
+
718
+
719
+ def build_processing_tree(run_dir: Path, max_entries: int = 600) -> list[str]:
720
+ entries = []
721
+ for path in sorted(run_dir.rglob("*")):
722
+ rel = path.relative_to(run_dir)
723
+ if len(entries) >= max_entries:
724
+ entries.append("... [truncated]")
725
+ break
726
+ if path.is_dir():
727
+ entries.append(f"{rel}/")
728
+ else:
729
+ entries.append(f"{rel}\t{path.stat().st_size} bytes")
730
+ return entries
731
+
732
+
733
+ def collect_trace_path_evidence(run_dir: Path) -> dict:
734
+ """Collect only folders/file paths from the run, matching the paper's trace input."""
735
+ execution_log = run_dir / "execution_log.txt"
736
+ path_mentions = []
737
+ if execution_log.exists():
738
+ text = execution_log.read_text(encoding="utf-8", errors="ignore")
739
+ for token in text.replace('"', " ").replace("'", " ").split():
740
+ if token.startswith("/") and ("/bioagent-bench/" in token or "/bioagent-bench-runs/" in token):
741
+ cleaned = token.rstrip("),.;:<>")
742
+ if cleaned not in path_mentions:
743
+ path_mentions.append(cleaned)
744
+ if len(path_mentions) >= 250:
745
+ break
746
+ return {
747
+ "processing_tree": build_processing_tree(run_dir),
748
+ "path_mentions_from_trace": path_mentions,
749
+ }
750
+
751
+
752
+ def build_artifact_metrics(task_id: str, run_dir: Path, dataset_root: Path) -> list[dict]:
753
+ config = TASK_CONFIGS[task_id]
754
+ truth_dir = dataset_root / task_id / "results"
755
+ artifacts = []
756
+ for truth_name, result_name in zip(config["truth_files"], config["result_files"], strict=False):
757
+ truth_path = truth_dir / truth_name
758
+ prediction_path = locate_prediction_file(task_id, run_dir, result_name)
759
+ entry = {
760
+ "truth_file": str(truth_path),
761
+ "prediction_file": str(prediction_path) if prediction_path else None,
762
+ "prediction_exists": bool(prediction_path and prediction_path.exists()),
763
+ }
764
+ if not prediction_path or not prediction_path.exists() or not truth_path.exists():
765
+ entry["metrics"] = {"error": "missing prediction or truth file"}
766
+ artifacts.append(entry)
767
+ continue
768
+ if config.get("vcf"):
769
+ entry["metrics"] = compare_vcf(prediction_path, truth_path, task_id, dataset_root)
770
+ else:
771
+ pred_rows = load_prediction_rows(task_id, truth_name, prediction_path)
772
+ truth_rows = load_prediction_rows(task_id, truth_name, truth_path)
773
+ entry["metrics"] = compare_table_rows(task_id, truth_name, pred_rows, truth_rows)
774
+ artifacts.append(entry)
775
+ return artifacts
776
+
777
+
778
+ def result_artifact_summaries(task_id: str, run_dir: Path, dataset_root: Path) -> tuple[list[dict], list[dict]]:
779
+ config = TASK_CONFIGS[task_id]
780
+ truth_dir = dataset_root / task_id / "results"
781
+ result_summaries = []
782
+ truth_summaries = []
783
+ for truth_name, result_name in zip(config["truth_files"], config["result_files"], strict=False):
784
+ prediction_path = locate_prediction_file(task_id, run_dir, result_name)
785
+ truth_path = truth_dir / truth_name
786
+ result_summaries.append(
787
+ summarize_file_for_judge(task_id, prediction_path) if prediction_path else {"path": result_name, "exists": False}
788
+ )
789
+ truth_summaries.append(summarize_file_for_judge(task_id, truth_path))
790
+ return result_summaries, truth_summaries
791
+
792
+
793
+ def infer_rule_steps(task_id: str, artifacts: list[dict], trace_evidence: dict) -> tuple[int, int, list[str]]:
794
+ config = TASK_CONFIGS[task_id]
795
+ expected_steps = config["pipeline_steps"]
796
+ total = len(expected_steps)
797
+ haystack = "\n".join(trace_evidence["processing_tree"] + trace_evidence["path_mentions_from_trace"]).lower()
798
+ completed = 0
799
+ evidence = []
800
+ keyword_sets = {
801
+ "inspect": ["task_query", "run_metadata", "data"],
802
+ "metadata": ["metadata", "counts"],
803
+ "differential": ["deseq", "de_results", "differential", "log2fold"],
804
+ "enrichment": ["kegg", "enrichment", "pathway"],
805
+ "protein": ["prodigal", ".faa", "protein"],
806
+ "ortholog": ["orthofinder", "mmseqs", "diamond", "cluster"],
807
+ "variant": [".vcf", "bcftools", "variant"],
808
+ "align": [".bam", ".sam", "bwa", "star", "aligned"],
809
+ "count": ["feature_counts", "counts", "abundance"],
810
+ "classify": ["kraken", "kaiju", "classification", "report"],
811
+ "assemble": ["spades", "megahit", "contigs", "assembly"],
812
+ "single-cell": ["matrix", "scanpy", "cluster", "umap", "markers"],
813
+ "index": ["index", ".idx", "star_index"],
814
+ "final": [artifact["prediction_file"] or "" for artifact in artifacts],
815
+ }
816
+ for step in expected_steps:
817
+ step_l = step.lower()
818
+ if (
819
+ ("write" in step_l or "final" in step_l)
820
+ and any(name in step_l for name in ["csv", "tsv", "vcf", "predicted"])
821
+ and not any(artifact.get("prediction_exists") for artifact in artifacts)
822
+ ):
823
+ continue
824
+ if (
825
+ ("write" in step_l or "final" in step_l)
826
+ and any(name in step_l for name in ["csv", "tsv", "vcf", "predicted"])
827
+ and all(artifact.get("prediction_exists") for artifact in artifacts)
828
+ ):
829
+ completed += 1
830
+ evidence.append(step)
831
+ continue
832
+ keys = []
833
+ for concept, words in keyword_sets.items():
834
+ if concept in step_l or any(word in step_l for word in words[:2]):
835
+ keys.extend(words)
836
+ if not keys:
837
+ generic = {"and", "or", "the", "to", "a", "an", "write", "final", "requested"}
838
+ keys = [token for token in step_l.replace(",", " ").split() if len(token) > 3 and token not in generic][:4]
839
+ if any(key and key.lower() in haystack for key in keys):
840
+ completed += 1
841
+ evidence.append(step)
842
+
843
+ # Final artifacts are the strongest evidence for final-result step.
844
+ final_files = [artifact for artifact in artifacts if artifact.get("prediction_exists")]
845
+ if final_files and completed < total:
846
+ completed = max(completed, total - 1)
847
+ evidence.append("final artifact(s) exist; inferred most upstream steps completed")
848
+ return min(completed, total), total, evidence
849
+
850
+
851
+ def rule_trial_judge(task_id: str, artifacts: list[dict], trace_evidence: dict) -> dict:
852
+ steps_completed, steps_to_completion, evidence = infer_rule_steps(task_id, artifacts, trace_evidence)
853
+ final_result_reached = all(artifact.get("prediction_exists") for artifact in artifacts)
854
+ metric_scores = []
855
+ giab_f1 = None
856
+ for artifact in artifacts:
857
+ metrics = artifact.get("metrics", {})
858
+ if "match_f1" in metrics:
859
+ metric_scores.append(metrics["match_f1"])
860
+ elif "f1" in metrics:
861
+ metric_scores.append(metrics["f1"])
862
+ if task_id == "giab":
863
+ giab_f1 = metrics["f1"]
864
+ elif "key_f1" in metrics:
865
+ metric_scores.append(metrics["key_f1"])
866
+ mean_metric_score = mean(metric_scores) if metric_scores else 0.0
867
+
868
+ # Paper-style correctness flag is task/rubric specific. In rule mode, use a conservative helper.
869
+ if TASK_CONFIGS[task_id].get("verifiable"):
870
+ threshold = 0.5 if task_id == "giab" else 0.8
871
+ results_match = final_result_reached and mean_metric_score >= threshold
872
+ else:
873
+ results_match = final_result_reached and steps_completed == steps_to_completion
874
+
875
+ notes = (
876
+ "Rule-mode approximation of the paper's LLM grader. "
877
+ f"Evidence-backed steps: {', '.join(evidence[:8]) if evidence else 'none detected'}."
878
+ )
879
+ return {
880
+ "steps_completed": steps_completed,
881
+ "steps_to_completion": steps_to_completion,
882
+ "final_result_reached": final_result_reached,
883
+ "notes": notes,
884
+ "results_match": results_match,
885
+ "f1_score": giab_f1,
886
+ }
887
+
888
+
889
+ def resolve_judge_client(provider: str, model: str | None, base_url: str | None, api_key: str | None):
890
+ try:
891
+ from openai import OpenAI
892
+ except ImportError as exc:
893
+ raise ImportError("openai package is required for LLM judging.") from exc
894
+
895
+ provider = provider.lower().strip()
896
+ if provider == "deepseek":
897
+ resolved_model = model or os.getenv("DEEPSEEK_MODEL_NAME", "deepseek-chat")
898
+ resolved_base_url = base_url or os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")
899
+ resolved_api_key = api_key or os.getenv("DEEPSEEK_API_KEY")
900
+ if not resolved_api_key:
901
+ raise RuntimeError("Missing DEEPSEEK_API_KEY for DeepSeek LLM judge.")
902
+ return OpenAI(api_key=resolved_api_key, base_url=resolved_base_url), resolved_model, provider
903
+ if provider == "openai":
904
+ resolved_model = model or os.getenv("OPENAI_MODEL_NAME", "gpt-5.1")
905
+ resolved_api_key = api_key or os.getenv("OPENAI_API_KEY")
906
+ if not resolved_api_key:
907
+ raise RuntimeError("Missing OPENAI_API_KEY for OpenAI LLM judge.")
908
+ return OpenAI(api_key=resolved_api_key), resolved_model, provider
909
+ raise ValueError(f"Unsupported LLM judge provider: {provider}")
910
+
911
+
912
+ def extract_json_object(text: str) -> dict:
913
+ text = text.strip()
914
+ if not text:
915
+ raise ValueError("Empty LLM judge response.")
916
+ try:
917
+ return json.loads(text)
918
+ except json.JSONDecodeError:
919
+ start = text.find("{")
920
+ end = text.rfind("}")
921
+ if start != -1 and end != -1 and end > start:
922
+ return json.loads(text[start : end + 1])
923
+ raise
924
+
925
+
926
+ def llm_trial_judge(
927
+ task_id: str,
928
+ task_prompt: str,
929
+ input_data_path: Path,
930
+ reference_data_path: Path,
931
+ result_summaries: list[dict],
932
+ truth_summaries: list[dict],
933
+ trace_evidence: dict,
934
+ artifacts: list[dict],
935
+ provider: str,
936
+ model: str | None,
937
+ base_url: str | None,
938
+ api_key: str | None,
939
+ ) -> dict:
940
+ client, resolved_model, resolved_provider = resolve_judge_client(provider, model, base_url, api_key)
941
+ config = TASK_CONFIGS[task_id]
942
+ compact_tree = "\n".join(trace_evidence["processing_tree"][:450])
943
+ path_mentions = "\n".join(trace_evidence["path_mentions_from_trace"][:180])
944
+
945
+ user_prompt = f"""
946
+ You are a strict, impartial Bioinformatics Pipeline Judge. Evaluate an LLM agent's work for executing a bioinformatics pipeline instructed by the prompt.
947
+
948
+ Inputs:
949
+ 1. Input data: {input_data_path}
950
+ 2. Reference data: {reference_data_path if reference_data_path.exists() else "<none>"}
951
+ 3. Processing tree:
952
+ {compact_tree}
953
+
954
+ Trace path mentions:
955
+ {path_mentions or "<none>"}
956
+
957
+ 4. Results:
958
+ {json.dumps(result_summaries, ensure_ascii=False, indent=2)}
959
+
960
+ 5. Truth:
961
+ {json.dumps(truth_summaries, ensure_ascii=False, indent=2)}
962
+
963
+ 6. Prompt:
964
+ {task_prompt}
965
+
966
+ Task expected pipeline steps:
967
+ {json.dumps(config["pipeline_steps"], ensure_ascii=False, indent=2)}
968
+
969
+ Auxiliary rule-based artifact metrics:
970
+ {json.dumps(artifacts, ensure_ascii=False, indent=2)}
971
+
972
+ Evaluation rules:
973
+ - Prioritize evaluation of pipeline completion over exact numerical correctness.
974
+ - If gene names are of different naming conventions, the result can still be valid.
975
+ - Estimate steps_to_completion from bioinformatics-relevant steps required for this task.
976
+ - Count upstream steps only if their expected artifacts are present.
977
+ - Do not count placeholders or mock completion as completed steps.
978
+ - For p-values, log-fold values, abundances, or other statistics, check that plausible prior steps generated them.
979
+ - Task-specific results-match guidance: {config.get("results_match_guidance", "")}
980
+
981
+ Return JSON only with exactly these fields:
982
+ - steps_completed: integer
983
+ - steps_to_completion: integer
984
+ - final_result_reached: boolean
985
+ - notes: string
986
+ - results_match: boolean
987
+ - f1_score: number or null; only use a real F1 for GIAB/variant concordance, otherwise null
988
+ """.strip()
989
+
990
+ response = client.chat.completions.create(
991
+ model=resolved_model,
992
+ temperature=0,
993
+ response_format={"type": "json_object"},
994
+ messages=[
995
+ {"role": "system", "content": "Return strict JSON only. Follow the BioAgent Bench EvaluationResults schema."},
996
+ {"role": "user", "content": user_prompt},
997
+ ],
998
+ )
999
+ parsed = extract_json_object(response.choices[0].message.content or "{}")
1000
+ parsed["provider"] = resolved_provider
1001
+ parsed["model"] = resolved_model
1002
+ return parsed
1003
+
1004
+
1005
+ def normalize_evaluation_results(raw: dict) -> dict:
1006
+ steps_completed = int(raw.get("steps_completed", 0) or 0)
1007
+ steps_to_completion = int(raw.get("steps_to_completion", 0) or 0)
1008
+ final_result_reached = bool(raw.get("final_result_reached", False))
1009
+ results_match = bool(raw.get("results_match", False))
1010
+ f1_score = raw.get("f1_score")
1011
+ if f1_score is not None:
1012
+ try:
1013
+ f1_score = float(f1_score)
1014
+ except Exception:
1015
+ f1_score = None
1016
+ completion_rate = steps_completed / steps_to_completion if steps_to_completion else 0.0
1017
+ return {
1018
+ "steps_completed": steps_completed,
1019
+ "steps_to_completion": steps_to_completion,
1020
+ "completion_rate": completion_rate,
1021
+ "final_result_reached": final_result_reached,
1022
+ "results_match": results_match,
1023
+ "f1_score": f1_score,
1024
+ "notes": str(raw.get("notes", "")),
1025
+ }
1026
+
1027
+
1028
+ def evaluate_task(
1029
+ task_id: str,
1030
+ run_dir: Path,
1031
+ dataset_root: Path,
1032
+ task_metadata: dict[str, dict],
1033
+ judge_mode: str = "rule",
1034
+ llm_provider: str = "deepseek",
1035
+ llm_model: str | None = None,
1036
+ llm_base_url: str | None = None,
1037
+ llm_api_key: str | None = None,
1038
+ ) -> dict:
1039
+ task_dir = dataset_root / task_id
1040
+ run_metadata = load_run_metadata(run_dir)
1041
+ task_prompt = (
1042
+ run_metadata.get("benchmark_task_context", {}).get("task_prompt")
1043
+ or task_metadata.get(task_id, {}).get("task_prompt")
1044
+ or run_metadata.get("query")
1045
+ or ""
1046
+ )
1047
+ trace_evidence = collect_trace_path_evidence(run_dir)
1048
+ artifacts = build_artifact_metrics(task_id, run_dir, dataset_root)
1049
+ result_summaries, truth_summaries = result_artifact_summaries(task_id, run_dir, dataset_root)
1050
+
1051
+ rule_raw = rule_trial_judge(task_id, artifacts, trace_evidence)
1052
+ rule_result = normalize_evaluation_results(rule_raw)
1053
+
1054
+ selected_raw = rule_raw
1055
+ llm_result = None
1056
+ if judge_mode in {"llm", "both"}:
1057
+ selected_raw = llm_trial_judge(
1058
+ task_id=task_id,
1059
+ task_prompt=task_prompt,
1060
+ input_data_path=task_dir / "data",
1061
+ reference_data_path=task_dir / "reference",
1062
+ result_summaries=result_summaries,
1063
+ truth_summaries=truth_summaries,
1064
+ trace_evidence=trace_evidence,
1065
+ artifacts=artifacts,
1066
+ provider=llm_provider,
1067
+ model=llm_model,
1068
+ base_url=llm_base_url,
1069
+ api_key=llm_api_key,
1070
+ )
1071
+ llm_result = normalize_evaluation_results(selected_raw)
1072
+
1073
+ selected = rule_result if judge_mode == "rule" else llm_result
1074
+ assert selected is not None
1075
+ return {
1076
+ "task_id": task_id,
1077
+ "run_dir": str(run_dir),
1078
+ "evaluated_at_utc": utc_timestamp(),
1079
+ "judge_mode": judge_mode,
1080
+ "evaluation_results": selected,
1081
+ "overall_score": selected["completion_rate"],
1082
+ "score_definition": (
1083
+ "BioAgent Bench-style completion rate: steps_completed / steps_to_completion. "
1084
+ "results_match and f1_score are reported separately."
1085
+ ),
1086
+ "rule_evaluation_results": rule_result,
1087
+ "llm_evaluation_results": llm_result,
1088
+ "artifacts": artifacts,
1089
+ "result_summaries": result_summaries,
1090
+ "truth_summaries": truth_summaries,
1091
+ "trace_evidence": trace_evidence,
1092
+ "paper_alignment": {
1093
+ "grader_inputs": [
1094
+ "input data path",
1095
+ "reference data path",
1096
+ "expected outcome/truth as text summary",
1097
+ "agent outcome as text summary",
1098
+ "agent trace represented as folders/file paths",
1099
+ "task prompt and grading logic",
1100
+ ],
1101
+ "grader_outputs": [
1102
+ "steps_completed",
1103
+ "steps_to_completion",
1104
+ "final_result_reached",
1105
+ "notes",
1106
+ "results_match",
1107
+ "f1_score",
1108
+ ],
1109
+ },
1110
+ }
1111
+
1112
+
1113
+ def parse_args() -> argparse.Namespace:
1114
+ parser = argparse.ArgumentParser(description="Evaluate Hypo_Bio_OS outputs on bioagent-bench.")
1115
+ parser.add_argument("--task", action="append", help="Task ID to evaluate. Can be provided multiple times.")
1116
+ parser.add_argument("--all", action="store_true", help="Evaluate all tasks that have run directories.")
1117
+ parser.add_argument("--run-dir", help="Specific run directory for single-task evaluation.")
1118
+ parser.add_argument("--runs-root", default=str(DEFAULT_RUNS_ROOT))
1119
+ parser.add_argument("--dataset-root", default=str(DATASET_ROOT))
1120
+ parser.add_argument("--metadata", default=str(METADATA_PATH))
1121
+ parser.add_argument("--output", default=None, help="Where to save the evaluation JSON.")
1122
+ parser.add_argument(
1123
+ "--judge-mode",
1124
+ choices=["rule", "llm", "both"],
1125
+ default="rule",
1126
+ help="Use local paper-shaped heuristic grading, LLM grading, or both.",
1127
+ )
1128
+ parser.add_argument(
1129
+ "--llm-provider",
1130
+ choices=["openai", "deepseek"],
1131
+ default=os.getenv("BIOAGENT_BENCH_JUDGE_PROVIDER", "deepseek"),
1132
+ help="OpenAI-compatible backend provider for the LLM judge.",
1133
+ )
1134
+ parser.add_argument("--llm-model", default=None, help="Override model name for the LLM judge.")
1135
+ parser.add_argument("--llm-base-url", default=None, help="Override base URL for the LLM judge.")
1136
+ parser.add_argument("--llm-api-key", default=None, help="Override API key for the LLM judge.")
1137
+ return parser.parse_args()
1138
+
1139
+
1140
+ def main() -> int:
1141
+ args = parse_args()
1142
+ dataset_root = Path(args.dataset_root)
1143
+ runs_root = Path(args.runs_root)
1144
+ task_metadata = load_task_metadata(Path(args.metadata))
1145
+
1146
+ if args.all:
1147
+ task_ids = list(TASK_CONFIGS)
1148
+ else:
1149
+ task_ids = args.task or []
1150
+ if not task_ids:
1151
+ raise SystemExit("Provide --task <task_id> or use --all.")
1152
+
1153
+ results = []
1154
+ for task_id in task_ids:
1155
+ if task_id not in TASK_CONFIGS:
1156
+ raise SystemExit(f"Unsupported task ID: {task_id}")
1157
+ if args.run_dir and len(task_ids) == 1:
1158
+ run_dir = Path(args.run_dir)
1159
+ else:
1160
+ run_dir = infer_latest_run_dir(task_id, runs_root)
1161
+ if run_dir is None:
1162
+ print(f"Skipping {task_id}: no run directory found under {runs_root}")
1163
+ continue
1164
+ result = evaluate_task(
1165
+ task_id=task_id,
1166
+ run_dir=run_dir,
1167
+ dataset_root=dataset_root,
1168
+ task_metadata=task_metadata,
1169
+ judge_mode=args.judge_mode,
1170
+ llm_provider=args.llm_provider,
1171
+ llm_model=args.llm_model,
1172
+ llm_base_url=args.llm_base_url,
1173
+ llm_api_key=args.llm_api_key,
1174
+ )
1175
+ results.append(result)
1176
+ print(json.dumps(result, ensure_ascii=False, indent=2))
1177
+
1178
+ completion_rates = [item["evaluation_results"]["completion_rate"] for item in results]
1179
+ payload = {
1180
+ "evaluated_at_utc": utc_timestamp(),
1181
+ "judge_mode": args.judge_mode,
1182
+ "primary_metric": "completion_rate",
1183
+ "mean_completion_rate": mean(completion_rates) if completion_rates else 0.0,
1184
+ "results": results,
1185
+ }
1186
+ if args.output:
1187
+ output_path = Path(args.output)
1188
+ else:
1189
+ task_eval_dir = runs_root / "evaluation_results"
1190
+ task_eval_dir.mkdir(parents=True, exist_ok=True)
1191
+ output_path = task_eval_dir / f"evaluation_{task_id}.json"
1192
+ output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
1193
+ print(f"Saved evaluation summary to: {output_path}")
1194
+ return 0
1195
+
1196
+
1197
+ if __name__ == "__main__":
1198
+ raise SystemExit(main())
experiments/bioagent_bench/gold_tools.json ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alzheimer-mouse": {
3
+ "task_name": "Alzheimer Mouse Models: Comparative Pathway Analysis",
4
+ "gold_tools": [
5
+ "DESeq2",
6
+ "edgeR",
7
+ "limma",
8
+ "clusterProfiler",
9
+ "KEGGREST"
10
+ ],
11
+ "gold_servers": [
12
+ "mcp_bioconductor-deseq2",
13
+ "mcp_bioconductor-edger",
14
+ "mcp_bioconductor-limma",
15
+ "mcp_bioconductor-clusterprofiler",
16
+ "mcp_bioconductor-keggrest"
17
+ ],
18
+ "workflow": [
19
+ {
20
+ "name": "load_mouse_count_tables",
21
+ "tool_patterns": [
22
+ "read",
23
+ "csv",
24
+ "count",
25
+ "pandas",
26
+ "data.table"
27
+ ]
28
+ },
29
+ {
30
+ "name": "normalize_and_model_expression",
31
+ "tool_patterns": [
32
+ "deseq",
33
+ "edger",
34
+ "limma",
35
+ "normalize"
36
+ ]
37
+ },
38
+ {
39
+ "name": "run_kegg_pathway_enrichment",
40
+ "tool_patterns": [
41
+ "clusterprofiler",
42
+ "kegg",
43
+ "enrich",
44
+ "pathway"
45
+ ]
46
+ },
47
+ {
48
+ "name": "compare_shared_pathways",
49
+ "tool_patterns": [
50
+ "merge",
51
+ "join",
52
+ "shared",
53
+ "csv",
54
+ "pandas"
55
+ ]
56
+ }
57
+ ]
58
+ },
59
+ "comparative-genomics": {
60
+ "task_name": "Comparative Genomics: Co-evolving Gene Clusters",
61
+ "gold_tools": [
62
+ "OrthoFinder",
63
+ "BLAST",
64
+ "DIAMOND",
65
+ "gffread",
66
+ "seqkit"
67
+ ],
68
+ "gold_servers": [
69
+ "mcp_orthofinder",
70
+ "mcp_blast",
71
+ "mcp_diamond",
72
+ "mcp_gffread",
73
+ "mcp_seqkit"
74
+ ],
75
+ "workflow": [
76
+ {
77
+ "name": "extract_or_prepare_proteomes",
78
+ "tool_patterns": [
79
+ "gffread",
80
+ "seqkit",
81
+ "fasta",
82
+ "faa"
83
+ ]
84
+ },
85
+ {
86
+ "name": "infer_orthogroups",
87
+ "tool_patterns": [
88
+ "orthofinder",
89
+ "orthogroup",
90
+ "blast",
91
+ "diamond"
92
+ ]
93
+ },
94
+ {
95
+ "name": "filter_core_coding_clusters",
96
+ "tool_patterns": [
97
+ "core",
98
+ "present",
99
+ "coding",
100
+ "orthogroup",
101
+ "pandas"
102
+ ]
103
+ },
104
+ {
105
+ "name": "assign_consensus_annotations",
106
+ "tool_patterns": [
107
+ "blast",
108
+ "diamond",
109
+ "kegg",
110
+ "annotation",
111
+ "consensus"
112
+ ]
113
+ }
114
+ ]
115
+ },
116
+ "cystic-fibrosis": {
117
+ "task_name": "Cystic Fibrosis Mendelian Variant Identification",
118
+ "gold_tools": [
119
+ "BWA",
120
+ "samtools",
121
+ "bcftools",
122
+ "GATK",
123
+ "SnpEff",
124
+ "SnpSift"
125
+ ],
126
+ "gold_servers": [
127
+ "mcp_bwa",
128
+ "mcp_samtools",
129
+ "mcp_bcftools",
130
+ "mcp_gatk4",
131
+ "mcp_snpeff",
132
+ "mcp_snpsift"
133
+ ],
134
+ "workflow": [
135
+ {
136
+ "name": "prepare_family_variant_data",
137
+ "tool_patterns": [
138
+ "bwa",
139
+ "samtools",
140
+ "bam",
141
+ "vcf"
142
+ ]
143
+ },
144
+ {
145
+ "name": "call_or_filter_variants",
146
+ "tool_patterns": [
147
+ "bcftools",
148
+ "gatk",
149
+ "variant",
150
+ "filter"
151
+ ]
152
+ },
153
+ {
154
+ "name": "annotate_with_effect_and_clinvar",
155
+ "tool_patterns": [
156
+ "snpeff",
157
+ "snpsift",
158
+ "clinvar",
159
+ "annotation"
160
+ ]
161
+ },
162
+ {
163
+ "name": "apply_recessive_family_filter",
164
+ "tool_patterns": [
165
+ "recessive",
166
+ "affected",
167
+ "family",
168
+ "genotype",
169
+ "pandas"
170
+ ]
171
+ }
172
+ ]
173
+ },
174
+ "deseq": {
175
+ "task_name": "RNA-Seq Differential Expression (DESeq2)",
176
+ "gold_tools": [
177
+ "STAR",
178
+ "HISAT2",
179
+ "featureCounts",
180
+ "Subread",
181
+ "DESeq2",
182
+ "tximport"
183
+ ],
184
+ "gold_servers": [
185
+ "mcp_star",
186
+ "mcp_hisat2",
187
+ "mcp_subread",
188
+ "mcp_bioconductor-rsubread",
189
+ "mcp_bioconductor-deseq2",
190
+ "mcp_bioconductor-tximport"
191
+ ],
192
+ "workflow": [
193
+ {
194
+ "name": "align_rnaseq_reads",
195
+ "tool_patterns": [
196
+ "star",
197
+ "hisat2",
198
+ "align",
199
+ "bam"
200
+ ]
201
+ },
202
+ {
203
+ "name": "count_gene_features",
204
+ "tool_patterns": [
205
+ "featurecounts",
206
+ "subread",
207
+ "count"
208
+ ]
209
+ },
210
+ {
211
+ "name": "differential_expression",
212
+ "tool_patterns": [
213
+ "deseq",
214
+ "edger",
215
+ "limma",
216
+ "log2foldchange"
217
+ ]
218
+ },
219
+ {
220
+ "name": "write_de_results_csv",
221
+ "tool_patterns": [
222
+ "csv",
223
+ "gene_id",
224
+ "padj",
225
+ "pvalue"
226
+ ]
227
+ }
228
+ ]
229
+ },
230
+ "evolution": {
231
+ "task_name": "Experimental Evolution Variant Calling (E. coli)",
232
+ "gold_tools": [
233
+ "BWA",
234
+ "samtools",
235
+ "bcftools",
236
+ "FreeBayes",
237
+ "SnpEff",
238
+ "SnpSift"
239
+ ],
240
+ "gold_servers": [
241
+ "mcp_bwa",
242
+ "mcp_samtools",
243
+ "mcp_bcftools",
244
+ "mcp_freebayes",
245
+ "mcp_snpeff",
246
+ "mcp_snpsift"
247
+ ],
248
+ "workflow": [
249
+ {
250
+ "name": "align_evolved_and_ancestor_reads",
251
+ "tool_patterns": [
252
+ "bwa",
253
+ "samtools",
254
+ "align",
255
+ "bam"
256
+ ]
257
+ },
258
+ {
259
+ "name": "call_variants_per_line",
260
+ "tool_patterns": [
261
+ "bcftools",
262
+ "freebayes",
263
+ "variant",
264
+ "vcf"
265
+ ]
266
+ },
267
+ {
268
+ "name": "annotate_variant_effects",
269
+ "tool_patterns": [
270
+ "snpeff",
271
+ "effect",
272
+ "impact",
273
+ "moderate"
274
+ ]
275
+ },
276
+ {
277
+ "name": "intersect_shared_variants",
278
+ "tool_patterns": [
279
+ "shared",
280
+ "intersect",
281
+ "evolved",
282
+ "pandas"
283
+ ]
284
+ }
285
+ ]
286
+ },
287
+ "giab": {
288
+ "task_name": "GIAB Variant Calling",
289
+ "gold_tools": [
290
+ "BWA",
291
+ "samtools",
292
+ "bcftools",
293
+ "GATK",
294
+ "FreeBayes",
295
+ "VariantAnnotation"
296
+ ],
297
+ "gold_servers": [
298
+ "mcp_bwa",
299
+ "mcp_samtools",
300
+ "mcp_bcftools",
301
+ "mcp_gatk4",
302
+ "mcp_freebayes",
303
+ "mcp_bioconductor-variantannotation"
304
+ ],
305
+ "workflow": [
306
+ {
307
+ "name": "align_exome_reads",
308
+ "tool_patterns": [
309
+ "bwa",
310
+ "samtools",
311
+ "align",
312
+ "bam"
313
+ ]
314
+ },
315
+ {
316
+ "name": "sort_index_and_qc_bam",
317
+ "tool_patterns": [
318
+ "samtools",
319
+ "sort",
320
+ "index",
321
+ "flagstat"
322
+ ]
323
+ },
324
+ {
325
+ "name": "call_germline_variants",
326
+ "tool_patterns": [
327
+ "gatk",
328
+ "bcftools",
329
+ "freebayes",
330
+ "haplotypecaller"
331
+ ]
332
+ },
333
+ {
334
+ "name": "compress_and_index_vcf",
335
+ "tool_patterns": [
336
+ "vcf",
337
+ "bgzip",
338
+ "tabix",
339
+ "index"
340
+ ]
341
+ }
342
+ ]
343
+ },
344
+ "metagenomics": {
345
+ "task_name": "Metagenomics: Community Comparison (Cuatro Cienegas)",
346
+ "gold_tools": [
347
+ "Kraken2",
348
+ "csvtk",
349
+ "pandas"
350
+ ],
351
+ "gold_servers": [
352
+ "mcp_kraken2",
353
+ "mcp_csvtk"
354
+ ],
355
+ "workflow": [
356
+ {
357
+ "name": "classify_paired_reads",
358
+ "tool_patterns": [
359
+ "kraken2",
360
+ "classify",
361
+ "paired",
362
+ "fastq"
363
+ ]
364
+ },
365
+ {
366
+ "name": "parse_kraken_reports",
367
+ "tool_patterns": [
368
+ "report",
369
+ "taxonomy",
370
+ "taxid",
371
+ "phylum"
372
+ ]
373
+ },
374
+ {
375
+ "name": "compute_bacterial_phylum_relative_abundance",
376
+ "tool_patterns": [
377
+ "bacteria",
378
+ "phylum",
379
+ "relative",
380
+ "abundance"
381
+ ]
382
+ },
383
+ {
384
+ "name": "write_phylum_abundance_csv",
385
+ "tool_patterns": [
386
+ "csv",
387
+ "OTU",
388
+ "Kingdom",
389
+ "Phylum",
390
+ "JP4D",
391
+ "JC1A"
392
+ ]
393
+ }
394
+ ]
395
+ },
396
+ "single-cell": {
397
+ "task_name": "Single-cell RNA-seq: Skeletal Muscle Exercise Response",
398
+ "gold_tools": [
399
+ "Scanpy",
400
+ "Seurat",
401
+ "CellTypist"
402
+ ],
403
+ "gold_servers": [
404
+ "mcp_scanpy",
405
+ "mcp_scanpy-cli",
406
+ "mcp_scanpy-scripts",
407
+ "mcp_seurat-scripts",
408
+ "mcp_celltypist"
409
+ ],
410
+ "workflow": [
411
+ {
412
+ "name": "load_single_cell_matrix",
413
+ "tool_patterns": [
414
+ "scanpy",
415
+ "read_h5ad",
416
+ "h5ad",
417
+ "matrix"
418
+ ]
419
+ },
420
+ {
421
+ "name": "normalize_cluster_and_embed",
422
+ "tool_patterns": [
423
+ "normalize",
424
+ "neighbors",
425
+ "leiden",
426
+ "umap",
427
+ "scanpy"
428
+ ]
429
+ },
430
+ {
431
+ "name": "annotate_cell_types",
432
+ "tool_patterns": [
433
+ "celltypist",
434
+ "marker",
435
+ "annotation",
436
+ "cell_type"
437
+ ]
438
+ },
439
+ {
440
+ "name": "differential_expression_by_condition",
441
+ "tool_patterns": [
442
+ "rank_genes",
443
+ "differential",
444
+ "exercise",
445
+ "wilcoxon"
446
+ ]
447
+ }
448
+ ]
449
+ },
450
+ "transcript-quant": {
451
+ "task_name": "Transcript Quantification (Simulated RNA-Seq)",
452
+ "gold_tools": [
453
+ "Kallisto",
454
+ "Salmon"
455
+ ],
456
+ "gold_servers": [
457
+ "mcp_kallisto",
458
+ "mcp_salmon"
459
+ ],
460
+ "workflow": [
461
+ {
462
+ "name": "build_transcriptome_index",
463
+ "tool_patterns": [
464
+ "kallisto_index",
465
+ "salmon",
466
+ "index",
467
+ "transcriptome"
468
+ ]
469
+ },
470
+ {
471
+ "name": "quantify_paired_end_reads",
472
+ "tool_patterns": [
473
+ "kallisto_quant",
474
+ "salmon",
475
+ "quant",
476
+ "paired"
477
+ ]
478
+ },
479
+ {
480
+ "name": "extract_transcript_counts",
481
+ "tool_patterns": [
482
+ "abundance",
483
+ "est_counts",
484
+ "count",
485
+ "transcript_id"
486
+ ]
487
+ },
488
+ {
489
+ "name": "write_no_header_tsv",
490
+ "tool_patterns": [
491
+ "tsv",
492
+ "transcript_id",
493
+ "count"
494
+ ]
495
+ }
496
+ ]
497
+ },
498
+ "viral-metagenomics": {
499
+ "task_name": "Viral Metagenomics: Species Identification (Dolphin)",
500
+ "gold_tools": [
501
+ "MEGAHIT",
502
+ "SPAdes",
503
+ "Kaiju",
504
+ "fastp",
505
+ "BLAST"
506
+ ],
507
+ "gold_servers": [
508
+ "mcp_megahit",
509
+ "mcp_spades",
510
+ "mcp_kaiju",
511
+ "mcp_fastp",
512
+ "mcp_blast"
513
+ ],
514
+ "workflow": [
515
+ {
516
+ "name": "assemble_viral_metagenomic_contigs",
517
+ "tool_patterns": [
518
+ "megahit",
519
+ "spades",
520
+ "assembly",
521
+ "contig"
522
+ ]
523
+ },
524
+ {
525
+ "name": "classify_contigs_taxonomically",
526
+ "tool_patterns": [
527
+ "kaiju",
528
+ "blast",
529
+ "taxonomy",
530
+ "virus"
531
+ ]
532
+ },
533
+ {
534
+ "name": "resolve_taxon_names",
535
+ "tool_patterns": [
536
+ "names",
537
+ "nodes",
538
+ "taxon",
539
+ "species"
540
+ ]
541
+ },
542
+ {
543
+ "name": "summarize_contig_counts_by_species",
544
+ "tool_patterns": [
545
+ "species",
546
+ "contig_count",
547
+ "domain",
548
+ "csv"
549
+ ]
550
+ }
551
+ ]
552
+ }
553
+ }
experiments/lab_bench/README_RUN.md ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LAB-Bench Biomni 实验新手运行指南
2
+
3
+ 这份说明用于运行 Biomni 在 LAB-Bench 上的实验,并且数据集设置要和
4
+ `/225040511/project/Hypo_Bio_OS/experiments/lab_bench/scripts` 里的 HypoBioOS
5
+ 实验保持一致。
6
+
7
+ ```bash
8
+ /225040511/project/Biomni/experiments/lab_bench
9
+ ```
10
+
11
+ LAB-Bench 数据和代码默认位于:
12
+
13
+ ```bash
14
+ /225040511/project/LAB-Bench
15
+ ```
16
+
17
+ ## 1. 进入 Biomni 项目
18
+
19
+ ```bash
20
+ cd /225040511/project/Biomni
21
+ ```
22
+
23
+ ## 2. 本实验跑什么数据
24
+
25
+ 不要跑 LAB-Bench 全量。为了和 HypoBioOS 对照实验一致,本实验只跑:
26
+
27
+ - `DbQA`
28
+ - `SeqQA`
29
+
30
+ 默认数据设置:
31
+
32
+ - split: `test`
33
+ - dev offset: `45`
34
+ - test size: `315`
35
+ - seed: `20260514`
36
+ - shard count: `10`
37
+ - runner threads: `1`
38
+
39
+ 对应 HypoBioOS 脚本里的默认环境变量:
40
+
41
+ ```bash
42
+ LAB_BENCH_SPLITS=test
43
+ LAB_BENCH_DEV_SIZE=45
44
+ LAB_BENCH_TEST_SIZE=315
45
+ LAB_BENCH_SUBSET_SEED=20260514
46
+ LAB_BENCH_TEST_SHARD_COUNT=10
47
+ LAB_BENCH_N_THREADS=1
48
+ ```
49
+
50
+ Biomni 这边的 `run_all_labbench_with_biomni.sh` 已经改成只代理 DbQA + SeqQA 子集,不会启动 LAB-Bench 全量 eval。
51
+
52
+ ## 3. 挂 DeepSeek 后端
53
+
54
+ 推荐把 DeepSeek key 写到 Biomni 项目根目录的 `.env`:
55
+
56
+ ```bash
57
+ cat > /225040511/project/Biomni/.env <<'EOF'
58
+ DEEPSEEK_API_KEY=你的 DeepSeek API Key
59
+ DEEPSEEK_MODEL_NAME=deepseek-chat
60
+ DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
61
+ EOF
62
+ ```
63
+
64
+ 也可以只在当前 shell 里临时设置:
65
+
66
+ ```bash
67
+ export DEEPSEEK_API_KEY="你的 DeepSeek API Key"
68
+ export DEEPSEEK_MODEL_NAME="deepseek-chat"
69
+ export DEEPSEEK_BASE_URL="https://api.deepseek.com/v1"
70
+ ```
71
+
72
+ 实验脚本会自动把 DeepSeek 变量转换成 Biomni 需要的 Custom provider 配置:
73
+
74
+ ```bash
75
+ BIOMNI_SOURCE=Custom
76
+ BIOMNI_LLM=$DEEPSEEK_MODEL_NAME
77
+ BIOMNI_CUSTOM_BASE_URL=$DEEPSEEK_BASE_URL
78
+ BIOMNI_CUSTOM_API_KEY=$DEEPSEEK_API_KEY
79
+ ```
80
+
81
+ 可以用下面命令确认 key 是否已设置,不会打印 key 内容:
82
+
83
+ ```bash
84
+ for k in DEEPSEEK_API_KEY BIOMNI_CUSTOM_API_KEY BIOMNI_SOURCE BIOMNI_LLM BIOMNI_CUSTOM_BASE_URL; do
85
+ if [ -n "${!k:-}" ]; then echo "$k=set"; else echo "$k=unset"; fi
86
+ done
87
+ ```
88
+
89
+ 如果你把 key 写进 `.env`,上面的检查命令在手动 `source .env` 之前可能显示 unset,这是正常的;运行脚本时会自动读取 `/225040511/project/Biomni/.env` 和 `/225040511/project/LAB-Bench/.env`。
90
+
91
+ ## 4. Python 环境
92
+
93
+ 默认使用:
94
+
95
+ ```bash
96
+ /225040511/miniconda3/envs/biomni_e1/bin/python
97
+ ```
98
+
99
+ 如果要换 Python,可以设置:
100
+
101
+ ```bash
102
+ export LAB_BENCH_RUNNER_PYTHON=/path/to/python
103
+ ```
104
+
105
+ ## 5. 快速跑一个小样本
106
+
107
+ 先用 debug 模式验证 DeepSeek、Biomni 和 LAB-Bench 都能跑通:
108
+
109
+ ```bash
110
+ /225040511/miniconda3/envs/biomni_e1/bin/python \
111
+ experiments/lab_bench/run_labbench_with_biomni.py \
112
+ --eval DbQA \
113
+ --split test \
114
+ --debug \
115
+ --output experiments/lab_bench/results/debug_dbqa.json
116
+ ```
117
+
118
+ 成功后会生成:
119
+
120
+ ```bash
121
+ experiments/lab_bench/results/debug_dbqa.json
122
+ ```
123
+
124
+ ## 6. 正式运行 DbQA 和 SeqQA
125
+
126
+ 推荐使用分片脚本。它们默认后台运行、默认断点续跑,并把结果追加到 JSONL。
127
+
128
+ 同时跑 DbQA + SeqQA:
129
+
130
+ ```bash
131
+ export LAB_BENCH_SPLITS=test
132
+ export LAB_BENCH_DEV_SIZE=45
133
+ export LAB_BENCH_TEST_SIZE=315
134
+ export LAB_BENCH_SUBSET_SEED=20260514
135
+ export LAB_BENCH_TEST_SHARD_COUNT=10
136
+ experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh --background --resume
137
+ ```
138
+
139
+ 只跑 DbQA:
140
+
141
+ ```bash
142
+ experiments/lab_bench/run_dbqa_biomni_experiment.sh --background --resume
143
+ ```
144
+
145
+ 只跑 SeqQA:
146
+
147
+ ```bash
148
+ experiments/lab_bench/run_seqqa_biomni_experiment.sh --background --resume
149
+ ```
150
+
151
+ 默认每个 eval 分成 10 个 shard 并行跑。可以调整 shard 数:
152
+
153
+ ```bash
154
+ export LAB_BENCH_TEST_SHARD_COUNT=5
155
+ experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh --background --resume
156
+ ```
157
+
158
+ ## 7. 前台调试
159
+
160
+ 如果想直接在终端看到错误,使用前台模式:
161
+
162
+ ```bash
163
+ experiments/lab_bench/run_dbqa_biomni_experiment.sh --foreground --resume
164
+ ```
165
+
166
+ 或者:
167
+
168
+ ```bash
169
+ experiments/lab_bench/run_seqqa_biomni_experiment.sh --foreground --resume
170
+ ```
171
+
172
+ 前台模式会占住当前终端,适合调试;长时间正式实验建议用后台模式。
173
+
174
+ ## 8. 结果位置
175
+
176
+ DbQA 分片结果:
177
+
178
+ ```bash
179
+ experiments/lab_bench/results/dbqa_batch/dbqa_results.jsonl
180
+ experiments/lab_bench/results/dbqa_batch/dbqa_reasoning.log
181
+ ```
182
+
183
+ SeqQA 分片结果:
184
+
185
+ ```bash
186
+ experiments/lab_bench/results/seqqa_batch/seqqa_results.jsonl
187
+ experiments/lab_bench/results/seqqa_batch/seqqa_reasoning.log
188
+ ```
189
+
190
+ `*_results.jsonl` 每行是一道题的紧凑结果,包含:
191
+
192
+ - `question`
193
+ - `answer`
194
+ - `agent_answer`
195
+
196
+ `*_reasoning.log` 保存每题的 prompt、raw output 和 Biomni reasoning log,方便排查错误。
197
+
198
+ ## 9. 断点续跑和重新开始
199
+
200
+ 默认推荐断点续跑:
201
+
202
+ ```bash
203
+ experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh --background --resume
204
+ ```
205
+
206
+ 脚本会跳过已经存在于 `*_results.jsonl` 里的问题。
207
+
208
+ 如果���清空旧结果重新跑:
209
+
210
+ ```bash
211
+ experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh --background --fresh
212
+ ```
213
+
214
+ ## 10. 常用参数
215
+
216
+ 切换 split。为了和 HypoBioOS 正式实验一致,默认用 `test`:
217
+
218
+ ```bash
219
+ export LAB_BENCH_SPLITS=test
220
+ ```
221
+
222
+ Biomni 分片脚本一次只支持一个 split。如果设置成 `dev test`,脚本会退出,避免和 HypoBioOS 默认测试集设置混淆。
223
+
224
+ 调整 dev/test 数量:
225
+
226
+ ```bash
227
+ export LAB_BENCH_DEV_SIZE=45
228
+ export LAB_BENCH_TEST_SIZE=315
229
+ ```
230
+
231
+ 固定抽样种子:
232
+
233
+ ```bash
234
+ export LAB_BENCH_SUBSET_SEED=20260514
235
+ ```
236
+
237
+ Biomni A1 runner 是有状态的,脚本默认:
238
+
239
+ ```bash
240
+ LAB_BENCH_N_THREADS=1
241
+ ```
242
+
243
+ 不建议改成更大的值。
244
+
245
+ ## 11. 检查是否在跑
246
+
247
+ 查看后台进程:
248
+
249
+ ```bash
250
+ ps -ef | grep lab_bench | grep -v grep
251
+ ```
252
+
253
+ 查看结果是否持续增长:
254
+
255
+ ```bash
256
+ wc -l experiments/lab_bench/results/dbqa_batch/dbqa_results.jsonl
257
+ wc -l experiments/lab_bench/results/seqqa_batch/seqqa_results.jsonl
258
+ ```
259
+
260
+ 查看最近 reasoning log:
261
+
262
+ ```bash
263
+ tail -80 experiments/lab_bench/results/dbqa_batch/dbqa_reasoning.log
264
+ tail -80 experiments/lab_bench/results/seqqa_batch/seqqa_reasoning.log
265
+ ```
266
+
267
+ ## 12. 常见报错
268
+
269
+ 如果看到:
270
+
271
+ ```text
272
+ No LLM API key found.
273
+ ```
274
+
275
+ 说明脚本没有读到 DeepSeek key。检查:
276
+
277
+ ```bash
278
+ ls -lh /225040511/project/Biomni/.env
279
+ ```
280
+
281
+ 并确认 `.env` 里至少有:
282
+
283
+ ```bash
284
+ DEEPSEEK_API_KEY=你的 DeepSeek API Key
285
+ DEEPSEEK_MODEL_NAME=deepseek-chat
286
+ DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
287
+ ```
288
+
289
+ 如果使用临时环境变量,确认你是在同一个 shell 里先 `export`,再启动脚本。
experiments/lab_bench/__pycache__/summarize_labbench_repeats.cpython-310.pyc ADDED
Binary file (2.36 kB). View file
 
experiments/lab_bench/__pycache__/summarize_labbench_repeats.cpython-313.pyc ADDED
Binary file (3.58 kB). View file
 
experiments/lab_bench/run_all_labbench_with_biomni.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ echo "This experiment is aligned with Hypo_Bio_OS: DbQA + SeqQA only, default split=test."
7
+ echo "Delegating to run_dbqa_seqqa_biomni_experiment.sh; no full LAB-Bench run will be launched."
8
+
9
+ "${SCRIPT_DIR}/run_dbqa_seqqa_biomni_experiment.sh" "$@"
experiments/lab_bench/run_dbqa_biomni_experiment.sh ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BIOMNI_ROOT="/225040511/project/Biomni"
5
+ LAB_BENCH_ROOT="/225040511/project/LAB-Bench"
6
+ RUNNER="${BIOMNI_ROOT}/experiments/lab_bench/run_labbench_with_biomni.py"
7
+ RUNNER_PYTHON="${LAB_BENCH_RUNNER_PYTHON:-${BIOMNI_EXECUTION_PYTHON:-/225040511/miniconda3/envs/biomni_e1/bin/python}}"
8
+
9
+ OUTPUT_ROOT="${BIOMNI_ROOT}/experiments/lab_bench/results/dbqa_batch"
10
+ RESULT_FILE="${OUTPUT_ROOT}/dbqa_results.jsonl"
11
+ REASONING_LOG="${OUTPUT_ROOT}/dbqa_reasoning.log"
12
+ EVAL_NAME="DbQA"
13
+
14
+ DEV_SIZE="${LAB_BENCH_DEV_SIZE:-45}"
15
+ TEST_SIZE="${LAB_BENCH_TEST_SIZE:-315}"
16
+ SEED="${LAB_BENCH_SUBSET_SEED:-20260514}"
17
+ N_THREADS="${LAB_BENCH_N_THREADS:-1}"
18
+ TEST_SHARD_COUNT="${LAB_BENCH_TEST_SHARD_COUNT:-10}"
19
+ SPLITS_TEXT="${LAB_BENCH_SPLITS:-${LAB_BENCH_SPLIT:-test}}"
20
+
21
+ BACKGROUND=1
22
+ RESUME=1
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --foreground)
26
+ BACKGROUND=0
27
+ shift
28
+ ;;
29
+ --background)
30
+ BACKGROUND=1
31
+ shift
32
+ ;;
33
+ --fresh)
34
+ RESUME=0
35
+ shift
36
+ ;;
37
+ --resume)
38
+ RESUME=1
39
+ shift
40
+ ;;
41
+ *)
42
+ exit 2
43
+ ;;
44
+ esac
45
+ done
46
+
47
+ if [[ "${BACKGROUND}" -eq 1 ]]; then
48
+ SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
49
+ FORWARD_ARGS=(--foreground)
50
+ if [[ "${RESUME}" -eq 0 ]]; then
51
+ FORWARD_ARGS+=(--fresh)
52
+ else
53
+ FORWARD_ARGS+=(--resume)
54
+ fi
55
+ nohup "${SCRIPT_ABS_PATH}" "${FORWARD_ARGS[@]}" > /dev/null 2>&1 < /dev/null &
56
+ exit 0
57
+ fi
58
+
59
+ mkdir -p "${OUTPUT_ROOT}"
60
+ if [[ "${RESUME}" -eq 0 ]]; then
61
+ : > "${RESULT_FILE}"
62
+ : > "${REASONING_LOG}"
63
+ else
64
+ touch "${RESULT_FILE}" "${REASONING_LOG}"
65
+ fi
66
+
67
+ for ENV_FILE in "${BIOMNI_ROOT}/.env" "${LAB_BENCH_ROOT}/.env"; do
68
+ if [[ -f "${ENV_FILE}" ]]; then
69
+ set -a
70
+ # shellcheck disable=SC1090
71
+ source "${ENV_FILE}"
72
+ set +a
73
+ fi
74
+ done
75
+
76
+ if [[ -n "${DEEPSEEK_API_KEY:-}" && -z "${BIOMNI_CUSTOM_API_KEY:-}" ]]; then
77
+ export BIOMNI_SOURCE="${BIOMNI_SOURCE:-Custom}"
78
+ export BIOMNI_LLM="${BIOMNI_LLM:-${DEEPSEEK_MODEL_NAME:-deepseek-chat}}"
79
+ export BIOMNI_CUSTOM_BASE_URL="${BIOMNI_CUSTOM_BASE_URL:-${DEEPSEEK_BASE_URL:-https://api.deepseek.com/v1}}"
80
+ export BIOMNI_CUSTOM_API_KEY="${DEEPSEEK_API_KEY}"
81
+ fi
82
+
83
+ if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${OPENAI_API_KEY:-}" && -z "${BIOMNI_CUSTOM_API_KEY:-}" ]]; then
84
+ printf "[launcher] missing LLM API key environment variables; batch did not start\n" >> "${REASONING_LOG}"
85
+ exit 2
86
+ fi
87
+
88
+ read -r -a SPLIT_ARRAY <<< "${SPLITS_TEXT}"
89
+ if [[ "${#SPLIT_ARRAY[@]}" -ne 1 ]]; then
90
+ printf "[launcher] Biomni LAB-Bench runner supports one split per batch; got LAB_BENCH_SPLITS='%s'\n" "${SPLITS_TEXT}" >> "${REASONING_LOG}"
91
+ exit 2
92
+ fi
93
+ SPLIT="${SPLIT_ARRAY[0]}"
94
+
95
+ declare -a CHILD_PIDS=()
96
+
97
+ launch_shard() {
98
+ local shard_index="$1"
99
+ local run_label="dbqa_shard$(printf '%02d' "$((shard_index + 1))")of$(printf '%02d' "${TEST_SHARD_COUNT}")"
100
+ local -a cmd=(
101
+ "${RUNNER_PYTHON}" "${RUNNER}"
102
+ "--eval" "${EVAL_NAME}"
103
+ "--split" "${SPLIT}"
104
+ "--dev-size" "${DEV_SIZE}"
105
+ "--test-size" "${TEST_SIZE}"
106
+ "--seed" "${SEED}"
107
+ "--repeat" "1"
108
+ "--n_threads" "${N_THREADS}"
109
+ "--output-root" "${OUTPUT_ROOT}"
110
+ "--agent-root" "${BIOMNI_ROOT}/experiments/lab_bench/agent_runtime"
111
+ "--run-label" "${run_label}"
112
+ "--shard-index" "${shard_index}"
113
+ "--shard-count" "${TEST_SHARD_COUNT}"
114
+ "--reasoning-log-path" "${REASONING_LOG}"
115
+ "--compact-results-path" "${RESULT_FILE}"
116
+ "--compact-output-only"
117
+ "--skip-existing-results"
118
+ )
119
+ "${cmd[@]}" > /dev/null 2>&1 &
120
+ CHILD_PIDS+=("$!")
121
+ }
122
+
123
+ for (( shard_index=0; shard_index<TEST_SHARD_COUNT; shard_index++ )); do
124
+ launch_shard "${shard_index}"
125
+ done
126
+
127
+ for pid in "${CHILD_PIDS[@]}"; do
128
+ wait "${pid}"
129
+ done
experiments/lab_bench/run_dbqa_seqqa_biomni_experiment.sh ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BIOMNI_ROOT="/225040511/project/Biomni"
5
+ DBQA_SCRIPT="${BIOMNI_ROOT}/experiments/lab_bench/run_dbqa_biomni_experiment.sh"
6
+ SEQQA_SCRIPT="${BIOMNI_ROOT}/experiments/lab_bench/run_seqqa_biomni_experiment.sh"
7
+
8
+ BACKGROUND=1
9
+ EXTRA_ARGS=()
10
+ while [[ $# -gt 0 ]]; do
11
+ case "$1" in
12
+ --foreground)
13
+ BACKGROUND=0
14
+ EXTRA_ARGS+=("$1")
15
+ shift
16
+ ;;
17
+ --background)
18
+ shift
19
+ ;;
20
+ --fresh|--resume)
21
+ EXTRA_ARGS+=("$1")
22
+ shift
23
+ ;;
24
+ *)
25
+ exit 2
26
+ ;;
27
+ esac
28
+ done
29
+
30
+ if [[ "${BACKGROUND}" -eq 1 ]]; then
31
+ SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
32
+ nohup "${SCRIPT_ABS_PATH}" --foreground "${EXTRA_ARGS[@]}" > /dev/null 2>&1 < /dev/null &
33
+ exit 0
34
+ fi
35
+
36
+ "${DBQA_SCRIPT}" "${EXTRA_ARGS[@]}"
37
+ "${SEQQA_SCRIPT}" "${EXTRA_ARGS[@]}"
experiments/lab_bench/run_seqqa_biomni_experiment.sh ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BIOMNI_ROOT="/225040511/project/Biomni"
5
+ LAB_BENCH_ROOT="/225040511/project/LAB-Bench"
6
+ RUNNER="${BIOMNI_ROOT}/experiments/lab_bench/run_labbench_with_biomni.py"
7
+ RUNNER_PYTHON="${LAB_BENCH_RUNNER_PYTHON:-${BIOMNI_EXECUTION_PYTHON:-/225040511/miniconda3/envs/biomni_e1/bin/python}}"
8
+
9
+ OUTPUT_ROOT="${BIOMNI_ROOT}/experiments/lab_bench/results/seqqa_batch"
10
+ RESULT_FILE="${OUTPUT_ROOT}/seqqa_results.jsonl"
11
+ REASONING_LOG="${OUTPUT_ROOT}/seqqa_reasoning.log"
12
+ EVAL_NAME="SeqQA"
13
+
14
+ DEV_SIZE="${LAB_BENCH_DEV_SIZE:-45}"
15
+ TEST_SIZE="${LAB_BENCH_TEST_SIZE:-315}"
16
+ SEED="${LAB_BENCH_SUBSET_SEED:-20260514}"
17
+ N_THREADS="${LAB_BENCH_N_THREADS:-1}"
18
+ TEST_SHARD_COUNT="${LAB_BENCH_TEST_SHARD_COUNT:-10}"
19
+ SPLITS_TEXT="${LAB_BENCH_SPLITS:-${LAB_BENCH_SPLIT:-test}}"
20
+
21
+ BACKGROUND=1
22
+ RESUME=1
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --foreground)
26
+ BACKGROUND=0
27
+ shift
28
+ ;;
29
+ --background)
30
+ BACKGROUND=1
31
+ shift
32
+ ;;
33
+ --fresh)
34
+ RESUME=0
35
+ shift
36
+ ;;
37
+ --resume)
38
+ RESUME=1
39
+ shift
40
+ ;;
41
+ *)
42
+ exit 2
43
+ ;;
44
+ esac
45
+ done
46
+
47
+ if [[ "${BACKGROUND}" -eq 1 ]]; then
48
+ SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
49
+ FORWARD_ARGS=(--foreground)
50
+ if [[ "${RESUME}" -eq 0 ]]; then
51
+ FORWARD_ARGS+=(--fresh)
52
+ else
53
+ FORWARD_ARGS+=(--resume)
54
+ fi
55
+ nohup "${SCRIPT_ABS_PATH}" "${FORWARD_ARGS[@]}" > /dev/null 2>&1 < /dev/null &
56
+ exit 0
57
+ fi
58
+
59
+ mkdir -p "${OUTPUT_ROOT}"
60
+ if [[ "${RESUME}" -eq 0 ]]; then
61
+ : > "${RESULT_FILE}"
62
+ : > "${REASONING_LOG}"
63
+ else
64
+ touch "${RESULT_FILE}" "${REASONING_LOG}"
65
+ fi
66
+
67
+ for ENV_FILE in "${BIOMNI_ROOT}/.env" "${LAB_BENCH_ROOT}/.env"; do
68
+ if [[ -f "${ENV_FILE}" ]]; then
69
+ set -a
70
+ # shellcheck disable=SC1090
71
+ source "${ENV_FILE}"
72
+ set +a
73
+ fi
74
+ done
75
+
76
+ if [[ -n "${DEEPSEEK_API_KEY:-}" && -z "${BIOMNI_CUSTOM_API_KEY:-}" ]]; then
77
+ export BIOMNI_SOURCE="${BIOMNI_SOURCE:-Custom}"
78
+ export BIOMNI_LLM="${BIOMNI_LLM:-${DEEPSEEK_MODEL_NAME:-deepseek-chat}}"
79
+ export BIOMNI_CUSTOM_BASE_URL="${BIOMNI_CUSTOM_BASE_URL:-${DEEPSEEK_BASE_URL:-https://api.deepseek.com/v1}}"
80
+ export BIOMNI_CUSTOM_API_KEY="${DEEPSEEK_API_KEY}"
81
+ fi
82
+
83
+ if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${OPENAI_API_KEY:-}" && -z "${BIOMNI_CUSTOM_API_KEY:-}" ]]; then
84
+ printf "[launcher] missing LLM API key environment variables; batch did not start\n" >> "${REASONING_LOG}"
85
+ exit 2
86
+ fi
87
+
88
+ read -r -a SPLIT_ARRAY <<< "${SPLITS_TEXT}"
89
+ if [[ "${#SPLIT_ARRAY[@]}" -ne 1 ]]; then
90
+ printf "[launcher] Biomni LAB-Bench runner supports one split per batch; got LAB_BENCH_SPLITS='%s'\n" "${SPLITS_TEXT}" >> "${REASONING_LOG}"
91
+ exit 2
92
+ fi
93
+ SPLIT="${SPLIT_ARRAY[0]}"
94
+
95
+ declare -a CHILD_PIDS=()
96
+
97
+ launch_shard() {
98
+ local shard_index="$1"
99
+ local run_label="seqqa_shard$(printf '%02d' "$((shard_index + 1))")of$(printf '%02d' "${TEST_SHARD_COUNT}")"
100
+ local -a cmd=(
101
+ "${RUNNER_PYTHON}" "${RUNNER}"
102
+ "--eval" "${EVAL_NAME}"
103
+ "--split" "${SPLIT}"
104
+ "--dev-size" "${DEV_SIZE}"
105
+ "--test-size" "${TEST_SIZE}"
106
+ "--seed" "${SEED}"
107
+ "--repeat" "1"
108
+ "--n_threads" "${N_THREADS}"
109
+ "--output-root" "${OUTPUT_ROOT}"
110
+ "--agent-root" "${BIOMNI_ROOT}/experiments/lab_bench/agent_runtime"
111
+ "--run-label" "${run_label}"
112
+ "--shard-index" "${shard_index}"
113
+ "--shard-count" "${TEST_SHARD_COUNT}"
114
+ "--reasoning-log-path" "${REASONING_LOG}"
115
+ "--compact-results-path" "${RESULT_FILE}"
116
+ "--compact-output-only"
117
+ "--skip-existing-results"
118
+ )
119
+ "${cmd[@]}" > /dev/null 2>&1 &
120
+ CHILD_PIDS+=("$!")
121
+ }
122
+
123
+ for (( shard_index=0; shard_index<TEST_SHARD_COUNT; shard_index++ )); do
124
+ launch_shard "${shard_index}"
125
+ done
126
+
127
+ for pid in "${CHILD_PIDS[@]}"; do
128
+ wait "${pid}"
129
+ done
figs/biomni_logo.png ADDED
license_info.md ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Biomni Data Source License Information
2
+
3
+ This document provides an overview of the data sources used by Biomni, their licenses, and suitability for internal hosting and commercial use.
4
+
5
+ A significant portion of the data used in Biomni requires a commercial license for any commercial application. Several datasets are explicitly licensed for non-commercial use only, which would prohibit their use in a commercial product without a separate agreement. Before proceeding with any commercial use, a thorough legal review of the licenses for each dataset you intend to use is strongly recommended.
6
+
7
+ ## Data License and Internal Hosting Analysis
8
+
9
+ | Data Source Category | Example Files | License | Internal Hosting | Source |
10
+ | :--- | :--- | :--- | :--- | :--- |
11
+ | **COSMIC** | `Cosmic_*.csv`, `Cosmic_*.parquet` | Requires commercial license for commercial use. | Yes, with a valid commercial license. | [Sanger Institute](https://cancer.sanger.ac.uk/cosmic) |
12
+ | **BindingDB** | `BindingDB_All_202409.tsv` | Custom, non-commercial use granted. Commercial use requires a license. | Yes, with a commercial license. | [BindingDB](https://www.bindingdb.org) |
13
+ | **Broad Repurposing Hub** | `broad_repurposing_hub_*.parquet` | CC BY 4.0 | Yes | [Broad Institute](https://www.broadinstitute.org/drug-repurposing-hub) |
14
+ | **DDInter** | `ddinter_*.csv` | CC BY-NC-SA 4.0 | No, non-commercial use only. | [DDInter](http://ddinter.scbdd.com/) |
15
+ | **DisGeNET** | `DisGeNET.parquet` | CC BY-NC-SA 4.0 | No, non-commercial use only. | [DisGeNET](https://www.disgenet.org/) |
16
+ | **Enamine** | `enamine_cloud_library_smiles.pkl` | Proprietary. Requires license for screening. | Yes, with a valid license. | [Enamine](https://enamine.net/) |
17
+ | **EveBio** | `evebio_*.csv` | Appears to be proprietary data from EveBio. | Requires permission from EveBio. | EveBio |
18
+ | **Gene Ontology (GO)** | `go-plus.json` | CC BY 4.0 | Yes | [Gene Ontology Consortium](http://geneontology.org/) |
19
+ | **GTEx** | `gtex_tissue_gene_tpm.parquet` | dbGaP controlled access. | Yes, with authorization. | [GTEx Portal](https://gtexportal.org/) |
20
+ | **Human Protein Atlas** | `proteinatlas.tsv` | CC BY-SA 3.0 | Yes | [Human Protein Atlas](https://www.proteinatlas.org/) |
21
+ | **MSigDB** | `msigdb_human_*.parquet` | Custom, requires license for commercial use. | Yes, with a license. | [Broad Institute](https://www.gsea-msigdb.org/gsea/msigdb) |
22
+ | **OMIM** | `omim.parquet` | Custom, requires license for commercial use. | Yes, with a license. | [OMIM](https://omim.org/) |
23
+ | **BioGRID** | `affinity_capture-ms.parquet`, etc. | OSL 3.0 | Yes | [BioGRID](https://thebiogrid.org/) |
24
+ | **CZI Cell Census** | `czi_census_datasets_v4.parquet` | CC BY 4.0 | Yes | [Chan Zuckerberg Initiative](https://cellxgene.cziscience.com/census) |
25
+ | **DepMap** | `DepMap_*.csv` | CC BY 4.0 | Yes | [Broad Institute DepMap](https://depmap.org/) |
26
+ | **Genebass** | `genebass_*.pkl` | ODC-By v1.0 | Yes | [Genebass](https://genebass.org/) |
27
+ | **GWAS Catalog** | `gwas_catalog.pkl` | Apache 2.0 | Yes | [EBI GWAS Catalog](https://www.ebi.ac.uk/gwas/) |
28
+ | **HPO** | `hp.obo` | Custom, free for all uses. | Yes | [Human Phenotype Ontology](https://hpo.jax.org/) |
29
+ | **McPAS-TCR** | `McPAS-TCR.parquet` | CC BY-NC-SA 4.0 | No, non-commercial use only. | [McPAS-TCR](http://friedmanlab.weizmann.ac.il/McPAS-TCR/) |
30
+ | **miRDB** | `miRDB_v6.0_results.parquet` | Custom, free for non-commercial use. | No, non-commercial use only. | [miRDB](http://mirdb.org/) |
31
+ | **miRTarBase** | `miRTarBase_*.parquet` | CC BY-NC 4.0 | No, non-commercial use only. | [miRTarBase](https://mirtarbase.cuhk.edu.cn/) |
32
+ | **MouseMine** | `mousemine_*.parquet` | CC BY 4.0 | Yes | [MouseMine](http://www.mousemine.org/) |
33
+ | **P-HIPSTER** | `Virus-Host_PPI_P-HIPSTER_2020.parquet` | CC BY 4.0 | Yes | [P-HIPSTER](http://phipster.org) |
34
+ | **TXGNN** | `txgnn_*.pkl` | MIT License | Yes | - |
35
+
36
+ ## Configuration for Commercial Use
37
+
38
+ To manage which datasets are used based on licensing, Biomni provides a configuration option. You can set the `commercial_mode` flag to `True` in your configuration to automatically exclude datasets that are not licensed for commercial use.
39
+
40
+ ### Usage
41
+
42
+ ```python
43
+ from biomni.agent import A1
44
+
45
+ # For commercial use (excludes non-commercial datasets)
46
+ agent = A1(commercial_mode=True)
47
+
48
+ # For academic/research use (includes all datasets)
49
+ agent = A1(commercial_mode=False) # default
50
+ ```
51
+
52
+ This configuration automatically selects the appropriate data environment description file and ensures compliance with licensing requirements.
pyproject.toml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "biomni"
7
+ dynamic = ["version"]
8
+ description = "Biomni"
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ license-files = ["LICENSE"]
12
+ authors = [
13
+ {name = "Biomni Team", email = "kexinh@cs.stanford.edu"}
14
+ ]
15
+ requires-python = ">=3.11"
16
+ dependencies = ["pydantic", "langchain", "python-dotenv"]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/snap-stanford/biomni"
20
+ Repository = "https://github.com/snap-stanford/biomni"
21
+
22
+ [project.optional-dependencies]
23
+ gradio = ["gradio>=5.0,<6.0"]
24
+
25
+ [tool.setuptools]
26
+ include-package-data = true
27
+
28
+ [tool.setuptools.dynamic]
29
+ version = {attr = "biomni.version.__version__"}
30
+
31
+ [tool.setuptools.packages.find]
32
+ exclude = ["test*", "tutorials*"]
33
+
34
+ [tool.ruff]
35
+ src = ["src"]
36
+ line-length = 120
37
+
38
+ [tool.ruff.format]
39
+ docstring-code-format = true
40
+
41
+ [tool.ruff.lint]
42
+ select = [
43
+ "F", # Errors detected by Pyflakes
44
+ "E", # Error detected by Pycodestyle
45
+ "W", # Warning detected by Pycodestyle
46
+ "I", # isort
47
+ #"D", # pydocstyle
48
+ "B", # flake8-bugbear
49
+ "TID", # flake8-tidy-imports
50
+ "C4", # flake8-comprehensions
51
+ "BLE", # flake8-blind-except
52
+ "UP", # pyupgrade
53
+ "RUF100", # Report unused noqa directives
54
+ "TCH", # Typing imports
55
+ #"NPY", # Numpy specific rules
56
+ #"PTH", # Use pathlib
57
+ #"PL" # pylint
58
+ ]
59
+ ignore = [
60
+ # line too long -> we accept long comment lines; black gets rid of long code lines
61
+ "E501",
62
+ # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
63
+ "E731",
64
+ # allow I, O, l as variable names -> I is the identity matrix
65
+ "E741",
66
+ # Missing docstring in public package
67
+ "D104",
68
+ # Missing docstring in public module
69
+ "D100",
70
+ # Missing docstring in __init__
71
+ "D107",
72
+ # Errors from function calls in argument defaults. These are fine when the result is immutable.
73
+ "B008",
74
+ # __magic__ methods are are often self-explanatory, allow missing docstrings
75
+ "D105",
76
+ # first line should end with a period [Bug: doesn't work with single-line docstrings]
77
+ "D400",
78
+ # First line should be in imperative mood; try rephrasing
79
+ "D401",
80
+ ## Disable one in each pair of mutually incompatible rules
81
+ # We don’t want a blank line before a class docstring
82
+ "D203",
83
+ # We want docstrings to start immediately after the opening triple quote
84
+ "D213",
85
+ # camcelcase imported as lowercase
86
+ "N813",
87
+ # module import not at top level of file
88
+ "E402",
89
+ # Too many arguments in function definition
90
+ "PLR0913",
91
+ # Too many branches
92
+ "PLR0912",
93
+ # magic value used in comparison
94
+ "PLR2004",
95
+ # Too many statements
96
+ "PLR0915",
97
+ # Extracting value from dictionary without calling `.items()` - clashes with sim
98
+ "PLC0206",
99
+ # import should be at top of file
100
+ "PLC0415",
101
+ # do not catch blind exception
102
+ "BLE001",
103
+ # strip with multi characters
104
+ "B005"
105
+ ]
tutorials/biomni_101.ipynb ADDED
@@ -0,0 +1,1070 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "## Biomni 101"
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "markdown",
12
+ "metadata": {},
13
+ "source": [
14
+ "Welcome to Biomni! Here is a simple tutorial on the basics of Biomni package. After you installed the environment, you can then simply try this to initialize the agent, which will automatically download the raw data lake files:"
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": 1,
20
+ "metadata": {},
21
+ "outputs": [
22
+ {
23
+ "name": "stdout",
24
+ "output_type": "stream",
25
+ "text": [
26
+ "🎓 Academic mode: Using all datasets (including non-commercial)\n",
27
+ "\n",
28
+ "==================================================\n",
29
+ "🔧 BIOMNI CONFIGURATION\n",
30
+ "==================================================\n",
31
+ "📋 DEFAULT CONFIG (Including Database LLM):\n",
32
+ " Path: ./data\n",
33
+ " Timeout Seconds: 600\n",
34
+ " Llm: claude-sonnet-4-5\n",
35
+ " Temperature: 0.7\n",
36
+ " Use Tool Retriever: True\n",
37
+ " Commercial Mode: Academic (all datasets)\n",
38
+ "\n",
39
+ "🤖 AGENT LLM (Constructor Override):\n",
40
+ " LLM Model: claude-sonnet-4-5-20250929\n",
41
+ "==================================================\n",
42
+ "\n",
43
+ "Created directory: ./biomni_data\n",
44
+ "Checking and downloading missing data lake files...\n",
45
+ "Downloading affinity_capture-ms.parquet from data_lake...\n"
46
+ ]
47
+ },
48
+ {
49
+ "name": "stderr",
50
+ "output_type": "stream",
51
+ "text": [
52
+ "affinity_capture-ms.parquet: 100%|█████████| 5.59M/5.59M [00:00<00:00, 11.8MB/s]\n"
53
+ ]
54
+ },
55
+ {
56
+ "name": "stdout",
57
+ "output_type": "stream",
58
+ "text": [
59
+ "✓ Successfully downloaded: affinity_capture-ms.parquet\n",
60
+ "Downloading affinity_capture-rna.parquet from data_lake...\n"
61
+ ]
62
+ },
63
+ {
64
+ "name": "stderr",
65
+ "output_type": "stream",
66
+ "text": [
67
+ "affinity_capture-rna.parquet: 100%|████████| 43.0k/43.0k [00:00<00:00, 1.21MB/s]\n"
68
+ ]
69
+ },
70
+ {
71
+ "name": "stdout",
72
+ "output_type": "stream",
73
+ "text": [
74
+ "✓ Successfully downloaded: affinity_capture-rna.parquet\n",
75
+ "Downloading BindingDB_All_202409.tsv from data_lake...\n"
76
+ ]
77
+ },
78
+ {
79
+ "name": "stderr",
80
+ "output_type": "stream",
81
+ "text": [
82
+ "BindingDB_All_202409.tsv: 2%|▎ | 154M/6.25G [00:43<28:35, 3.55MB/s]\n"
83
+ ]
84
+ },
85
+ {
86
+ "ename": "KeyboardInterrupt",
87
+ "evalue": "",
88
+ "output_type": "error",
89
+ "traceback": [
90
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
91
+ "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
92
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 7\u001b[39m\n\u001b[32m 3\u001b[39m sys.path.append(\u001b[33m\"\u001b[39m\u001b[33m../\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 5\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mbiomni\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01magent\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m A1\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m agent = \u001b[43mA1\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m./biomni_data\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mllm\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mclaude-sonnet-4-5-20250929\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
93
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Biomni/tutorials/../biomni/agent/a1.py:167\u001b[39m, in \u001b[36mA1.__init__\u001b[39m\u001b[34m(self, path, llm, source, use_tool_retriever, timeout_seconds, base_url, api_key, commercial_mode, expected_data_lake_files)\u001b[39m\n\u001b[32m 165\u001b[39m \u001b[38;5;66;03m# Check and download missing data lake files\u001b[39;00m\n\u001b[32m 166\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mChecking and downloading missing data lake files...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m167\u001b[39m \u001b[43mcheck_and_download_s3_files\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 168\u001b[39m \u001b[43m \u001b[49m\u001b[43ms3_bucket_url\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mhttps://biomni-release.s3.amazonaws.com\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 169\u001b[39m \u001b[43m \u001b[49m\u001b[43mlocal_data_lake_path\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdata_lake_dir\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 170\u001b[39m \u001b[43m \u001b[49m\u001b[43mexpected_files\u001b[49m\u001b[43m=\u001b[49m\u001b[43mexpected_data_lake_files\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 171\u001b[39m \u001b[43m \u001b[49m\u001b[43mfolder\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mdata_lake\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 172\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 174\u001b[39m \u001b[38;5;66;03m# Check if benchmark directory structure is complete\u001b[39;00m\n\u001b[32m 175\u001b[39m benchmark_ok = \u001b[38;5;28;01mFalse\u001b[39;00m\n",
94
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Biomni/tutorials/../biomni/utils.py:1011\u001b[39m, in \u001b[36mcheck_and_download_s3_files\u001b[39m\u001b[34m(s3_bucket_url, local_data_lake_path, expected_files, folder)\u001b[39m\n\u001b[32m 1008\u001b[39m s3_file_url = urljoin(s3_bucket_url + \u001b[33m\"\u001b[39m\u001b[33m/\u001b[39m\u001b[33m\"\u001b[39m + folder + \u001b[33m\"\u001b[39m\u001b[33m/\u001b[39m\u001b[33m\"\u001b[39m, filename)\n\u001b[32m 1009\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mDownloading \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfilename\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m from \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfolder\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1011\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[43mdownload_with_progress\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms3_file_url\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlocal_file_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilename\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[32m 1012\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m✓ Successfully downloaded: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfilename\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 1013\u001b[39m download_results[filename] = \u001b[38;5;28;01mTrue\u001b[39;00m\n",
95
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/Biomni/tutorials/../biomni/utils.py:949\u001b[39m, in \u001b[36mcheck_and_download_s3_files.<locals>.download_with_progress\u001b[39m\u001b[34m(url, file_path, desc)\u001b[39m\n\u001b[32m 947\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m total_size > \u001b[32m0\u001b[39m:\n\u001b[32m 948\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m tqdm.tqdm(total=total_size, unit=\u001b[33m\"\u001b[39m\u001b[33mB\u001b[39m\u001b[33m\"\u001b[39m, unit_scale=\u001b[38;5;28;01mTrue\u001b[39;00m, desc=desc, ncols=\u001b[32m80\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m pbar:\n\u001b[32m--> \u001b[39m\u001b[32m949\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m.\u001b[49m\u001b[43miter_content\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchunk_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m8192\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 950\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 951\u001b[39m \u001b[43m \u001b[49m\u001b[43mf\u001b[49m\u001b[43m.\u001b[49m\u001b[43mwrite\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m)\u001b[49m\n",
96
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/site-packages/requests/models.py:820\u001b[39m, in \u001b[36mResponse.iter_content.<locals>.generate\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 818\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(\u001b[38;5;28mself\u001b[39m.raw, \u001b[33m\"\u001b[39m\u001b[33mstream\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m 819\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m820\u001b[39m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m.raw.stream(chunk_size, decode_content=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 821\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ProtocolError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 822\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m ChunkedEncodingError(e)\n",
97
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/site-packages/urllib3/response.py:1091\u001b[39m, in \u001b[36mHTTPResponse.stream\u001b[39m\u001b[34m(self, amt, decode_content)\u001b[39m\n\u001b[32m 1089\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1090\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m is_fp_closed(\u001b[38;5;28mself\u001b[39m._fp) \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m._decoded_buffer) > \u001b[32m0\u001b[39m:\n\u001b[32m-> \u001b[39m\u001b[32m1091\u001b[39m data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mamt\u001b[49m\u001b[43m=\u001b[49m\u001b[43mamt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1093\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m data:\n\u001b[32m 1094\u001b[39m \u001b[38;5;28;01myield\u001b[39;00m data\n",
98
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/site-packages/urllib3/response.py:980\u001b[39m, in \u001b[36mHTTPResponse.read\u001b[39m\u001b[34m(self, amt, decode_content, cache_content)\u001b[39m\n\u001b[32m 977\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m._decoded_buffer) >= amt:\n\u001b[32m 978\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._decoded_buffer.get(amt)\n\u001b[32m--> \u001b[39m\u001b[32m980\u001b[39m data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_raw_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mamt\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 982\u001b[39m flush_decoder = amt \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m (amt != \u001b[32m0\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m data)\n\u001b[32m 984\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m data \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m._decoded_buffer) == \u001b[32m0\u001b[39m:\n",
99
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/site-packages/urllib3/response.py:904\u001b[39m, in \u001b[36mHTTPResponse._raw_read\u001b[39m\u001b[34m(self, amt, read1)\u001b[39m\n\u001b[32m 901\u001b[39m fp_closed = \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m._fp, \u001b[33m\"\u001b[39m\u001b[33mclosed\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[32m 903\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m._error_catcher():\n\u001b[32m--> \u001b[39m\u001b[32m904\u001b[39m data = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_fp_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mamt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mread1\u001b[49m\u001b[43m=\u001b[49m\u001b[43mread1\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m fp_closed \u001b[38;5;28;01melse\u001b[39;00m \u001b[33mb\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 905\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m amt \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m amt != \u001b[32m0\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m data:\n\u001b[32m 906\u001b[39m \u001b[38;5;66;03m# Platform-specific: Buggy versions of Python.\u001b[39;00m\n\u001b[32m 907\u001b[39m \u001b[38;5;66;03m# Close the connection when no data is returned\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 912\u001b[39m \u001b[38;5;66;03m# not properly close the connection in all cases. There is\u001b[39;00m\n\u001b[32m 913\u001b[39m \u001b[38;5;66;03m# no harm in redundantly calling close.\u001b[39;00m\n\u001b[32m 914\u001b[39m \u001b[38;5;28mself\u001b[39m._fp.close()\n",
100
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/site-packages/urllib3/response.py:887\u001b[39m, in \u001b[36mHTTPResponse._fp_read\u001b[39m\u001b[34m(self, amt, read1)\u001b[39m\n\u001b[32m 884\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._fp.read1(amt) \u001b[38;5;28;01mif\u001b[39;00m amt \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m._fp.read1()\n\u001b[32m 885\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 886\u001b[39m \u001b[38;5;66;03m# StringIO doesn't like amt=None\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m887\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_fp\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mamt\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mif\u001b[39;00m amt \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m._fp.read()\n",
101
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/http/client.py:473\u001b[39m, in \u001b[36mHTTPResponse.read\u001b[39m\u001b[34m(self, amt)\u001b[39m\n\u001b[32m 470\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.length \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m amt > \u001b[38;5;28mself\u001b[39m.length:\n\u001b[32m 471\u001b[39m \u001b[38;5;66;03m# clip the read to the \"end of response\"\u001b[39;00m\n\u001b[32m 472\u001b[39m amt = \u001b[38;5;28mself\u001b[39m.length\n\u001b[32m--> \u001b[39m\u001b[32m473\u001b[39m s = \u001b[38;5;28mself\u001b[39m.fp.read(amt)\n\u001b[32m 474\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m s \u001b[38;5;129;01mand\u001b[39;00m amt:\n\u001b[32m 475\u001b[39m \u001b[38;5;66;03m# Ideally, we would raise IncompleteRead if the content-length\u001b[39;00m\n\u001b[32m 476\u001b[39m \u001b[38;5;66;03m# wasn't satisfied, but it might break compatibility.\u001b[39;00m\n\u001b[32m 477\u001b[39m \u001b[38;5;28mself\u001b[39m._close_conn()\n",
102
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/socket.py:718\u001b[39m, in \u001b[36mSocketIO.readinto\u001b[39m\u001b[34m(self, b)\u001b[39m\n\u001b[32m 716\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m 717\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m718\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_sock\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrecv_into\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 719\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m timeout:\n\u001b[32m 720\u001b[39m \u001b[38;5;28mself\u001b[39m._timeout_occurred = \u001b[38;5;28;01mTrue\u001b[39;00m\n",
103
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/ssl.py:1314\u001b[39m, in \u001b[36mSSLSocket.recv_into\u001b[39m\u001b[34m(self, buffer, nbytes, flags)\u001b[39m\n\u001b[32m 1310\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m flags != \u001b[32m0\u001b[39m:\n\u001b[32m 1311\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 1312\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mnon-zero flags not allowed in calls to recv_into() on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m %\n\u001b[32m 1313\u001b[39m \u001b[38;5;28mself\u001b[39m.\u001b[34m__class__\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1314\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnbytes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1315\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1316\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28msuper\u001b[39m().recv_into(buffer, nbytes, flags)\n",
104
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/biomni_fixed/lib/python3.11/ssl.py:1166\u001b[39m, in \u001b[36mSSLSocket.read\u001b[39m\u001b[34m(self, len, buffer)\u001b[39m\n\u001b[32m 1164\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 1165\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m buffer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1166\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_sslobj\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1167\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1168\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._sslobj.read(\u001b[38;5;28mlen\u001b[39m)\n",
105
+ "\u001b[31mKeyboardInterrupt\u001b[39m: "
106
+ ]
107
+ }
108
+ ],
109
+ "source": [
110
+ "import sys\n",
111
+ "\n",
112
+ "sys.path.append(\"../\")\n",
113
+ "\n",
114
+ "from biomni.agent import A1\n",
115
+ "\n",
116
+ "agent = A1(path=\"./biomni_data\", llm=\"claude-sonnet-4-5-20250929\")"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "metadata": {},
122
+ "source": [
123
+ "Then, you can simply start prompting the agent with the desired biomedical research task!"
124
+ ]
125
+ },
126
+ {
127
+ "cell_type": "code",
128
+ "execution_count": 2,
129
+ "metadata": {},
130
+ "outputs": [
131
+ {
132
+ "name": "stdout",
133
+ "output_type": "stream",
134
+ "text": [
135
+ "Using prompt-based retrieval with the agent's LLM\n",
136
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
137
+ "\n",
138
+ "Plan a CRISPR screen to identify genes that regulate T cell exhaustion, \n",
139
+ " measured by the change in T cell receptor (TCR) signaling between acute \n",
140
+ " (interleukin-2 [IL-2] only) and chronic (anti-CD3 and IL-2) stimulation conditions. \n",
141
+ " Generate 32 genes that maximize the perturbation effect.\n",
142
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
143
+ "\n",
144
+ "I'll help you plan a CRISPR screen to identify genes that regulate T cell exhaustion. Let me break this down into a systematic approach.\n",
145
+ "\n",
146
+ "## Plan:\n",
147
+ "\n",
148
+ "1. [ ] Research T cell exhaustion biology and identify key pathways\n",
149
+ "2. [ ] Query databases for genes involved in T cell exhaustion, TCR signaling, and chronic stimulation\n",
150
+ "3. [ ] Identify genes from exhaustion-related pathways (checkpoint inhibitors, metabolic regulators, transcription factors)\n",
151
+ "4. [ ] Select genes that are likely to show differential effects between acute vs chronic stimulation\n",
152
+ "5. [ ] Design sgRNAs for the selected 32 genes\n",
153
+ "6. [ ] Validate the gene list and provide experimental design recommendations\n",
154
+ "\n",
155
+ "Let me start by researching the biological basis of T cell exhaustion and identifying relevant genes.\n",
156
+ "\n",
157
+ "<execute>\n",
158
+ "# First, let's import necessary libraries and query databases for T cell exhaustion-related genes\n",
159
+ "from biomni.tool.database import query_uniprot, query_kegg, query_reactome, query_ensembl\n",
160
+ "from biomni.tool.genomics import gene_set_enrichment_analysis, get_gene_set_enrichment_analysis_supported_database_list\n",
161
+ "from biomni.tool.molecular_biology import design_knockout_sgrna\n",
162
+ "import pandas as pd\n",
163
+ "import numpy as np\n",
164
+ "\n",
165
+ "print(\"=== Step 1: Researching T cell exhaustion biology ===\")\n",
166
+ "\n",
167
+ "# Query for T cell exhaustion pathways\n",
168
+ "exhaustion_pathways = query_reactome(\n",
169
+ " prompt=\"Find pathways related to T cell exhaustion, TCR signaling, and chronic T cell stimulation\"\n",
170
+ ")\n",
171
+ "print(\"Reactome pathways for T cell exhaustion:\")\n",
172
+ "print(exhaustion_pathways)\n",
173
+ "</execute>\n",
174
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
175
+ "\n",
176
+ "<observation>The output is too long to be added to context. Here are the first 10K characters...\n",
177
+ "=== Step 1: Researching T cell exhaustion biology ===\n",
178
+ "Reactome pathways for T cell exhaustion:\n",
179
+ "{'success': True, 'query_info': {'endpoint': 'https://reactome.org/ContentService/search/query', 'method': 'GET', 'description': 'Search Reactome for pathways related to T cell exhaustion, T cell receptor signaling, and chronic T cell stimulation'}, 'result': {'results': [{'entries': [{'dbId': '202403', 'stId': 'R-HSA-202403', 'id': 'R-HSA-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Homo sapiens'], 'summation': 'The <span class=\"highlighting\" >TCR</span> is a multisubunit complex that consists of clonotypic alpha/beta chains noncovalently associated with the invariant CD3 delta/epsilon/gamma and <span class=\"highlighting\" >TCR</span> zeta chains. T <span class=\"highlighting\" >cell</span> activation by antigen presenting cells (APCs) results in the activation of protein tyrosine kinases (PTKs) that associate with CD3 and <span class=\"highlighting\" >TCR</span> zeta subunits and the co-receptor CD4. Members of the Src kinases (Lck), Syk kinases (ZAP-70), Tec (Itk) and Csk families of nonreceptor PTKs play a crucial role in T <span class=\"highlighting\" >cell</span> activation. Activation of PTKs following <span class=\"highlighting\" >TCR</span> engagement results in the recruitment and tyrosine phosphorylation of enzymes such as phospholipase C gamma1 and Vav as well as critical adaptor proteins such as LAT, SLP-76 and Gads. These proximal activation leads to reorganization of the cytoskeleton as well as transcription activation of multiple genes leading to T lymphocyte proliferation, differentiation and/or effector function. ', 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10069478', 'stId': 'R-MMU-202403', 'id': 'R-MMU-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Mus musculus'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10185057', 'stId': 'R-RNO-202403', 'id': 'R-RNO-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Rattus norvegicus'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10293315', 'stId': 'R-CFA-202403', 'id': 'R-CFA-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Canis familiaris'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10407569', 'stId': 'R-BTA-202403', 'id': 'R-BTA-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Bos taurus'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10523735', 'stId': 'R-SSC-202403', 'id': 'R-SSC-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Sus scrofa'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10585787', 'stId': 'R-DRE-202403', 'id': 'R-DRE-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Danio rerio'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10661801', 'stId': 'R-XTR-202403', 'id': 'R-XTR-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Xenopus tropicalis'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10759741', 'stId': 'R-GGA-202403', 'id': 'R-GGA-202403', 'name': '<span class=\"highlighting\" >TCR</span> <span class=\"highlighting\" >signaling</span>', 'exactType': 'Pathway', 'species': ['Gallus gallus'], 'summation': \"This event has been computationally inferred from an event that has been demonstrated in another species.<p>The inference is based on the homology mapping from PANTHER. Briefly, reactions for which all involved PhysicalEntities (in input, output and catalyst) have a mapped orthologue/paralogue (for complexes at least 75% of components must have a mapping) are inferred to the other species. High level events are also inferred for these events to allow for easier navigation.<p><a href='/electronic_inference_compara.html' target = 'NEW'>More details and caveats of the event inference in Reactome.</a> For details on PANTHER see also: <a href='http://www.pantherdb.org/about.jsp' target='NEW'>http://www.pantherdb.org/about.jsp</a>\", 'isDisease': False, 'disease': False, 'icon': False}, {'dbId': '10848043', 'stId': 'R-D</observation>\n",
180
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
181
+ "\n",
182
+ "<execute>\n",
183
+ "# Let's also query for immune checkpoint and T cell activation pathways\n",
184
+ "print(\"\\n=== Querying for immune checkpoint pathways ===\")\n",
185
+ "checkpoint_pathways = query_reactome(\n",
186
+ " prompt=\"Find pathways related to immune checkpoint inhibition, PD-1, CTLA-4, and T cell activation\"\n",
187
+ ")\n",
188
+ "print(\"Checkpoint pathways found:\", len(checkpoint_pathways.get('result', {}).get('results', [])))\n",
189
+ "</execute>\n",
190
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
191
+ "\n",
192
+ "<observation>\n",
193
+ "=== Querying for immune checkpoint pathways ===\n",
194
+ "Checkpoint pathways found: 10\n",
195
+ "</observation>\n",
196
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
197
+ "\n",
198
+ "<execute>\n",
199
+ "# Now let's identify key genes involved in T cell exhaustion from literature and databases\n",
200
+ "print(\"=== Step 2: Identifying key T cell exhaustion genes ===\")\n",
201
+ "\n",
202
+ "# Key categories of genes involved in T cell exhaustion:\n",
203
+ "# 1. Immune checkpoint receptors and ligands\n",
204
+ "checkpoint_genes = [\n",
205
+ " 'PDCD1', # PD-1\n",
206
+ " 'CD274', # PD-L1\n",
207
+ " 'PDCD1LG2', # PD-L2\n",
208
+ " 'CTLA4', # CTLA-4\n",
209
+ " 'HAVCR2', # TIM-3\n",
210
+ " 'LAG3', # LAG-3\n",
211
+ " 'TIGIT', # TIGIT\n",
212
+ " 'CD96', # CD96\n",
213
+ " 'BTLA', # BTLA\n",
214
+ " 'CD160' # CD160\n",
215
+ "]\n",
216
+ "\n",
217
+ "# 2. Transcription factors regulating exhaustion\n",
218
+ "transcription_factors = [\n",
219
+ " 'TOX', # TOX - master regulator of exhaustion\n",
220
+ " 'TOX2', # TOX2\n",
221
+ " 'EOMES', # Eomesodermin\n",
222
+ " 'TBX21', # T-bet\n",
223
+ " 'BATF', # BATF\n",
224
+ " 'IRF4', # IRF4\n",
225
+ " 'NFAT5', # NFAT5\n",
226
+ " 'NFATC1', # NFATC1\n",
227
+ " 'RUNX3', # RUNX3\n",
228
+ " 'STAT3' # STAT3\n",
229
+ "]\n",
230
+ "\n",
231
+ "# 3. Metabolic regulators\n",
232
+ "metabolic_genes = [\n",
233
+ " 'PPAR', # PPAR family\n",
234
+ " 'HIF1A', # HIF-1α\n",
235
+ " 'MYC', # c-Myc\n",
236
+ " 'MTOR', # mTOR\n",
237
+ " 'AMPK', # AMPK (PRKAA1)\n",
238
+ " 'LDHA', # Lactate dehydrogenase A\n",
239
+ " 'PKM', # Pyruvate kinase M\n",
240
+ " 'GLUT1' # Glucose transporter 1 (SLC2A1)\n",
241
+ "]\n",
242
+ "\n",
243
+ "# Let's refine the metabolic genes with proper gene symbols\n",
244
+ "metabolic_genes_refined = [\n",
245
+ " 'PPARA', # PPAR alpha\n",
246
+ " 'HIF1A', # HIF-1α\n",
247
+ " 'MYC', # c-Myc\n",
248
+ " 'MTOR', # mTOR\n",
249
+ " 'PRKAA1', # AMPK alpha 1\n",
250
+ " 'LDHA', # Lactate dehydrogenase A\n",
251
+ " 'PKM', # Pyruvate kinase M\n",
252
+ " 'SLC2A1' # GLUT1\n",
253
+ "]\n",
254
+ "\n",
255
+ "# 4. TCR signaling components\n",
256
+ "tcr_signaling_genes = [\n",
257
+ " 'LCK', # Lck\n",
258
+ " 'ZAP70', # ZAP-70\n",
259
+ " 'LAT', # LAT\n",
260
+ " 'SLP76', # SLP-76 (LCP2)\n",
261
+ " 'PLCG1', # PLCγ1\n",
262
+ " 'VAV1', # Vav1\n",
263
+ " 'ITK', # Itk\n",
264
+ " 'CD3E', # CD3ε\n",
265
+ " 'CD3G', # CD3γ\n",
266
+ " 'CD3D' # CD3δ\n",
267
+ "]\n",
268
+ "\n",
269
+ "# Refine TCR signaling genes\n",
270
+ "tcr_signaling_genes_refined = [\n",
271
+ " 'LCK',\n",
272
+ " 'ZAP70',\n",
273
+ " 'LAT',\n",
274
+ " 'LCP2', # SLP-76\n",
275
+ " 'PLCG1',\n",
276
+ " 'VAV1',\n",
277
+ " 'ITK',\n",
278
+ " 'CD3E',\n",
279
+ " 'CD3G',\n",
280
+ " 'CD3D'\n",
281
+ "]\n",
282
+ "\n",
283
+ "print(\"Checkpoint genes:\", len(checkpoint_genes))\n",
284
+ "print(\"Transcription factors:\", len(transcription_factors))\n",
285
+ "print(\"Metabolic genes:\", len(metabolic_genes_refined))\n",
286
+ "print(\"TCR signaling genes:\", len(tcr_signaling_genes_refined))\n",
287
+ "\n",
288
+ "# Combine all categories\n",
289
+ "all_candidate_genes = checkpoint_genes + transcription_factors + metabolic_genes_refined + tcr_signaling_genes_refined\n",
290
+ "print(f\"\\nTotal candidate genes: {len(all_candidate_genes)}\")\n",
291
+ "print(\"All candidates:\", all_candidate_genes)\n",
292
+ "</execute>\n",
293
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
294
+ "\n",
295
+ "<observation>=== Step 2: Identifying key T cell exhaustion genes ===\n",
296
+ "Checkpoint genes: 10\n",
297
+ "Transcription factors: 10\n",
298
+ "Metabolic genes: 8\n",
299
+ "TCR signaling genes: 10\n",
300
+ "\n",
301
+ "Total candidate genes: 38\n",
302
+ "All candidates: ['PDCD1', 'CD274', 'PDCD1LG2', 'CTLA4', 'HAVCR2', 'LAG3', 'TIGIT', 'CD96', 'BTLA', 'CD160', 'TOX', 'TOX2', 'EOMES', 'TBX21', 'BATF', 'IRF4', 'NFAT5', 'NFATC1', 'RUNX3', 'STAT3', 'PPARA', 'HIF1A', 'MYC', 'MTOR', 'PRKAA1', 'LDHA', 'PKM', 'SLC2A1', 'LCK', 'ZAP70', 'LAT', 'LCP2', 'PLCG1', 'VAV1', 'ITK', 'CD3E', 'CD3G', 'CD3D']\n",
303
+ "</observation>\n",
304
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
305
+ "\n",
306
+ "<execute>\n",
307
+ "# Now let's prioritize and select the most impactful 32 genes\n",
308
+ "print(\"=== Step 3: Prioritizing genes for maximum perturbation effect ===\")\n",
309
+ "\n",
310
+ "# Priority scoring based on:\n",
311
+ "# 1. Known role in T cell exhaustion (literature evidence)\n",
312
+ "# 2. Potential for differential effect between acute vs chronic stimulation\n",
313
+ "# 3. Druggability/therapeutic relevance\n",
314
+ "\n",
315
+ "high_priority_genes = {\n",
316
+ " # Master regulators of exhaustion (highest priority)\n",
317
+ " 'TOX': {'priority': 10, 'category': 'Master TF', 'rationale': 'Master regulator of T cell exhaustion'},\n",
318
+ " 'PDCD1': {'priority': 10, 'category': 'Checkpoint', 'rationale': 'PD-1, primary exhaustion checkpoint'},\n",
319
+ " 'EOMES': {'priority': 9, 'category': 'TF', 'rationale': 'Key exhaustion transcription factor'},\n",
320
+ " 'HAVCR2': {'priority': 9, 'category': 'Checkpoint', 'rationale': 'TIM-3, critical exhaustion marker'},\n",
321
+ " \n",
322
+ " # High impact checkpoint receptors\n",
323
+ " 'LAG3': {'priority': 9, 'category': 'Checkpoint', 'rationale': 'LAG-3, major exhaustion checkpoint'},\n",
324
+ " 'TIGIT': {'priority': 8, 'category': 'Checkpoint', 'rationale': 'TIGIT, emerging exhaustion target'},\n",
325
+ " 'CTLA4': {'priority': 8, 'category': 'Checkpoint', 'rationale': 'CTLA-4, early checkpoint inhibitor'},\n",
326
+ " \n",
327
+ " # Transcription factors\n",
328
+ " 'BATF': {'priority': 8, 'category': 'TF', 'rationale': 'BATF, exhaustion-promoting TF'},\n",
329
+ " 'TBX21': {'priority': 7, 'category': 'TF', 'rationale': 'T-bet, effector vs exhaustion balance'},\n",
330
+ " 'IRF4': {'priority': 7, 'category': 'TF', 'rationale': 'IRF4, T cell differentiation'},\n",
331
+ " 'NFATC1': {'priority': 7, 'category': 'TF', 'rationale': 'NFAT, chronic stimulation response'},\n",
332
+ " 'TOX2': {'priority': 7, 'category': 'TF', 'rationale': 'TOX2, TOX family member'},\n",
333
+ " \n",
334
+ " # Metabolic regulators (key for chronic vs acute differences)\n",
335
+ " 'HIF1A': {'priority': 8, 'category': 'Metabolism', 'rationale': 'HIF-1α, hypoxia and exhaustion'},\n",
336
+ " 'MYC': {'priority': 8, 'category': 'Metabolism', 'rationale': 'c-Myc, metabolic reprogramming'},\n",
337
+ " 'MTOR': {'priority': 7, 'category': 'Metabolism', 'rationale': 'mTOR, metabolic sensing'},\n",
338
+ " 'PRKAA1': {'priority': 7, 'category': 'Metabolism', 'rationale': 'AMPK, energy sensing'},\n",
339
+ " 'LDHA': {'priority': 6, 'category': 'Metabolism', 'rationale': 'Lactate metabolism in exhaustion'},\n",
340
+ " \n",
341
+ " # TCR signaling (differential between acute/chronic)\n",
342
+ " 'LCK': {'priority': 8, 'category': 'TCR', 'rationale': 'Lck, proximal TCR signaling'},\n",
343
+ " 'ZAP70': {'priority': 8, 'category': 'TCR', 'rationale': 'ZAP-70, TCR signal strength'},\n",
344
+ " 'LAT': {'priority': 7, 'category': 'TCR', 'rationale': 'LAT, TCR signaling hub'},\n",
345
+ " 'PLCG1': {'priority': 7, 'category': 'TCR', 'rationale': 'PLCγ1, downstream TCR signaling'},\n",
346
+ " 'LCP2': {'priority': 6, 'category': 'TCR', 'rationale': 'SLP-76, TCR signaling adaptor'},\n",
347
+ " \n",
348
+ " # Additional checkpoint molecules\n",
349
+ " 'CD274': {'priority': 6, 'category': 'Checkpoint', 'rationale': 'PD-L1, PD-1 ligand'},\n",
350
+ " 'BTLA': {'priority': 6, 'category': 'Checkpoint', 'rationale': 'BTLA, inhibitory receptor'},\n",
351
+ " 'CD96': {'priority': 6, 'category': 'Checkpoint', 'rationale': 'CD96, TIGIT pathway'},\n",
352
+ " \n",
353
+ " # Additional metabolic/signaling\n",
354
+ " 'STAT3': {'priority': 7, 'category': 'Signaling', 'rationale': 'STAT3, cytokine signaling'},\n",
355
+ " 'VAV1': {'priority': 6, 'category': 'TCR', 'rationale': 'Vav1, TCR signaling'},\n",
356
+ " 'ITK': {'priority': 6, 'category': 'TCR', 'rationale': 'Itk, TCR signaling kinase'},\n",
357
+ " 'RUNX3': {'priority': 6, 'category': 'TF', 'rationale': 'RUNX3, T cell differentiation'},\n",
358
+ " 'PKM': {'priority': 5, 'category': 'Metabolism', 'rationale': 'Pyruvate kinase, glycolysis'},\n",
359
+ " 'SLC2A1': {'priority': 5, 'category': 'Metabolism', 'rationale': 'GLUT1, glucose uptake'},\n",
360
+ " 'PPARA': {'priority': 5, 'category': 'Metabolism', 'rationale': 'PPARα, fatty acid oxidation'},\n",
361
+ " 'CD160': {'priority': 5, 'category': 'Checkpoint', 'rationale': 'CD160, inhibitory receptor'}\n",
362
+ "}\n",
363
+ "\n",
364
+ "# Sort by priority and select top 32\n",
365
+ "sorted_genes = sorted(high_priority_genes.items(), key=lambda x: x[1]['priority'], reverse=True)\n",
366
+ "selected_32_genes = [gene for gene, info in sorted_genes[:32]]\n",
367
+ "\n",
368
+ "print(f\"Selected 32 genes for CRISPR screen:\")\n",
369
+ "for i, (gene, info) in enumerate(sorted_genes[:32], 1):\n",
370
+ " print(f\"{i:2d}. {gene:<8} (Priority: {info['priority']}, {info['category']}) - {info['rationale']}\")\n",
371
+ "\n",
372
+ "print(f\"\\nFinal gene list: {selected_32_genes}\")\n",
373
+ "</execute>\n",
374
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
375
+ "\n",
376
+ "<observation>=== Step 3: Prioritizing genes for maximum perturbation effect ===\n",
377
+ "Selected 32 genes for CRISPR screen:\n",
378
+ " 1. TOX (Priority: 10, Master TF) - Master regulator of T cell exhaustion\n",
379
+ " 2. PDCD1 (Priority: 10, Checkpoint) - PD-1, primary exhaustion checkpoint\n",
380
+ " 3. EOMES (Priority: 9, TF) - Key exhaustion transcription factor\n",
381
+ " 4. HAVCR2 (Priority: 9, Checkpoint) - TIM-3, critical exhaustion marker\n",
382
+ " 5. LAG3 (Priority: 9, Checkpoint) - LAG-3, major exhaustion checkpoint\n",
383
+ " 6. TIGIT (Priority: 8, Checkpoint) - TIGIT, emerging exhaustion target\n",
384
+ " 7. CTLA4 (Priority: 8, Checkpoint) - CTLA-4, early checkpoint inhibitor\n",
385
+ " 8. BATF (Priority: 8, TF) - BATF, exhaustion-promoting TF\n",
386
+ " 9. HIF1A (Priority: 8, Metabolism) - HIF-1α, hypoxia and exhaustion\n",
387
+ "10. MYC (Priority: 8, Metabolism) - c-Myc, metabolic reprogramming\n",
388
+ "11. LCK (Priority: 8, TCR) - Lck, proximal TCR signaling\n",
389
+ "12. ZAP70 (Priority: 8, TCR) - ZAP-70, TCR signal strength\n",
390
+ "13. TBX21 (Priority: 7, TF) - T-bet, effector vs exhaustion balance\n",
391
+ "14. IRF4 (Priority: 7, TF) - IRF4, T cell differentiation\n",
392
+ "15. NFATC1 (Priority: 7, TF) - NFAT, chronic stimulation response\n",
393
+ "16. TOX2 (Priority: 7, TF) - TOX2, TOX family member\n",
394
+ "17. MTOR (Priority: 7, Metabolism) - mTOR, metabolic sensing\n",
395
+ "18. PRKAA1 (Priority: 7, Metabolism) - AMPK, energy sensing\n",
396
+ "19. LAT (Priority: 7, TCR) - LAT, TCR signaling hub\n",
397
+ "20. PLCG1 (Priority: 7, TCR) - PLCγ1, downstream TCR signaling\n",
398
+ "21. STAT3 (Priority: 7, Signaling) - STAT3, cytokine signaling\n",
399
+ "22. LDHA (Priority: 6, Metabolism) - Lactate metabolism in exhaustion\n",
400
+ "23. LCP2 (Priority: 6, TCR) - SLP-76, TCR signaling adaptor\n",
401
+ "24. CD274 (Priority: 6, Checkpoint) - PD-L1, PD-1 ligand\n",
402
+ "25. BTLA (Priority: 6, Checkpoint) - BTLA, inhibitory receptor\n",
403
+ "26. CD96 (Priority: 6, Checkpoint) - CD96, TIGIT pathway\n",
404
+ "27. VAV1 (Priority: 6, TCR) - Vav1, TCR signaling\n",
405
+ "28. ITK (Priority: 6, TCR) - Itk, TCR signaling kinase\n",
406
+ "29. RUNX3 (Priority: 6, TF) - RUNX3, T cell differentiation\n",
407
+ "30. PKM (Priority: 5, Metabolism) - Pyruvate kinase, glycolysis\n",
408
+ "31. SLC2A1 (Priority: 5, Metabolism) - GLUT1, glucose uptake\n",
409
+ "32. PPARA (Priority: 5, Metabolism) - PPARα, fatty acid oxidation\n",
410
+ "\n",
411
+ "Final gene list: ['TOX', 'PDCD1', 'EOMES', 'HAVCR2', 'LAG3', 'TIGIT', 'CTLA4', 'BATF', 'HIF1A', 'MYC', 'LCK', 'ZAP70', 'TBX21', 'IRF4', 'NFATC1', 'TOX2', 'MTOR', 'PRKAA1', 'LAT', 'PLCG1', 'STAT3', 'LDHA', 'LCP2', 'CD274', 'BTLA', 'CD96', 'VAV1', 'ITK', 'RUNX3', 'PKM', 'SLC2A1', 'PPARA']\n",
412
+ "</observation>\n",
413
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
414
+ "\n",
415
+ "1. [✓] Research T cell exhaustion biology and identify key pathways (completed)\n",
416
+ "2. [✓] Query databases for genes involved in T cell exhaustion, TCR signaling, and chronic stimulation (completed)\n",
417
+ "3. [✓] Identify genes from exhaustion-related pathways (checkpoint inhibitors, metabolic regulators, transcription factors) (completed)\n",
418
+ "4. [✓] Select genes that are likely to show differential effects between acute vs chronic stimulation (completed)\n",
419
+ "5. [ ] Design sgRNAs for the selected 32 genes\n",
420
+ "6. [ ] Validate the gene list and provide experimental design recommendations\n",
421
+ "\n",
422
+ "<execute>\n",
423
+ "# Now let's design sgRNAs for our selected genes\n",
424
+ "print(\"=== Step 4: Designing sgRNAs for selected genes ===\")\n",
425
+ "\n",
426
+ "data_lake_path = \"/dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake\"\n",
427
+ "\n",
428
+ "# We'll design sgRNAs for each of our 32 selected genes\n",
429
+ "selected_genes = ['TOX', 'PDCD1', 'EOMES', 'HAVCR2', 'LAG3', 'TIGIT', 'CTLA4', 'BATF', 'HIF1A', 'MYC', \n",
430
+ " 'LCK', 'ZAP70', 'TBX21', 'IRF4', 'NFATC1', 'TOX2', 'MTOR', 'PRKAA1', 'LAT', 'PLCG1', \n",
431
+ " 'STAT3', 'LDHA', 'LCP2', 'CD274', 'BTLA', 'CD96', 'VAV1', 'ITK', 'RUNX3', 'PKM', \n",
432
+ " 'SLC2A1', 'PPARA']\n",
433
+ "\n",
434
+ "print(f\"Designing sgRNAs for {len(selected_genes)} genes...\")\n",
435
+ "\n",
436
+ "# Let's try to design sgRNAs for a few key genes as examples\n",
437
+ "key_genes_for_sgrna = ['TOX', 'PDCD1', 'EOMES', 'HAVCR2', 'LAG3']\n",
438
+ "\n",
439
+ "sgrna_results = {}\n",
440
+ "for gene in key_genes_for_sgrna:\n",
441
+ " try:\n",
442
+ " print(f\"\\nDesigning sgRNAs for {gene}...\")\n",
443
+ " result = design_knockout_sgrna(\n",
444
+ " gene_name=gene,\n",
445
+ " data_lake_path=data_lake_path,\n",
446
+ " species=\"human\",\n",
447
+ " num_guides=3 # Get 3 guides per gene for redundancy\n",
448
+ " )\n",
449
+ " sgrna_results[gene] = result\n",
450
+ " print(f\"Successfully designed sgRNAs for {gene}\")\n",
451
+ " if result:\n",
452
+ " print(f\"Result type: {type(result)}\")\n",
453
+ " except Exception as e:\n",
454
+ " print(f\"Error designing sgRNAs for {gene}: {str(e)}\")\n",
455
+ " sgrna_results[gene] = None\n",
456
+ "\n",
457
+ "print(f\"\\nsgRNA design completed for {len(sgrna_results)} genes\")\n",
458
+ "</execute>\n",
459
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
460
+ "\n",
461
+ "<observation>=== Step 4: Designing sgRNAs for selected genes ===\n",
462
+ "Designing sgRNAs for 32 genes...\n",
463
+ "\n",
464
+ "Designing sgRNAs for TOX...\n",
465
+ "Error designing sgRNAs for TOX: Library file for human not found at path: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake/sgRNA/KO_SP_human.txt\n",
466
+ "\n",
467
+ "Designing sgRNAs for PDCD1...\n",
468
+ "Error designing sgRNAs for PDCD1: Library file for human not found at path: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake/sgRNA/KO_SP_human.txt\n",
469
+ "\n",
470
+ "Designing sgRNAs for EOMES...\n",
471
+ "Error designing sgRNAs for EOMES: Library file for human not found at path: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake/sgRNA/KO_SP_human.txt\n",
472
+ "\n",
473
+ "Designing sgRNAs for HAVCR2...\n",
474
+ "Error designing sgRNAs for HAVCR2: Library file for human not found at path: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake/sgRNA/KO_SP_human.txt\n",
475
+ "\n",
476
+ "Designing sgRNAs for LAG3...\n",
477
+ "Error designing sgRNAs for LAG3: Library file for human not found at path: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake/sgRNA/KO_SP_human.txt\n",
478
+ "\n",
479
+ "sgRNA design completed for 5 genes\n",
480
+ "</observation>\n",
481
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
482
+ "\n",
483
+ "<execute>\n",
484
+ "# Let's check what's available in the data lake for sgRNA design\n",
485
+ "import os\n",
486
+ "\n",
487
+ "print(\"=== Checking data lake contents for sgRNA libraries ===\")\n",
488
+ "data_lake_path = \"/dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake\"\n",
489
+ "\n",
490
+ "# Check if the directory exists\n",
491
+ "if os.path.exists(data_lake_path):\n",
492
+ " print(f\"Data lake path exists: {data_lake_path}\")\n",
493
+ " \n",
494
+ " # List contents\n",
495
+ " contents = os.listdir(data_lake_path)\n",
496
+ " print(f\"Contents of data lake: {contents}\")\n",
497
+ " \n",
498
+ " # Check for sgRNA directory\n",
499
+ " sgrna_path = os.path.join(data_lake_path, \"sgRNA\")\n",
500
+ " if os.path.exists(sgrna_path):\n",
501
+ " print(f\"sgRNA directory exists\")\n",
502
+ " sgrna_contents = os.listdir(sgrna_path)\n",
503
+ " print(f\"sgRNA directory contents: {sgrna_contents}\")\n",
504
+ " else:\n",
505
+ " print(\"sgRNA directory does not exist\")\n",
506
+ " \n",
507
+ " # Check for any files that might contain sgRNA data\n",
508
+ " for item in contents:\n",
509
+ " item_path = os.path.join(data_lake_path, item)\n",
510
+ " if os.path.isfile(item_path) and ('sgrna' in item.lower() or 'guide' in item.lower() or 'crispr' in item.lower()):\n",
511
+ " print(f\"Found potential sgRNA file: {item}\")\n",
512
+ "else:\n",
513
+ " print(f\"Data lake path does not exist: {data_lake_path}\")\n",
514
+ "</execute>\n",
515
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
516
+ "\n",
517
+ "<observation>=== Checking data lake contents for sgRNA libraries ===\n",
518
+ "Data lake path exists: /dfs/project/bioagentos/biomni_data_test/biomni_data/data_lake\n",
519
+ "Contents of data lake: ['co-fractionation.csv', 'Cosmic_CompleteCNA_v101_GRCh38.tsv.gz', 'Cosmic_Breakpoints_v101_GRCh38.csv', 'two-hybrid.csv', 'Cosmic_CompleteGeneExpression_v101_GRCh38.tsv.gz', 'broad_repurposing_hub_phase_moa_target_info.csv', 'hp.obo', 'BindingDB_All_202409.tsv', 'genebass_synonymous_filtered.pkl', 'omim.csv', 'miRTarBase_microRNA_target_interaction.csv', 'gtex_tissue_gene_tpm.csv', 'Cosmic_CompleteDifferentialMethylation_v101_GRCh38.tsv.gz', 'synthetic_lethality.csv', 'proximity_label-ms.csv', 'synthetic_growth_defect.csv', 'broad_repurposing_hub_molecule_with_smiles.csv', 'czi_census_datasets_v4.csv', 'mousemine_mh_hallmark_geneset.csv', 'mousemine_m1_positional_geneset.csv', 'genebass_pLoF_filtered.pkl', 'Virus-Host_PPI_P-HIPSTER_2020.csv', 'msigdb_human_c8_celltype_signature_geneset.csv', 'msigdb_human_c6_oncogenic_signature_geneset.csv', 'Cosmic_CancerGeneCensus_v101_GRCh38.csv', 'gwas_catalog.pkl', 'miRTarBase_microRNA_target_interaction_pubmed_abtract.txt', 'Cosmic_CancerGeneCensusHallmarksOfCancer_v101_GRCh38.csv', 'mousemine_m5_ontology_geneset.csv', 'go-plus.json', 'gene_info.csv', 'msigdb_human_h_hallmark_geneset.csv', 'msigdb_human_c5_ontology_geneset.csv', 'variant_table.csv', 'enamine_cloud_library_smiles.pkl', 'Cosmic_MutantCensus_v101_GRCh38.csv', 'reconstituted_complex.csv', 'DisGeNET.csv', 'msigdb_human_c3_regulatory_target_geneset.csv', 'genebass_missense_LC_filtered.pkl', 'Cosmic_Genes_v101_GRCh38.csv', 'mousemine_m3_regulatory_target_geneset.csv', 'msigdb_human_c1_positional_geneset.csv', 'msigdb_human_c4_computational_geneset.csv', 'miRDB_v6.0_results.csv', 'Cosmic_ResistanceMutations_v101_GRCh38.csv', 'msigdb_human_c2_curated_geneset.csv', 'Cosmic_GenomeScreensMutant_v101_GRCh38.tsv.gz', 'mousemine_m2_curated_geneset.csv', 'affinity_capture-ms.csv', 'msigdb_human_c3_subset_transcription_factor_targets_from_GTRD.csv', 'miRTarBase_MicroRNA_Target_Sites.csv', 'msigdb_human_c7_immunologic_signature_geneset.csv', 'McPAS-TCR.csv', 'dosage_growth_defect.csv', 'Cosmic_Classification_v101_GRCh38.csv', 'affinity_capture-rna.csv', 'proteinatlas.tsv', 'genetic_interaction.csv', 'synthetic_rescue.csv', 'Cosmic_Fusion_v101_GRCh38.csv', 'Cosmic_ClassificationPaper_v101_GRCh38.csv', 'mousemine_m8_celltype_signature_geneset.csv', 'marker_celltype.csv']\n",
520
+ "sgRNA directory does not exist\n",
521
+ "</observation>\n",
522
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
523
+ "\n",
524
+ "5. [✗] Design sgRNAs for the selected 32 genes (failed because sgRNA library not available in data lake)\n",
525
+ "5. [ ] Provide theoretical sgRNA design recommendations and validate gene selection\n",
526
+ "6. [ ] Provide comprehensive experimental design recommendations\n",
527
+ "\n",
528
+ "<execute>\n",
529
+ "# Since we don't have access to the sgRNA library, let's provide theoretical sgRNA design recommendations\n",
530
+ "# and create a comprehensive experimental plan\n",
531
+ "\n",
532
+ "print(\"=== Step 5: Theoretical sgRNA Design and Validation ===\")\n",
533
+ "\n",
534
+ "selected_genes = ['TOX', 'PDCD1', 'EOMES', 'HAVCR2', 'LAG3', 'TIGIT', 'CTLA4', 'BATF', 'HIF1A', 'MYC', \n",
535
+ " 'LCK', 'ZAP70', 'TBX21', 'IRF4', 'NFATC1', 'TOX2', 'MTOR', 'PRKAA1', 'LAT', 'PLCG1', \n",
536
+ " 'STAT3', 'LDHA', 'LCP2', 'CD274', 'BTLA', 'CD96', 'VAV1', 'ITK', 'RUNX3', 'PKM', \n",
537
+ " 'SLC2A1', 'PPARA']\n",
538
+ "\n",
539
+ "# Categorize genes by functional category for analysis\n",
540
+ "gene_categories = {\n",
541
+ " 'Immune Checkpoints': ['PDCD1', 'HAVCR2', 'LAG3', 'TIGIT', 'CTLA4', 'CD274', 'BTLA', 'CD96'],\n",
542
+ " 'Transcription Factors': ['TOX', 'EOMES', 'BATF', 'TBX21', 'IRF4', 'NFATC1', 'TOX2', 'RUNX3'],\n",
543
+ " 'Metabolic Regulators': ['HIF1A', 'MYC', 'MTOR', 'PRKAA1', 'LDHA', 'PKM', 'SLC2A1', 'PPARA'],\n",
544
+ " 'TCR Signaling': ['LCK', 'ZAP70', 'LAT', 'PLCG1', 'LCP2', 'VAV1', 'ITK'],\n",
545
+ " 'Cytokine Signaling': ['STAT3']\n",
546
+ "}\n",
547
+ "\n",
548
+ "print(\"Gene categorization:\")\n",
549
+ "for category, genes in gene_categories.items():\n",
550
+ " print(f\"{category}: {len(genes)} genes\")\n",
551
+ " print(f\" {', '.join(genes)}\")\n",
552
+ " print()\n",
553
+ "\n",
554
+ "# Expected effects on T cell exhaustion\n",
555
+ "expected_effects = {\n",
556
+ " 'TOX': 'KO should reduce exhaustion (master exhaustion TF)',\n",
557
+ " 'PDCD1': 'KO should reduce exhaustion (remove PD-1 checkpoint)',\n",
558
+ " 'EOMES': 'KO should reduce exhaustion (exhaustion-promoting TF)',\n",
559
+ " 'HAVCR2': 'KO should reduce exhaustion (remove TIM-3 checkpoint)',\n",
560
+ " 'LAG3': 'KO should reduce exhaustion (remove LAG-3 checkpoint)',\n",
561
+ " 'TIGIT': 'KO should reduce exhaustion (remove TIGIT checkpoint)',\n",
562
+ " 'CTLA4': 'KO should enhance early activation',\n",
563
+ " 'BATF': 'KO should reduce exhaustion (exhaustion-promoting TF)',\n",
564
+ " 'HIF1A': 'KO may reduce exhaustion (metabolic stress response)',\n",
565
+ " 'MYC': 'KO may increase exhaustion (metabolic reprogramming)',\n",
566
+ " 'LCK': 'KO should reduce TCR signaling strength',\n",
567
+ " 'ZAP70': 'KO should reduce TCR signaling strength',\n",
568
+ " 'TBX21': 'KO should shift toward exhaustion (effector TF)',\n",
569
+ " 'IRF4': 'KO may affect T cell differentiation balance',\n",
570
+ " 'NFATC1': 'KO may reduce chronic stimulation response',\n",
571
+ " 'MTOR': 'KO should affect metabolic reprogramming',\n",
572
+ " 'STAT3': 'KO should affect cytokine responsiveness'\n",
573
+ "}\n",
574
+ "\n",
575
+ "print(\"Expected functional outcomes:\")\n",
576
+ "for gene in selected_genes[:17]: # Show first 17 as examples\n",
577
+ " if gene in expected_effects:\n",
578
+ " print(f\"{gene}: {expected_effects[gene]}\")\n",
579
+ "\n",
580
+ "print(\"\\nsgRNA Design Recommendations:\")\n",
581
+ "print(\"1. Use 3-4 sgRNAs per gene for redundancy\")\n",
582
+ "print(\"2. Target early exons to ensure knockout\")\n",
583
+ "print(\"3. Include non-targeting controls (scrambled guides)\")\n",
584
+ "print(\"4. Include positive controls (known exhaustion regulators)\")\n",
585
+ "print(\"5. Use established sgRNA design tools (e.g., Broad GPP portal, CHOPCHOP)\")\n",
586
+ "</execute>\n",
587
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
588
+ "\n",
589
+ "<observation>=== Step 5: Theoretical sgRNA Design and Validation ===\n",
590
+ "Gene categorization:\n",
591
+ "Immune Checkpoints: 8 genes\n",
592
+ " PDCD1, HAVCR2, LAG3, TIGIT, CTLA4, CD274, BTLA, CD96\n",
593
+ "\n",
594
+ "Transcription Factors: 8 genes\n",
595
+ " TOX, EOMES, BATF, TBX21, IRF4, NFATC1, TOX2, RUNX3\n",
596
+ "\n",
597
+ "Metabolic Regulators: 8 genes\n",
598
+ " HIF1A, MYC, MTOR, PRKAA1, LDHA, PKM, SLC2A1, PPARA\n",
599
+ "\n",
600
+ "TCR Signaling: 7 genes\n",
601
+ " LCK, ZAP70, LAT, PLCG1, LCP2, VAV1, ITK\n",
602
+ "\n",
603
+ "Cytokine Signaling: 1 genes\n",
604
+ " STAT3\n",
605
+ "\n",
606
+ "Expected functional outcomes:\n",
607
+ "TOX: KO should reduce exhaustion (master exhaustion TF)\n",
608
+ "PDCD1: KO should reduce exhaustion (remove PD-1 checkpoint)\n",
609
+ "EOMES: KO should reduce exhaustion (exhaustion-promoting TF)\n",
610
+ "HAVCR2: KO should reduce exhaustion (remove TIM-3 checkpoint)\n",
611
+ "LAG3: KO should reduce exhaustion (remove LAG-3 checkpoint)\n",
612
+ "TIGIT: KO should reduce exhaustion (remove TIGIT checkpoint)\n",
613
+ "CTLA4: KO should enhance early activation\n",
614
+ "BATF: KO should reduce exhaustion (exhaustion-promoting TF)\n",
615
+ "HIF1A: KO may reduce exhaustion (metabolic stress response)\n",
616
+ "MYC: KO may increase exhaustion (metabolic reprogramming)\n",
617
+ "LCK: KO should reduce TCR signaling strength\n",
618
+ "ZAP70: KO should reduce TCR signaling strength\n",
619
+ "TBX21: KO should shift toward exhaustion (effector TF)\n",
620
+ "IRF4: KO may affect T cell differentiation balance\n",
621
+ "NFATC1: KO may reduce chronic stimulation response\n",
622
+ "MTOR: KO should affect metabolic reprogramming\n",
623
+ "\n",
624
+ "sgRNA Design Recommendations:\n",
625
+ "1. Use 3-4 sgRNAs per gene for redundancy\n",
626
+ "2. Target early exons to ensure knockout\n",
627
+ "3. Include non-targeting controls (scrambled guides)\n",
628
+ "4. Include positive controls (known exhaustion regulators)\n",
629
+ "5. Use established sgRNA design tools (e.g., Broad GPP portal, CHOPCHOP)\n",
630
+ "</observation>\n",
631
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
632
+ "\n",
633
+ "<execute>\n",
634
+ "# Now let's create a comprehensive experimental design\n",
635
+ "print(\"=== Step 6: Comprehensive Experimental Design for CRISPR Screen ===\")\n",
636
+ "\n",
637
+ "experimental_design = {\n",
638
+ " 'objective': 'Identify genes regulating T cell exhaustion through differential TCR signaling between acute and chronic stimulation',\n",
639
+ " 'readout': 'TCR signaling strength measured by phospho-flow cytometry, cytokine production, and transcriptional markers',\n",
640
+ " 'conditions': {\n",
641
+ " 'acute': 'IL-2 only (maintains T cell survival without chronic stimulation)',\n",
642
+ " 'chronic': 'Anti-CD3 + IL-2 (chronic TCR stimulation leading to exhaustion)'\n",
643
+ " }\n",
644
+ "}\n",
645
+ "\n",
646
+ "print(\"EXPERIMENTAL DESIGN OVERVIEW\")\n",
647
+ "print(\"=\"*50)\n",
648
+ "print(f\"Objective: {experimental_design['objective']}\")\n",
649
+ "print(f\"Primary Readout: {experimental_design['readout']}\")\n",
650
+ "print()\n",
651
+ "\n",
652
+ "print(\"STIMULATION CONDITIONS:\")\n",
653
+ "print(f\"1. Acute condition: {experimental_design['conditions']['acute']}\")\n",
654
+ "print(f\"2. Chronic condition: {experimental_design['conditions']['chronic']}\")\n",
655
+ "print()\n",
656
+ "\n",
657
+ "print(\"SELECTED 32 GENES FOR CRISPR SCREEN:\")\n",
658
+ "print(\"=\"*50)\n",
659
+ "\n",
660
+ "# Create a detailed gene table\n",
661
+ "gene_info = []\n",
662
+ "for i, gene in enumerate(selected_genes, 1):\n",
663
+ " category = None\n",
664
+ " for cat, genes in gene_categories.items():\n",
665
+ " if gene in genes:\n",
666
+ " category = cat\n",
667
+ " break\n",
668
+ " \n",
669
+ " gene_info.append({\n",
670
+ " 'rank': i,\n",
671
+ " 'gene': gene,\n",
672
+ " 'category': category,\n",
673
+ " 'expected_effect': expected_effects.get(gene, 'To be determined')\n",
674
+ " })\n",
675
+ "\n",
676
+ "# Print gene table\n",
677
+ "print(f\"{'Rank':<4} {'Gene':<8} {'Category':<20} {'Expected Effect'}\")\n",
678
+ "print(\"-\" * 80)\n",
679
+ "for info in gene_info:\n",
680
+ " print(f\"{info['rank']:<4} {info['gene']:<8} {info['category']:<20} {info['expected_effect'][:50]}\")\n",
681
+ "\n",
682
+ "print()\n",
683
+ "print(\"EXPERIMENTAL PROTOCOL:\")\n",
684
+ "print(\"=\"*50)\n",
685
+ "\n",
686
+ "protocol_steps = [\n",
687
+ " \"1. T Cell Isolation and Activation\",\n",
688
+ " \" - Isolate primary CD8+ T cells from healthy donors\",\n",
689
+ " \" - Activate with anti-CD3/CD28 for 2-3 days\",\n",
690
+ " \"\",\n",
691
+ " \"2. CRISPR-Cas9 Delivery\",\n",
692
+ " \" - Electroporate Cas9 RNP complexes with sgRNA library\",\n",
693
+ " \" - 3-4 sgRNAs per gene + non-targeting controls\",\n",
694
+ " \" - Allow 3-5 days for gene knockout\",\n",
695
+ " \"\",\n",
696
+ " \"3. Stimulation Conditions (7-14 days)\",\n",
697
+ " \" - Acute: IL-2 (100 U/ml) only\",\n",
698
+ " \" - Chronic: Anti-CD3 (1 μg/ml) + IL-2 (100 U/ml)\",\n",
699
+ " \" - Refresh media every 2-3 days\",\n",
700
+ " \"\",\n",
701
+ " \"4. Readout Measurements\",\n",
702
+ " \" - TCR signaling: Phospho-flow for pZAP70, pERK, pS6\",\n",
703
+ " \" - Exhaustion markers: PD-1, TIM-3, LAG-3 expression\",\n",
704
+ " \" - Function: IFN-γ, TNF-α, IL-2 production\",\n",
705
+ " \" - Transcription: RNA-seq for exhaustion signatures\",\n",
706
+ " \"\",\n",
707
+ " \"5. Data Analysis\",\n",
708
+ " \" - Calculate perturbation scores for each gene\",\n",
709
+ " \" - Identify genes with differential effects between conditions\",\n",
710
+ " \" - Validate top hits with individual knockouts\"\n",
711
+ "]\n",
712
+ "\n",
713
+ "for step in protocol_steps:\n",
714
+ " print(step)\n",
715
+ "\n",
716
+ "print()\n",
717
+ "print(\"KEY CONTROLS:\")\n",
718
+ "print(\"=\"*20)\n",
719
+ "controls = [\n",
720
+ " \"- Non-targeting sgRNAs (negative control)\",\n",
721
+ " \"- Known exhaustion genes (positive control): TOX, PDCD1\",\n",
722
+ " \"- Essential genes (toxicity control): RPL23, RPS19\",\n",
723
+ " \"- Unstimulated cells (baseline)\",\n",
724
+ " \"- Wild-type cells (no CRISPR)\"\n",
725
+ "]\n",
726
+ "\n",
727
+ "for control in controls:\n",
728
+ " print(control)\n",
729
+ "\n",
730
+ "print()\n",
731
+ "print(\"EXPECTED OUTCOMES:\")\n",
732
+ "print(\"=\"*20)\n",
733
+ "outcomes = [\n",
734
+ " \"1. Genes that reduce exhaustion when knocked out:\",\n",
735
+ " \" - Should show enhanced TCR signaling in chronic condition\",\n",
736
+ " \" - Examples: TOX, PDCD1, HAVCR2, LAG3, EOMES\",\n",
737
+ " \"\",\n",
738
+ " \"2. Genes that promote effector function:\",\n",
739
+ " \" - Should show reduced signaling when knocked out\",\n",
740
+ " \" - Examples: TBX21, MYC (context-dependent)\",\n",
741
+ " \"\",\n",
742
+ " \"3. Metabolic regulators:\",\n",
743
+ " \" - Should show differential effects on sustained signaling\",\n",
744
+ " \" - Examples: MTOR, HIF1A, PRKAA1\",\n",
745
+ " \"\",\n",
746
+ " \"4. TCR signaling components:\",\n",
747
+ " \" - Should show proportional effects on signaling strength\",\n",
748
+ " \" - Examples: LCK, ZAP70, LAT\"\n",
749
+ "]\n",
750
+ "\n",
751
+ "for outcome in outcomes:\n",
752
+ " print(outcome)\n",
753
+ "</execute>\n",
754
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
755
+ "\n",
756
+ "<observation>=== Step 6: Comprehensive Experimental Design for CRISPR Screen ===\n",
757
+ "EXPERIMENTAL DESIGN OVERVIEW\n",
758
+ "==================================================\n",
759
+ "Objective: Identify genes regulating T cell exhaustion through differential TCR signaling between acute and chronic stimulation\n",
760
+ "Primary Readout: TCR signaling strength measured by phospho-flow cytometry, cytokine production, and transcriptional markers\n",
761
+ "\n",
762
+ "STIMULATION CONDITIONS:\n",
763
+ "1. Acute condition: IL-2 only (maintains T cell survival without chronic stimulation)\n",
764
+ "2. Chronic condition: Anti-CD3 + IL-2 (chronic TCR stimulation leading to exhaustion)\n",
765
+ "\n",
766
+ "SELECTED 32 GENES FOR CRISPR SCREEN:\n",
767
+ "==================================================\n",
768
+ "Rank Gene Category Expected Effect\n",
769
+ "--------------------------------------------------------------------------------\n",
770
+ "1 TOX Transcription Factors KO should reduce exhaustion (master exhaustion TF)\n",
771
+ "2 PDCD1 Immune Checkpoints KO should reduce exhaustion (remove PD-1 checkpoin\n",
772
+ "3 EOMES Transcription Factors KO should reduce exhaustion (exhaustion-promoting \n",
773
+ "4 HAVCR2 Immune Checkpoints KO should reduce exhaustion (remove TIM-3 checkpoi\n",
774
+ "5 LAG3 Immune Checkpoints KO should reduce exhaustion (remove LAG-3 checkpoi\n",
775
+ "6 TIGIT Immune Checkpoints KO should reduce exhaustion (remove TIGIT checkpoi\n",
776
+ "7 CTLA4 Immune Checkpoints KO should enhance early activation\n",
777
+ "8 BATF Transcription Factors KO should reduce exhaustion (exhaustion-promoting \n",
778
+ "9 HIF1A Metabolic Regulators KO may reduce exhaustion (metabolic stress respons\n",
779
+ "10 MYC Metabolic Regulators KO may increase exhaustion (metabolic reprogrammin\n",
780
+ "11 LCK TCR Signaling KO should reduce TCR signaling strength\n",
781
+ "12 ZAP70 TCR Signaling KO should reduce TCR signaling strength\n",
782
+ "13 TBX21 Transcription Factors KO should shift toward exhaustion (effector TF)\n",
783
+ "14 IRF4 Transcription Factors KO may affect T cell differentiation balance\n",
784
+ "15 NFATC1 Transcription Factors KO may reduce chronic stimulation response\n",
785
+ "16 TOX2 Transcription Factors To be determined\n",
786
+ "17 MTOR Metabolic Regulators KO should affect metabolic reprogramming\n",
787
+ "18 PRKAA1 Metabolic Regulators To be determined\n",
788
+ "19 LAT TCR Signaling To be determined\n",
789
+ "20 PLCG1 TCR Signaling To be determined\n",
790
+ "21 STAT3 Cytokine Signaling KO should affect cytokine responsiveness\n",
791
+ "22 LDHA Metabolic Regulators To be determined\n",
792
+ "23 LCP2 TCR Signaling To be determined\n",
793
+ "24 CD274 Immune Checkpoints To be determined\n",
794
+ "25 BTLA Immune Checkpoints To be determined\n",
795
+ "26 CD96 Immune Checkpoints To be determined\n",
796
+ "27 VAV1 TCR Signaling To be determined\n",
797
+ "28 ITK TCR Signaling To be determined\n",
798
+ "29 RUNX3 Transcription Factors To be determined\n",
799
+ "30 PKM Metabolic Regulators To be determined\n",
800
+ "31 SLC2A1 Metabolic Regulators To be determined\n",
801
+ "32 PPARA Metabolic Regulators To be determined\n",
802
+ "\n",
803
+ "EXPERIMENTAL PROTOCOL:\n",
804
+ "==================================================\n",
805
+ "1. T Cell Isolation and Activation\n",
806
+ " - Isolate primary CD8+ T cells from healthy donors\n",
807
+ " - Activate with anti-CD3/CD28 for 2-3 days\n",
808
+ "\n",
809
+ "2. CRISPR-Cas9 Delivery\n",
810
+ " - Electroporate Cas9 RNP complexes with sgRNA library\n",
811
+ " - 3-4 sgRNAs per gene + non-targeting controls\n",
812
+ " - Allow 3-5 days for gene knockout\n",
813
+ "\n",
814
+ "3. Stimulation Conditions (7-14 days)\n",
815
+ " - Acute: IL-2 (100 U/ml) only\n",
816
+ " - Chronic: Anti-CD3 (1 μg/ml) + IL-2 (100 U/ml)\n",
817
+ " - Refresh media every 2-3 days\n",
818
+ "\n",
819
+ "4. Readout Measurements\n",
820
+ " - TCR signaling: Phospho-flow for pZAP70, pERK, pS6\n",
821
+ " - Exhaustion markers: PD-1, TIM-3, LAG-3 expression\n",
822
+ " - Function: IFN-γ, TNF-α, IL-2 production\n",
823
+ " - Transcription: RNA-seq for exhaustion signatures\n",
824
+ "\n",
825
+ "5. Data Analysis\n",
826
+ " - Calculate perturbation scores for each gene\n",
827
+ " - Identify genes with differential effects between conditions\n",
828
+ " - Validate top hits with individual knockouts\n",
829
+ "\n",
830
+ "KEY CONTROLS:\n",
831
+ "====================\n",
832
+ "- Non-targeting sgRNAs (negative control)\n",
833
+ "- Known exhaustion genes (positive control): TOX, PDCD1\n",
834
+ "- Essential genes (toxicity control): RPL23, RPS19\n",
835
+ "- Unstimulated cells (baseline)\n",
836
+ "- Wild-type cells (no CRISPR)\n",
837
+ "\n",
838
+ "EXPECTED OUTCOMES:\n",
839
+ "====================\n",
840
+ "1. Genes that reduce exhaustion when knocked out:\n",
841
+ " - Should show enhanced TCR signaling in chronic condition\n",
842
+ " - Examples: TOX, PDCD1, HAVCR2, LAG3, EOMES\n",
843
+ "\n",
844
+ "2. Genes that promote effector function:\n",
845
+ " - Should show reduced signaling when knocked out\n",
846
+ " - Examples: TBX21, MYC (context-dependent)\n",
847
+ "\n",
848
+ "3. Metabolic regulators:\n",
849
+ " - Should show differential effects on sustained signaling\n",
850
+ " - Examples: MTOR, HIF1A, PRKAA1\n",
851
+ "\n",
852
+ "4. TCR signaling components:\n",
853
+ " - Should show proportional effects on signaling strength\n",
854
+ " - Examples: LCK, ZAP70, LAT\n",
855
+ "</observation>\n",
856
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
857
+ "\n",
858
+ "5. [✓] Provide theoretical sgRNA design recommendations and validate gene selection (completed)\n",
859
+ "6. [✓] Provide comprehensive experimental design recommendations (completed)\n",
860
+ "\n",
861
+ "<solution>\n",
862
+ "# CRISPR Screen to Identify T Cell Exhaustion Regulators\n",
863
+ "\n",
864
+ "## Selected 32 Genes for Maximum Perturbation Effect\n",
865
+ "\n",
866
+ "Based on comprehensive analysis of T cell exhaustion biology, I have identified 32 genes that will maximize perturbation effects in your CRISPR screen measuring differential TCR signaling between acute (IL-2 only) and chronic (anti-CD3 + IL-2) stimulation conditions:\n",
867
+ "\n",
868
+ "### Gene List (Ranked by Priority):\n",
869
+ "\n",
870
+ "**Immune Checkpoint Molecules (8 genes):**\n",
871
+ "1. **PDCD1** (PD-1) - Primary exhaustion checkpoint receptor\n",
872
+ "2. **HAVCR2** (TIM-3) - Critical exhaustion marker and checkpoint\n",
873
+ "3. **LAG3** - Major exhaustion checkpoint receptor\n",
874
+ "4. **TIGIT** - Emerging exhaustion checkpoint target\n",
875
+ "5. **CTLA4** - Early checkpoint inhibitor\n",
876
+ "6. **CD274** (PD-L1) - PD-1 ligand\n",
877
+ "7. **BTLA** - Inhibitory receptor\n",
878
+ "8. **CD96** - TIGIT pathway component\n",
879
+ "\n",
880
+ "**Transcription Factors (8 genes):**\n",
881
+ "9. **TOX** - Master regulator of T cell exhaustion\n",
882
+ "10. **EOMES** - Key exhaustion-promoting transcription factor\n",
883
+ "11. **BATF** - Exhaustion-promoting transcription factor\n",
884
+ "12. **TBX21** (T-bet) - Effector vs exhaustion balance\n",
885
+ "13. **IRF4** - T cell differentiation regulator\n",
886
+ "14. **NFATC1** - Chronic stimulation response factor\n",
887
+ "15. **TOX2** - TOX family member\n",
888
+ "16. **RUNX3** - T cell differentiation factor\n",
889
+ "\n",
890
+ "**Metabolic Regulators (8 genes):**\n",
891
+ "17. **HIF1A** - Hypoxia and exhaustion regulator\n",
892
+ "18. **MYC** - Metabolic reprogramming master regulator\n",
893
+ "19. **MTOR** - Metabolic sensing and signaling\n",
894
+ "20. **PRKAA1** (AMPK) - Energy sensing kinase\n",
895
+ "21. **LDHA** - Lactate metabolism in exhaustion\n",
896
+ "22. **PKM** - Pyruvate kinase, glycolysis regulator\n",
897
+ "23. **SLC2A1** (GLUT1) - Glucose uptake transporter\n",
898
+ "24. **PPARA** - Fatty acid oxidation regulator\n",
899
+ "\n",
900
+ "**TCR Signaling Components (7 genes):**\n",
901
+ "25. **LCK** - Proximal TCR signaling kinase\n",
902
+ "26. **ZAP70** - TCR signal strength regulator\n",
903
+ "27. **LAT** - TCR signaling hub adaptor\n",
904
+ "28. **PLCG1** (PLCγ1) - Downstream TCR signaling\n",
905
+ "29. **LCP2** (SLP-76) - TCR signaling adaptor\n",
906
+ "30. **VAV1** - TCR signaling regulator\n",
907
+ "31. **ITK** - TCR signaling kinase\n",
908
+ "\n",
909
+ "**Cytokine Signaling (1 gene):**\n",
910
+ "32. **STAT3** - Cytokine signaling transcription factor\n",
911
+ "\n",
912
+ "## Experimental Design Overview\n",
913
+ "\n",
914
+ "### Objective:\n",
915
+ "Identify genes that regulate T cell exhaustion by measuring differential TCR signaling responses between acute and chronic stimulation conditions.\n",
916
+ "\n",
917
+ "### Key Features:\n",
918
+ "- **Balanced gene selection** across 4 major functional categories\n",
919
+ "- **High-priority targets** based on literature evidence and therapeutic relevance\n",
920
+ "- **Expected differential effects** between acute vs chronic conditions\n",
921
+ "- **Multiple readouts** for comprehensive phenotyping\n",
922
+ "\n",
923
+ "### Stimulation Conditions:\n",
924
+ "- **Acute**: IL-2 only (maintains survival without exhaustion)\n",
925
+ "- **Chronic**: Anti-CD3 + IL-2 (induces exhaustion through chronic TCR stimulation)\n",
926
+ "\n",
927
+ "### Primary Readouts:\n",
928
+ "1. **TCR signaling strength**: Phospho-flow cytometry (pZAP70, pERK, pS6)\n",
929
+ "2. **Exhaustion markers**: PD-1, TIM-3, LAG-3 surface expression\n",
930
+ "3. **Functional capacity**: Cytokine production (IFN-γ, TNF-α, IL-2)\n",
931
+ "4. **Transcriptional state**: RNA-seq for exhaustion gene signatures\n",
932
+ "\n",
933
+ "### Expected Outcomes:\n",
934
+ "- **Checkpoint inhibitors** (PDCD1, HAVCR2, LAG3): KO should enhance signaling in chronic condition\n",
935
+ "- **Exhaustion TFs** (TOX, EOMES, BATF): KO should reduce exhaustion phenotype\n",
936
+ "- **Metabolic regulators** (MTOR, HIF1A, MYC): Context-dependent effects on sustained signaling\n",
937
+ "- **TCR components** (LCK, ZAP70, LAT): Proportional effects on signaling strength\n",
938
+ "\n",
939
+ "### sgRNA Design Recommendations:\n",
940
+ "- Use 3-4 sgRNAs per gene for redundancy\n",
941
+ "- Include non-targeting controls and positive controls (TOX, PDCD1)\n",
942
+ "- Target early exons for effective knockouts\n",
943
+ "- Use established design tools (Broad GPP portal, CHOPCHOP)\n",
944
+ "\n",
945
+ "This gene set maximizes the potential to identify novel regulators of T cell exhaustion while covering all major pathways involved in the acute-to-chronic transition of T cell responses.\n",
946
+ "</solution>\n",
947
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
948
+ "\n",
949
+ "5. [✓] Provide theoretical sgRNA design recommendations and validate gene selection (completed)\n",
950
+ "6. [✓] Provide comprehensive experimental design recommendations (completed)\n",
951
+ "\n",
952
+ "<solution>\n",
953
+ "# CRISPR Screen to Identify T Cell Exhaustion Regulators\n",
954
+ "\n",
955
+ "## Selected 32 Genes for Maximum Perturbation Effect\n",
956
+ "\n",
957
+ "Based on comprehensive analysis of T cell exhaustion biology, I have identified 32 genes that will maximize perturbation effects in your CRISPR screen measuring differential TCR signaling between acute (IL-2 only) and chronic (anti-CD3 + IL-2) stimulation conditions:\n",
958
+ "\n",
959
+ "### Gene List (Ranked by Priority):\n",
960
+ "\n",
961
+ "**Immune Checkpoint Molecules (8 genes):**\n",
962
+ "1. **PDCD1** (PD-1) - Primary exhaustion checkpoint receptor\n",
963
+ "2. **HAVCR2** (TIM-3) - Critical exhaustion marker and checkpoint\n",
964
+ "3. **LAG3** - Major exhaustion checkpoint receptor\n",
965
+ "4. **TIGIT** - Emerging exhaustion checkpoint target\n",
966
+ "5. **CTLA4** - Early checkpoint inhibitor\n",
967
+ "6. **CD274** (PD-L1) - PD-1 ligand\n",
968
+ "7. **BTLA** - Inhibitory receptor\n",
969
+ "8. **CD96** - TIGIT pathway component\n",
970
+ "\n",
971
+ "**Transcription Factors (8 genes):**\n",
972
+ "9. **TOX** - Master regulator of T cell exhaustion\n",
973
+ "10. **EOMES** - Key exhaustion-promoting transcription factor\n",
974
+ "11. **BATF** - Exhaustion-promoting transcription factor\n",
975
+ "12. **TBX21** (T-bet) - Effector vs exhaustion balance\n",
976
+ "13. **IRF4** - T cell differentiation regulator\n",
977
+ "14. **NFATC1** - Chronic stimulation response factor\n",
978
+ "15. **TOX2** - TOX family member\n",
979
+ "16. **RUNX3** - T cell differentiation factor\n",
980
+ "\n",
981
+ "**Metabolic Regulators (8 genes):**\n",
982
+ "17. **HIF1A** - Hypoxia and exhaustion regulator\n",
983
+ "18. **MYC** - Metabolic reprogramming master regulator\n",
984
+ "19. **MTOR** - Metabolic sensing and signaling\n",
985
+ "20. **PRKAA1** (AMPK) - Energy sensing kinase\n",
986
+ "21. **LDHA** - Lactate metabolism in exhaustion\n",
987
+ "22. **PKM** - Pyruvate kinase, glycolysis regulator\n",
988
+ "23. **SLC2A1** (GLUT1) - Glucose uptake transporter\n",
989
+ "24. **PPARA** - Fatty acid oxidation regulator\n",
990
+ "\n",
991
+ "**TCR Signaling Components (7 genes):**\n",
992
+ "25. **LCK** - Proximal TCR signaling kinase\n",
993
+ "26. **ZAP70** - TCR signal strength regulator\n",
994
+ "27. **LAT** - TCR signaling hub adaptor\n",
995
+ "28. **PLCG1** (PLCγ1) - Downstream TCR signaling\n",
996
+ "29. **LCP2** (SLP-76) - TCR signaling adaptor\n",
997
+ "30. **VAV1** - TCR signaling regulator\n",
998
+ "31. **ITK** - TCR signaling kinase\n",
999
+ "\n",
1000
+ "**Cytokine Signaling (1 gene):**\n",
1001
+ "32. **STAT3** - Cytokine signaling transcription factor\n",
1002
+ "\n",
1003
+ "## Experimental Design Overview\n",
1004
+ "\n",
1005
+ "### Objective:\n",
1006
+ "Identify genes that regulate T cell exhaustion by measuring differential TCR signaling responses between acute and chronic stimulation conditions.\n",
1007
+ "\n",
1008
+ "### Key Features:\n",
1009
+ "- **Balanced gene selection** across 4 major functional categories\n",
1010
+ "- **High-priority targets** based on literature evidence and therapeutic relevance\n",
1011
+ "- **Expected differential effects** between acute vs chronic conditions\n",
1012
+ "- **Multiple readouts** for comprehensive phenotyping\n",
1013
+ "\n",
1014
+ "### Stimulation Conditions:\n",
1015
+ "- **Acute**: IL-2 only (maintains survival without exhaustion)\n",
1016
+ "- **Chronic**: Anti-CD3 + IL-2 (induces exhaustion through chronic TCR stimulation)\n",
1017
+ "\n",
1018
+ "### Primary Readouts:\n",
1019
+ "1. **TCR signaling strength**: Phospho-flow cytometry (pZAP70, pERK, pS6)\n",
1020
+ "2. **Exhaustion markers**: PD-1, TIM-3, LAG-3 surface expression\n",
1021
+ "3. **Functional capacity**: Cytokine production (IFN-γ, TNF-α, IL-2)\n",
1022
+ "4. **Transcriptional state**: RNA-seq for exhaustion gene signatures\n",
1023
+ "\n",
1024
+ "### Expected Outcomes:\n",
1025
+ "- **Checkpoint inhibitors** (PDCD1, HAVCR2, LAG3): KO should enhance signaling in chronic condition\n",
1026
+ "- **Exhaustion TFs** (TOX, EOMES, BATF): KO should reduce exhaustion phenotype\n",
1027
+ "- **Metabolic regulators** (MTOR, HIF1A, MYC): Context-dependent effects on sustained signaling\n",
1028
+ "- **TCR components** (LCK, ZAP70, LAT): Proportional effects on signaling strength\n",
1029
+ "\n",
1030
+ "### sgRNA Design Recommendations:\n",
1031
+ "- Use 3-4 sgRNAs per gene for redundancy\n",
1032
+ "- Include non-targeting controls and positive controls (TOX, PDCD1)\n",
1033
+ "- Target early exons for effective knockouts\n",
1034
+ "- Use established design tools (Broad GPP portal, CHOPCHOP)\n",
1035
+ "\n",
1036
+ "This gene set maximizes the potential to identify novel regulators of T cell exhaustion while covering all major pathways involved in the acute-to-chronic transition of T cell responses.\n",
1037
+ "</solution>\n"
1038
+ ]
1039
+ }
1040
+ ],
1041
+ "source": [
1042
+ "log = agent.go(\"\"\"Plan a CRISPR screen to identify genes that regulate T cell exhaustion,\n",
1043
+ " measured by the change in T cell receptor (TCR) signaling between acute\n",
1044
+ " (interleukin-2 [IL-2] only) and chronic (anti-CD3 and IL-2) stimulation conditions.\n",
1045
+ " Generate 32 genes that maximize the perturbation effect.\"\"\")"
1046
+ ]
1047
+ }
1048
+ ],
1049
+ "metadata": {
1050
+ "kernelspec": {
1051
+ "display_name": "biomni_fixed",
1052
+ "language": "python",
1053
+ "name": "python3"
1054
+ },
1055
+ "language_info": {
1056
+ "codemirror_mode": {
1057
+ "name": "ipython",
1058
+ "version": 3
1059
+ },
1060
+ "file_extension": ".py",
1061
+ "mimetype": "text/x-python",
1062
+ "name": "python",
1063
+ "nbconvert_exporter": "python",
1064
+ "pygments_lexer": "ipython3",
1065
+ "version": "3.11.13"
1066
+ }
1067
+ },
1068
+ "nbformat": 4,
1069
+ "nbformat_minor": 2
1070
+ }