generesearcher 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- generesearcher-0.1.0/LICENSE +25 -0
- generesearcher-0.1.0/PKG-INFO +229 -0
- generesearcher-0.1.0/README.md +198 -0
- generesearcher-0.1.0/generesearcher/__init__.py +19 -0
- generesearcher-0.1.0/generesearcher/__main__.py +3 -0
- generesearcher-0.1.0/generesearcher/api.py +172 -0
- generesearcher-0.1.0/generesearcher/cli.py +147 -0
- generesearcher-0.1.0/generesearcher/config.py +115 -0
- generesearcher-0.1.0/generesearcher/connectors/__init__.py +0 -0
- generesearcher-0.1.0/generesearcher/connectors/batch_manager.py +120 -0
- generesearcher-0.1.0/generesearcher/connectors/embedding_connector.py +397 -0
- generesearcher-0.1.0/generesearcher/connectors/llm_api.py +222 -0
- generesearcher-0.1.0/generesearcher/connectors/milvus_connector.py +1011 -0
- generesearcher-0.1.0/generesearcher/connectors/ncbi_connector.py +573 -0
- generesearcher-0.1.0/generesearcher/core/__init__.py +0 -0
- generesearcher-0.1.0/generesearcher/core/data_io.py +247 -0
- generesearcher-0.1.0/generesearcher/core/data_models.py +120 -0
- generesearcher-0.1.0/generesearcher/core/naming.py +6 -0
- generesearcher-0.1.0/generesearcher/core/parsers.py +589 -0
- generesearcher-0.1.0/generesearcher/logging_utils.py +67 -0
- generesearcher-0.1.0/generesearcher/model_installer.py +59 -0
- generesearcher-0.1.0/generesearcher/persistence.py +88 -0
- generesearcher-0.1.0/generesearcher/pipeline/__init__.py +0 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_01_retrieval.py +668 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_02_semantic_filter.py +493 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_03_relevance_filter.py +1037 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_04_synthesis.py +1301 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_05_reporting.py +990 -0
- generesearcher-0.1.0/generesearcher/pipeline/step_06_report_generation.py +389 -0
- generesearcher-0.1.0/generesearcher/run.py +349 -0
- generesearcher-0.1.0/generesearcher/runtime_dependencies.py +113 -0
- generesearcher-0.1.0/generesearcher.egg-info/PKG-INFO +229 -0
- generesearcher-0.1.0/generesearcher.egg-info/SOURCES.txt +37 -0
- generesearcher-0.1.0/generesearcher.egg-info/dependency_links.txt +1 -0
- generesearcher-0.1.0/generesearcher.egg-info/entry_points.txt +2 -0
- generesearcher-0.1.0/generesearcher.egg-info/requires.txt +24 -0
- generesearcher-0.1.0/generesearcher.egg-info/top_level.txt +1 -0
- generesearcher-0.1.0/pyproject.toml +50 -0
- generesearcher-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, GeneResearcher contributors
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: generesearcher
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Gene-centered biomedical literature retrieval and analysis pipeline
|
|
5
|
+
License-Expression: BSD-2-Clause
|
|
6
|
+
Requires-Python: <3.13,>=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: biopython<2,>=1.83
|
|
10
|
+
Requires-Dist: click<9,>=8
|
|
11
|
+
Requires-Dist: numpy<3,>=1.26
|
|
12
|
+
Requires-Dist: openai<3,>=1.30
|
|
13
|
+
Requires-Dist: pandas<4,>=2.2
|
|
14
|
+
Requires-Dist: pymilvus[milvus_lite]==3.0.0
|
|
15
|
+
Requires-Dist: milvus-lite==2.5.1
|
|
16
|
+
Requires-Dist: setuptools<81,>=68
|
|
17
|
+
Requires-Dist: python-dotenv<2,>=1.0
|
|
18
|
+
Requires-Dist: PyYAML<7,>=6
|
|
19
|
+
Requires-Dist: requests<3,>=2.31
|
|
20
|
+
Requires-Dist: scispacy<0.7,>=0.6
|
|
21
|
+
Requires-Dist: spacy<3.8,>=3.7
|
|
22
|
+
Requires-Dist: tqdm<5,>=4.66
|
|
23
|
+
Provides-Extra: dashscope
|
|
24
|
+
Requires-Dist: dashscope<2,>=1.20; extra == "dashscope"
|
|
25
|
+
Provides-Extra: local-embedding
|
|
26
|
+
Requires-Dist: torch>=2.2; extra == "local-embedding"
|
|
27
|
+
Requires-Dist: transformers<5,>=4.40; extra == "local-embedding"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff<1,>=0.6; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# GeneResearcher
|
|
33
|
+
|
|
34
|
+
GeneResearcher searches biomedical literature for gene/function/background relationships, retrieves article text, filters evidence, synthesizes results, and writes a Markdown report.
|
|
35
|
+
|
|
36
|
+
## Choose how to use it
|
|
37
|
+
|
|
38
|
+
| Use case | Install | Run |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| Use a built package | Install the wheel, then run `python -m generesearcher install-spacy-model`. | Import `ResearchConfig` and `run_analysis` in your Python code. |
|
|
41
|
+
| Run or modify the source | From the project source directory, run `python -m pip install -e .`, then `python -m generesearcher install-spacy-model`. | Create a study YAML file and run `python -m generesearcher run --config study.yaml`. |
|
|
42
|
+
|
|
43
|
+
Both routes use the same `ResearchConfig` and pipeline. Users provide their own `.env` credentials. The SciSpaCy language model is a separate download, installed with `install-spacy-model` after the package dependencies are installed. The package has build metadata and a working wheel; PyPI installation is available after the project publishes a release.
|
|
44
|
+
|
|
45
|
+
## How a study is organized
|
|
46
|
+
|
|
47
|
+
- One `project_folder` represents one research background.
|
|
48
|
+
- A project can contain multiple function analyses. Each run analyzes one `function_name` for one or more `genes` and writes to its own `analysis_<function>` directory.
|
|
49
|
+
- Re-running the same function clears that function directory's Milvus database and generated exports before starting. The Python API emits a warning if a database already exists; the pipeline also logs the directory it clears.
|
|
50
|
+
- CSV and TXT files are display exports. Pipeline recovery and later steps use the database as their source of truth.
|
|
51
|
+
|
|
52
|
+
The six pipeline stages run in this order:
|
|
53
|
+
|
|
54
|
+
1. Retrieve articles and their available text.
|
|
55
|
+
2. Embed article text and recall relevant sentences.
|
|
56
|
+
3. Filter recalled evidence for relevance.
|
|
57
|
+
4. Synthesize per-article and cross-article results.
|
|
58
|
+
5. Build gene-level and study-level reporting results.
|
|
59
|
+
6. Generate the final Markdown report.
|
|
60
|
+
|
|
61
|
+
## Install and run from source
|
|
62
|
+
|
|
63
|
+
Use Python 3.10–3.12 in an isolated environment. SciSpaCy 0.6.x does not support Python 3.13. The clean installation has been verified on macOS arm64 with Python 3.11. From this directory:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -m pip install -e .
|
|
67
|
+
python -m generesearcher install-spacy-model
|
|
68
|
+
cp .env.example .env
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Edit `.env` with the required service values. Do not commit it. The default `.env` is read from the current working directory; `--env-file` can point to another file.
|
|
72
|
+
|
|
73
|
+
Step 2 uses a SciSpaCy model to split article text into sentences. The default is `en_core_sci_lg`; you may instead select `en_core_sci_sm`, `en_core_sci_md`, or `en_core_sci_scibert`. The four choices are SciSpaCy's general biomedical pipelines; GeneResearcher does not use the separate NER-only models. After installing GeneResearcher, download and validate the default model with:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m generesearcher install-spacy-model
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
To use another model, install it and set the same name in `study.yaml` under `scispacy_model`:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m generesearcher install-spacy-model --model en_core_sci_sm
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
scispacy_model: en_core_sci_sm
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The installer downloads the selected model archive from the [official SciSpaCy model list](https://github.com/allenai/scispacy/blob/main/docs/index.md). It does not retain the archive in pip's cache. The default `en_core_sci_lg` model uses about 620 MB after installation; choose `en_core_sci_sm` if you need a smaller model. GeneResearcher checks required dependencies and loads the selected model before it starts a run or clears an existing analysis database; a missing or incompatible model produces an actionable error. Model names are also saved in `params.json` with the analysis parameters.
|
|
90
|
+
|
|
91
|
+
The default install supports the configured Gitee-compatible embedding endpoint. DashScope and local Transformers are optional extras. Install the matching extra with the package:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Source checkout
|
|
95
|
+
python -m pip install -e '.[dashscope]'
|
|
96
|
+
|
|
97
|
+
# Or a built wheel
|
|
98
|
+
python -m pip install 'path/to/generesearcher-0.1.0-py3-none-any.whl[dashscope]'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For a local Transformers embedding model, use `.[local-embedding]` instead and set `EMBEDDING_PROVIDER=local` plus `EMBEDDING_MODEL_NAME` in `.env`. PyTorch can be a large download, so install that extra only when using local embeddings.
|
|
102
|
+
|
|
103
|
+
Copy and edit the example study file, then run:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cp study.example.yaml study.yaml
|
|
107
|
+
python -m generesearcher run --config study.yaml
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
You can specify an environment file and see detailed logs with:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -m generesearcher run --config study.yaml --env-file .env --log-level DEBUG
|
|
114
|
+
python -m generesearcher run --config study.yaml --quiet
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The log levels follow one rule across the package:
|
|
118
|
+
|
|
119
|
+
- `DEBUG`: cache decisions, retry attempts, and low-level diagnostics.
|
|
120
|
+
- `INFO`: stage starts and completions, durations, paths, and record counts.
|
|
121
|
+
- `WARNING`: recoverable omissions, fallbacks, and partial results.
|
|
122
|
+
- `ERROR`: failures that stop a stage or the full run.
|
|
123
|
+
|
|
124
|
+
The default is `INFO`. Use `--log-level DEBUG` (or `--verbose`) for detail, `--log-level WARNING` to reduce routine progress messages, `--quiet` to show errors only, or `--log-level OFF` to disable logs. Fatal run failures include a traceback at the Python API boundary. Progress bars are disabled when the level is above `INFO`. Application logs do not include prompts or article text. The Python warning about clearing an existing analysis database remains visible even when logs are disabled.
|
|
125
|
+
|
|
126
|
+
Relative `project_folder` paths in YAML are resolved from the YAML file's directory. The CLI looks for `.env` beside the YAML file, then falls back to `.env` in the current working directory.
|
|
127
|
+
|
|
128
|
+
## Use as an installed Python package
|
|
129
|
+
|
|
130
|
+
To create an installable wheel from this checkout:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python -m pip install build
|
|
134
|
+
python -m build
|
|
135
|
+
python -m pip install dist/generesearcher-0.1.0-py3-none-any.whl
|
|
136
|
+
python -m generesearcher install-spacy-model
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
PyPI publishing is a separate maintainer release step. Until then, users can install a supplied wheel directly as shown above; people working from the source checkout can use `pip install .` or `pip install -e .`.
|
|
140
|
+
|
|
141
|
+
Synchronous Python:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import logging
|
|
145
|
+
import os
|
|
146
|
+
from generesearcher import ResearchConfig, run_analysis
|
|
147
|
+
|
|
148
|
+
# Python API users control package log output with Python's logging settings.
|
|
149
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
150
|
+
# To suppress GeneResearcher logs in this process, use:
|
|
151
|
+
# logging.getLogger("generesearcher").setLevel(logging.CRITICAL + 1)
|
|
152
|
+
|
|
153
|
+
# Service credentials can be set in this process. Values already present in
|
|
154
|
+
# the process environment take precedence over values loaded from .env.
|
|
155
|
+
os.environ["ENTREZ_EMAIL"] = "your-email@example.org"
|
|
156
|
+
os.environ["EMBEDDING_PROVIDER"] = "dashscope"
|
|
157
|
+
os.environ["EMBEDDING_API_KEY"] = "your-embedding-api-key"
|
|
158
|
+
os.environ["LLM_MODEL_NAME"] = "your-model-name"
|
|
159
|
+
os.environ["LLM_API_KEY"] = "your-llm-api-key"
|
|
160
|
+
os.environ["LLM_BASE_URL"] = "https://your-llm-endpoint/v1"
|
|
161
|
+
|
|
162
|
+
config = ResearchConfig(
|
|
163
|
+
genes=["EP300", "CREBBP"],
|
|
164
|
+
function_name="histone acetylation",
|
|
165
|
+
research_background_template=(
|
|
166
|
+
"What is the role of {gene} in {function_name}, especially in cancer development?"
|
|
167
|
+
),
|
|
168
|
+
project_folder="./results/example_study",
|
|
169
|
+
keywords=["cancer", "tumor"],
|
|
170
|
+
gene_aliases={"EP300": ["p300"], "CREBBP": ["CBP"]},
|
|
171
|
+
scispacy_model="en_core_sci_lg",
|
|
172
|
+
)
|
|
173
|
+
result = run_analysis(config)
|
|
174
|
+
print(result.report_path)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The values above are examples; do not commit real credentials in a Python script. You can instead set the variables in the shell, a deployment secret store, or a local `.env` file. GeneResearcher reads process environment variables directly and does not overwrite them when it loads `.env`. The `.env` file is searched in the current working directory unless you pass `env_file` explicitly.
|
|
178
|
+
|
|
179
|
+
For an already-running async application, use `await run_analysis_async(config, env_file=".env")`.
|
|
180
|
+
|
|
181
|
+
### Save and restore an analysis
|
|
182
|
+
|
|
183
|
+
The usual workflow needs no explicit save or load call. `run_analysis()` returns an `AnalysisResult` in memory. If you want to keep a separate copy or restore a previous result later, use the optional helpers:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from generesearcher import load_analysis, save_analysis
|
|
187
|
+
|
|
188
|
+
# Save the completed function analysis to a new directory.
|
|
189
|
+
saved_directory = save_analysis(result, "./backups/ep300_histone_acetylation")
|
|
190
|
+
|
|
191
|
+
# Restore the parameters and result tables into memory later.
|
|
192
|
+
restored_result = load_analysis(saved_directory)
|
|
193
|
+
print(restored_result.analysis_data.raw_papers_df.head())
|
|
194
|
+
print(restored_result.report_path)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`save_analysis()` copies the function's complete analysis directory, including its Milvus Lite database, parameters, and generated files. It does not serialize through CSV. The destination directory must not already exist. `load_analysis()` restores from the database and does not call literature, embedding, or language-model services. It returns the saved result for inspection and downstream Python use; it does not rerun the pipeline. Keep the saved directory separate from the active `project_folder`, because a new full run clears the active function analysis before starting.
|
|
198
|
+
|
|
199
|
+
`ResearchConfig`, `params.json`, and Milvus use the same canonical field names. PMID is the exclusion identifier; PMCID is used only as the retrieval/full-text identifier and as the rare fallback when an article has no PMID.
|
|
200
|
+
|
|
201
|
+
## Configuration fields
|
|
202
|
+
|
|
203
|
+
The YAML file and `ResearchConfig` accept the same fields:
|
|
204
|
+
|
|
205
|
+
| Field | Required | Meaning |
|
|
206
|
+
| --- | --- | --- |
|
|
207
|
+
| `genes` | Yes | One or more gene symbols |
|
|
208
|
+
| `function_name` | Yes | The function or pathway analyzed in this run |
|
|
209
|
+
| `research_background_template` | Yes | Background text; `{gene}` and `{function_name}` are replaced during retrieval and analysis |
|
|
210
|
+
| `project_folder` | No | Root folder shared by runs for the same research background |
|
|
211
|
+
| `keywords` | No | Literature search terms |
|
|
212
|
+
| `function_query` | No | Optional search wording for the function |
|
|
213
|
+
| `exclude_pmids` | No | PMID values to exclude |
|
|
214
|
+
| `publication_date_before` | No | Inclusive search cutoff in the format supported by the search code |
|
|
215
|
+
| `gene_aliases` | No | Additional search aliases keyed by gene symbol |
|
|
216
|
+
| `max_papers_per_gene` | No | Maximum search results per gene; defaults to 10 |
|
|
217
|
+
| `scispacy_model` | No | SciSpaCy sentence-processing model; defaults to `en_core_sci_lg` |
|
|
218
|
+
|
|
219
|
+
Credentials and provider settings belong in environment variables or `.env`, not in the study YAML. Existing process environment variables take precedence over `.env` values. Use `EMBEDDING_API_KEY` for the embedding provider key, including DashScope.
|
|
220
|
+
|
|
221
|
+
## Naming and persisted data
|
|
222
|
+
|
|
223
|
+
Public Python entry points, configuration fields, persisted JSON keys, and Milvus fields use descriptive `snake_case` names. The project uses the canonical first-version format and does not translate alternate spellings. Research background templates use `{gene}` and `{function_name}` placeholders.
|
|
224
|
+
|
|
225
|
+
Only the `generesearcher` package is included in built distributions. Notebook checkpoints, bytecode caches, and historical duplicate scripts at the repository root are not runtime package modules.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
GeneResearcher is distributed under the BSD-2-Clause License. See [LICENSE](LICENSE) for the full terms.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# GeneResearcher
|
|
2
|
+
|
|
3
|
+
GeneResearcher searches biomedical literature for gene/function/background relationships, retrieves article text, filters evidence, synthesizes results, and writes a Markdown report.
|
|
4
|
+
|
|
5
|
+
## Choose how to use it
|
|
6
|
+
|
|
7
|
+
| Use case | Install | Run |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Use a built package | Install the wheel, then run `python -m generesearcher install-spacy-model`. | Import `ResearchConfig` and `run_analysis` in your Python code. |
|
|
10
|
+
| Run or modify the source | From the project source directory, run `python -m pip install -e .`, then `python -m generesearcher install-spacy-model`. | Create a study YAML file and run `python -m generesearcher run --config study.yaml`. |
|
|
11
|
+
|
|
12
|
+
Both routes use the same `ResearchConfig` and pipeline. Users provide their own `.env` credentials. The SciSpaCy language model is a separate download, installed with `install-spacy-model` after the package dependencies are installed. The package has build metadata and a working wheel; PyPI installation is available after the project publishes a release.
|
|
13
|
+
|
|
14
|
+
## How a study is organized
|
|
15
|
+
|
|
16
|
+
- One `project_folder` represents one research background.
|
|
17
|
+
- A project can contain multiple function analyses. Each run analyzes one `function_name` for one or more `genes` and writes to its own `analysis_<function>` directory.
|
|
18
|
+
- Re-running the same function clears that function directory's Milvus database and generated exports before starting. The Python API emits a warning if a database already exists; the pipeline also logs the directory it clears.
|
|
19
|
+
- CSV and TXT files are display exports. Pipeline recovery and later steps use the database as their source of truth.
|
|
20
|
+
|
|
21
|
+
The six pipeline stages run in this order:
|
|
22
|
+
|
|
23
|
+
1. Retrieve articles and their available text.
|
|
24
|
+
2. Embed article text and recall relevant sentences.
|
|
25
|
+
3. Filter recalled evidence for relevance.
|
|
26
|
+
4. Synthesize per-article and cross-article results.
|
|
27
|
+
5. Build gene-level and study-level reporting results.
|
|
28
|
+
6. Generate the final Markdown report.
|
|
29
|
+
|
|
30
|
+
## Install and run from source
|
|
31
|
+
|
|
32
|
+
Use Python 3.10–3.12 in an isolated environment. SciSpaCy 0.6.x does not support Python 3.13. The clean installation has been verified on macOS arm64 with Python 3.11. From this directory:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install -e .
|
|
36
|
+
python -m generesearcher install-spacy-model
|
|
37
|
+
cp .env.example .env
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Edit `.env` with the required service values. Do not commit it. The default `.env` is read from the current working directory; `--env-file` can point to another file.
|
|
41
|
+
|
|
42
|
+
Step 2 uses a SciSpaCy model to split article text into sentences. The default is `en_core_sci_lg`; you may instead select `en_core_sci_sm`, `en_core_sci_md`, or `en_core_sci_scibert`. The four choices are SciSpaCy's general biomedical pipelines; GeneResearcher does not use the separate NER-only models. After installing GeneResearcher, download and validate the default model with:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m generesearcher install-spacy-model
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
To use another model, install it and set the same name in `study.yaml` under `scispacy_model`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m generesearcher install-spacy-model --model en_core_sci_sm
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
scispacy_model: en_core_sci_sm
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The installer downloads the selected model archive from the [official SciSpaCy model list](https://github.com/allenai/scispacy/blob/main/docs/index.md). It does not retain the archive in pip's cache. The default `en_core_sci_lg` model uses about 620 MB after installation; choose `en_core_sci_sm` if you need a smaller model. GeneResearcher checks required dependencies and loads the selected model before it starts a run or clears an existing analysis database; a missing or incompatible model produces an actionable error. Model names are also saved in `params.json` with the analysis parameters.
|
|
59
|
+
|
|
60
|
+
The default install supports the configured Gitee-compatible embedding endpoint. DashScope and local Transformers are optional extras. Install the matching extra with the package:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Source checkout
|
|
64
|
+
python -m pip install -e '.[dashscope]'
|
|
65
|
+
|
|
66
|
+
# Or a built wheel
|
|
67
|
+
python -m pip install 'path/to/generesearcher-0.1.0-py3-none-any.whl[dashscope]'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For a local Transformers embedding model, use `.[local-embedding]` instead and set `EMBEDDING_PROVIDER=local` plus `EMBEDDING_MODEL_NAME` in `.env`. PyTorch can be a large download, so install that extra only when using local embeddings.
|
|
71
|
+
|
|
72
|
+
Copy and edit the example study file, then run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cp study.example.yaml study.yaml
|
|
76
|
+
python -m generesearcher run --config study.yaml
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
You can specify an environment file and see detailed logs with:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m generesearcher run --config study.yaml --env-file .env --log-level DEBUG
|
|
83
|
+
python -m generesearcher run --config study.yaml --quiet
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The log levels follow one rule across the package:
|
|
87
|
+
|
|
88
|
+
- `DEBUG`: cache decisions, retry attempts, and low-level diagnostics.
|
|
89
|
+
- `INFO`: stage starts and completions, durations, paths, and record counts.
|
|
90
|
+
- `WARNING`: recoverable omissions, fallbacks, and partial results.
|
|
91
|
+
- `ERROR`: failures that stop a stage or the full run.
|
|
92
|
+
|
|
93
|
+
The default is `INFO`. Use `--log-level DEBUG` (or `--verbose`) for detail, `--log-level WARNING` to reduce routine progress messages, `--quiet` to show errors only, or `--log-level OFF` to disable logs. Fatal run failures include a traceback at the Python API boundary. Progress bars are disabled when the level is above `INFO`. Application logs do not include prompts or article text. The Python warning about clearing an existing analysis database remains visible even when logs are disabled.
|
|
94
|
+
|
|
95
|
+
Relative `project_folder` paths in YAML are resolved from the YAML file's directory. The CLI looks for `.env` beside the YAML file, then falls back to `.env` in the current working directory.
|
|
96
|
+
|
|
97
|
+
## Use as an installed Python package
|
|
98
|
+
|
|
99
|
+
To create an installable wheel from this checkout:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python -m pip install build
|
|
103
|
+
python -m build
|
|
104
|
+
python -m pip install dist/generesearcher-0.1.0-py3-none-any.whl
|
|
105
|
+
python -m generesearcher install-spacy-model
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
PyPI publishing is a separate maintainer release step. Until then, users can install a supplied wheel directly as shown above; people working from the source checkout can use `pip install .` or `pip install -e .`.
|
|
109
|
+
|
|
110
|
+
Synchronous Python:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import logging
|
|
114
|
+
import os
|
|
115
|
+
from generesearcher import ResearchConfig, run_analysis
|
|
116
|
+
|
|
117
|
+
# Python API users control package log output with Python's logging settings.
|
|
118
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
119
|
+
# To suppress GeneResearcher logs in this process, use:
|
|
120
|
+
# logging.getLogger("generesearcher").setLevel(logging.CRITICAL + 1)
|
|
121
|
+
|
|
122
|
+
# Service credentials can be set in this process. Values already present in
|
|
123
|
+
# the process environment take precedence over values loaded from .env.
|
|
124
|
+
os.environ["ENTREZ_EMAIL"] = "your-email@example.org"
|
|
125
|
+
os.environ["EMBEDDING_PROVIDER"] = "dashscope"
|
|
126
|
+
os.environ["EMBEDDING_API_KEY"] = "your-embedding-api-key"
|
|
127
|
+
os.environ["LLM_MODEL_NAME"] = "your-model-name"
|
|
128
|
+
os.environ["LLM_API_KEY"] = "your-llm-api-key"
|
|
129
|
+
os.environ["LLM_BASE_URL"] = "https://your-llm-endpoint/v1"
|
|
130
|
+
|
|
131
|
+
config = ResearchConfig(
|
|
132
|
+
genes=["EP300", "CREBBP"],
|
|
133
|
+
function_name="histone acetylation",
|
|
134
|
+
research_background_template=(
|
|
135
|
+
"What is the role of {gene} in {function_name}, especially in cancer development?"
|
|
136
|
+
),
|
|
137
|
+
project_folder="./results/example_study",
|
|
138
|
+
keywords=["cancer", "tumor"],
|
|
139
|
+
gene_aliases={"EP300": ["p300"], "CREBBP": ["CBP"]},
|
|
140
|
+
scispacy_model="en_core_sci_lg",
|
|
141
|
+
)
|
|
142
|
+
result = run_analysis(config)
|
|
143
|
+
print(result.report_path)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The values above are examples; do not commit real credentials in a Python script. You can instead set the variables in the shell, a deployment secret store, or a local `.env` file. GeneResearcher reads process environment variables directly and does not overwrite them when it loads `.env`. The `.env` file is searched in the current working directory unless you pass `env_file` explicitly.
|
|
147
|
+
|
|
148
|
+
For an already-running async application, use `await run_analysis_async(config, env_file=".env")`.
|
|
149
|
+
|
|
150
|
+
### Save and restore an analysis
|
|
151
|
+
|
|
152
|
+
The usual workflow needs no explicit save or load call. `run_analysis()` returns an `AnalysisResult` in memory. If you want to keep a separate copy or restore a previous result later, use the optional helpers:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from generesearcher import load_analysis, save_analysis
|
|
156
|
+
|
|
157
|
+
# Save the completed function analysis to a new directory.
|
|
158
|
+
saved_directory = save_analysis(result, "./backups/ep300_histone_acetylation")
|
|
159
|
+
|
|
160
|
+
# Restore the parameters and result tables into memory later.
|
|
161
|
+
restored_result = load_analysis(saved_directory)
|
|
162
|
+
print(restored_result.analysis_data.raw_papers_df.head())
|
|
163
|
+
print(restored_result.report_path)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`save_analysis()` copies the function's complete analysis directory, including its Milvus Lite database, parameters, and generated files. It does not serialize through CSV. The destination directory must not already exist. `load_analysis()` restores from the database and does not call literature, embedding, or language-model services. It returns the saved result for inspection and downstream Python use; it does not rerun the pipeline. Keep the saved directory separate from the active `project_folder`, because a new full run clears the active function analysis before starting.
|
|
167
|
+
|
|
168
|
+
`ResearchConfig`, `params.json`, and Milvus use the same canonical field names. PMID is the exclusion identifier; PMCID is used only as the retrieval/full-text identifier and as the rare fallback when an article has no PMID.
|
|
169
|
+
|
|
170
|
+
## Configuration fields
|
|
171
|
+
|
|
172
|
+
The YAML file and `ResearchConfig` accept the same fields:
|
|
173
|
+
|
|
174
|
+
| Field | Required | Meaning |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| `genes` | Yes | One or more gene symbols |
|
|
177
|
+
| `function_name` | Yes | The function or pathway analyzed in this run |
|
|
178
|
+
| `research_background_template` | Yes | Background text; `{gene}` and `{function_name}` are replaced during retrieval and analysis |
|
|
179
|
+
| `project_folder` | No | Root folder shared by runs for the same research background |
|
|
180
|
+
| `keywords` | No | Literature search terms |
|
|
181
|
+
| `function_query` | No | Optional search wording for the function |
|
|
182
|
+
| `exclude_pmids` | No | PMID values to exclude |
|
|
183
|
+
| `publication_date_before` | No | Inclusive search cutoff in the format supported by the search code |
|
|
184
|
+
| `gene_aliases` | No | Additional search aliases keyed by gene symbol |
|
|
185
|
+
| `max_papers_per_gene` | No | Maximum search results per gene; defaults to 10 |
|
|
186
|
+
| `scispacy_model` | No | SciSpaCy sentence-processing model; defaults to `en_core_sci_lg` |
|
|
187
|
+
|
|
188
|
+
Credentials and provider settings belong in environment variables or `.env`, not in the study YAML. Existing process environment variables take precedence over `.env` values. Use `EMBEDDING_API_KEY` for the embedding provider key, including DashScope.
|
|
189
|
+
|
|
190
|
+
## Naming and persisted data
|
|
191
|
+
|
|
192
|
+
Public Python entry points, configuration fields, persisted JSON keys, and Milvus fields use descriptive `snake_case` names. The project uses the canonical first-version format and does not translate alternate spellings. Research background templates use `{gene}` and `{function_name}` placeholders.
|
|
193
|
+
|
|
194
|
+
Only the `generesearcher` package is included in built distributions. Notebook checkpoints, bytecode caches, and historical duplicate scripts at the repository root are not runtime package modules.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
GeneResearcher is distributed under the BSD-2-Clause License. See [LICENSE](LICENSE) for the full terms.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""GeneResearcher literature-analysis package."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
6
|
+
|
|
7
|
+
from .api import AnalysisResult, ResearchConfig, run_analysis, run_analysis_async
|
|
8
|
+
from .persistence import load_analysis, save_analysis
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AnalysisResult",
|
|
12
|
+
"ResearchConfig",
|
|
13
|
+
"load_analysis",
|
|
14
|
+
"run_analysis",
|
|
15
|
+
"run_analysis_async",
|
|
16
|
+
"save_analysis",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Stable Python API for running one GeneResearcher analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import warnings
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import date, datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .runtime_dependencies import DEFAULT_SCISPACY_MODEL, validate_scispacy_model_name
|
|
13
|
+
from .logging_utils import log_operation
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .core.data_models import AnalysisData
|
|
17
|
+
else:
|
|
18
|
+
# Keep importing the public API possible before runtime packages are checked.
|
|
19
|
+
AnalysisData = Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ResearchConfig:
|
|
24
|
+
"""Inputs for one function analysis within a research project.
|
|
25
|
+
|
|
26
|
+
A project folder represents one research background and may contain
|
|
27
|
+
multiple function analyses. Each config runs one function for one or more
|
|
28
|
+
genes.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
genes: List[str]
|
|
32
|
+
function_name: str
|
|
33
|
+
research_background_template: str
|
|
34
|
+
project_folder: str | Path = "GeneResearch"
|
|
35
|
+
keywords: List[str] = field(default_factory=list)
|
|
36
|
+
function_query: Optional[str] = None
|
|
37
|
+
exclude_pmids: List[str] = field(default_factory=list)
|
|
38
|
+
publication_date_before: Optional[str] = None
|
|
39
|
+
gene_aliases: Dict[str, List[str]] = field(default_factory=dict)
|
|
40
|
+
max_papers_per_gene: int = 10
|
|
41
|
+
scispacy_model: str = DEFAULT_SCISPACY_MODEL
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
if isinstance(self.genes, str) or not self.genes:
|
|
45
|
+
raise ValueError("genes must be a non-empty list of gene names")
|
|
46
|
+
if any(not isinstance(gene, str) or not gene.strip() for gene in self.genes):
|
|
47
|
+
raise ValueError("genes must contain only non-empty strings")
|
|
48
|
+
if not isinstance(self.function_name, str) or not self.function_name.strip():
|
|
49
|
+
raise ValueError("function_name must be a non-empty string")
|
|
50
|
+
if not isinstance(self.research_background_template, str) or not self.research_background_template.strip():
|
|
51
|
+
raise ValueError("research_background_template must be a non-empty string")
|
|
52
|
+
if not isinstance(self.project_folder, (str, Path)):
|
|
53
|
+
raise TypeError("project_folder must be a string or pathlib.Path")
|
|
54
|
+
if not str(self.project_folder).strip():
|
|
55
|
+
raise ValueError("project_folder must be a non-empty path")
|
|
56
|
+
if not isinstance(self.max_papers_per_gene, int) or isinstance(self.max_papers_per_gene, bool):
|
|
57
|
+
raise TypeError("max_papers_per_gene must be an integer")
|
|
58
|
+
if self.max_papers_per_gene < 1:
|
|
59
|
+
raise ValueError("max_papers_per_gene must be at least 1")
|
|
60
|
+
if not isinstance(self.scispacy_model, str):
|
|
61
|
+
raise TypeError("scispacy_model must be a string")
|
|
62
|
+
validate_scispacy_model_name(self.scispacy_model)
|
|
63
|
+
if isinstance(self.keywords, str) or not isinstance(self.keywords, list):
|
|
64
|
+
raise TypeError("keywords must be a list of search terms")
|
|
65
|
+
if any(not isinstance(keyword, str) or not keyword.strip() for keyword in self.keywords):
|
|
66
|
+
raise ValueError("keywords must contain only non-empty strings")
|
|
67
|
+
if isinstance(self.exclude_pmids, str) or not isinstance(self.exclude_pmids, list):
|
|
68
|
+
raise TypeError("exclude_pmids must be a list of PMID values")
|
|
69
|
+
if any(not isinstance(pmid, str) or not pmid.strip() for pmid in self.exclude_pmids):
|
|
70
|
+
raise ValueError("exclude_pmids must contain only non-empty strings")
|
|
71
|
+
if not isinstance(self.gene_aliases, dict):
|
|
72
|
+
raise TypeError("gene_aliases must map gene symbols to lists of aliases")
|
|
73
|
+
if any(
|
|
74
|
+
not isinstance(gene, str)
|
|
75
|
+
or isinstance(aliases, str)
|
|
76
|
+
or not isinstance(aliases, list)
|
|
77
|
+
or any(not isinstance(alias, str) for alias in aliases)
|
|
78
|
+
for gene, aliases in self.gene_aliases.items()
|
|
79
|
+
):
|
|
80
|
+
raise ValueError("gene_aliases must map gene symbols to lists of alias strings")
|
|
81
|
+
if isinstance(self.publication_date_before, datetime):
|
|
82
|
+
self.publication_date_before = self.publication_date_before.date().isoformat()
|
|
83
|
+
elif isinstance(self.publication_date_before, date):
|
|
84
|
+
self.publication_date_before = self.publication_date_before.isoformat()
|
|
85
|
+
elif self.publication_date_before is not None and not isinstance(self.publication_date_before, str):
|
|
86
|
+
raise TypeError("publication_date_before must be a date string or date value")
|
|
87
|
+
|
|
88
|
+
def to_analysis_data(self) -> AnalysisData:
|
|
89
|
+
"""Map the public config onto the canonical pipeline state model."""
|
|
90
|
+
from .core.data_models import AnalysisData
|
|
91
|
+
|
|
92
|
+
return AnalysisData(
|
|
93
|
+
genes=list(self.genes),
|
|
94
|
+
function_name=self.function_name,
|
|
95
|
+
research_background_template=self.research_background_template,
|
|
96
|
+
keywords=list(self.keywords),
|
|
97
|
+
function_query=self.function_query,
|
|
98
|
+
exclude_pmids=list(self.exclude_pmids),
|
|
99
|
+
publication_date_before=self.publication_date_before,
|
|
100
|
+
gene_aliases={gene: list(aliases) for gene, aliases in self.gene_aliases.items()},
|
|
101
|
+
max_papers_per_gene=self.max_papers_per_gene,
|
|
102
|
+
scispacy_model=self.scispacy_model,
|
|
103
|
+
project_folder=str(Path(self.project_folder).expanduser()),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class AnalysisResult:
|
|
109
|
+
"""Paths and restored state produced by a completed analysis."""
|
|
110
|
+
|
|
111
|
+
analysis_directory: Path
|
|
112
|
+
report_path: Path
|
|
113
|
+
analysis_data: AnalysisData
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@log_operation("GeneResearcher analysis", log_failure=True)
|
|
117
|
+
async def run_analysis_async(
|
|
118
|
+
config: ResearchConfig,
|
|
119
|
+
*,
|
|
120
|
+
env_file: str | Path | None = None,
|
|
121
|
+
) -> AnalysisResult:
|
|
122
|
+
"""Run the complete pipeline from asynchronous Python code."""
|
|
123
|
+
if not isinstance(config, ResearchConfig):
|
|
124
|
+
raise TypeError("config must be a ResearchConfig instance")
|
|
125
|
+
|
|
126
|
+
# Validate dependencies and load the large NLP model before initializing
|
|
127
|
+
# services or clearing any previous analysis database.
|
|
128
|
+
from .runtime_dependencies import load_scispacy_model
|
|
129
|
+
|
|
130
|
+
spacy_nlp = load_scispacy_model(config.scispacy_model)
|
|
131
|
+
analysis_data = config.to_analysis_data()
|
|
132
|
+
from .core.naming import analysis_directory_name
|
|
133
|
+
from .run import GeneResearchPipeline
|
|
134
|
+
|
|
135
|
+
analysis_directory = Path(analysis_data.project_folder) / (
|
|
136
|
+
f"analysis_{analysis_directory_name(config.function_name)}"
|
|
137
|
+
)
|
|
138
|
+
database_path = analysis_directory / "step2_embeddings.db"
|
|
139
|
+
if database_path.exists():
|
|
140
|
+
warnings.warn(
|
|
141
|
+
f"Existing analysis data will be cleared before this full run: {database_path}",
|
|
142
|
+
UserWarning,
|
|
143
|
+
stacklevel=2,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
pipeline = GeneResearchPipeline(
|
|
147
|
+
project_folder=analysis_data.project_folder,
|
|
148
|
+
env_file=env_file,
|
|
149
|
+
spacy_nlp=spacy_nlp,
|
|
150
|
+
)
|
|
151
|
+
completed_data = await pipeline.run_full_analysis(analysis_data)
|
|
152
|
+
return AnalysisResult(
|
|
153
|
+
analysis_directory=analysis_directory,
|
|
154
|
+
report_path=analysis_directory / "analysis_report.md",
|
|
155
|
+
analysis_data=completed_data,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def run_analysis(
|
|
160
|
+
config: ResearchConfig,
|
|
161
|
+
*,
|
|
162
|
+
env_file: str | Path | None = None,
|
|
163
|
+
) -> AnalysisResult:
|
|
164
|
+
"""Run the complete pipeline from regular synchronous Python code."""
|
|
165
|
+
try:
|
|
166
|
+
asyncio.get_running_loop()
|
|
167
|
+
except RuntimeError:
|
|
168
|
+
return asyncio.run(run_analysis_async(config, env_file=env_file))
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
"run_analysis() cannot run inside an active event loop; "
|
|
171
|
+
"use `await run_analysis_async(...)` instead."
|
|
172
|
+
)
|