GEOMeta is an LLM-guided framework for sample-level metadata extraction, semantic standardization, controlled inference, biomedical mapping, and quality-controlled release generation for GEO transcriptomic studies.
GEO metadata are highly heterogeneous across studies due to inconsistent free-text annotations, incomplete sample descriptions, variable disease terminology, fragmented tissue naming conventions, and inconsistent experimental metadata reporting.
GEOMeta addresses these challenges through a fixed multi-stage workflow that combines:
- Context-aware sample-level metadata extraction
- Field-specific semantic standardization
- Controlled metadata inference
- Disease, tissue, RNA-source, and perturbation mapping
- Deterministic and evidence-grounded quality control
- Review-aware recovery and release generation
The current curated GEOMeta release comprises 594,989 GSM samples from 22,782 unique GSE studies, spanning diverse disease, tissue, demographic, and perturbation contexts.
The installation command retrieves compatible dependencies from conda-forge. The successful installation and demo were tested with Python 3.11.16, NumPy 2.4.6, pandas 3.0.6, scikit-learn 1.9.1, openpyxl 3.1.5, fastparquet 2026.5.0, requests 2.34.2, python-docx 1.2.0, RapidFuzz 3.14.6, and OpenAI Python SDK 3.17.0. Dependency versions are not pinned and may vary across installations. Users can inspect their installed versions using conda list -n geometa.
The installation and demo were tested on macOS 26.5 using an Apple M2 Pro MacBook Pro with 32 GB RAM and a Conda osx-64 environment. The standard API-based workflow does not require a local GPU. Local model deployments may have additional hardware requirements.
First, move to the local folder where you want to download GEOMeta:
cd /path/to/your/workspaceThen clone the repository:
git clone https://github.com/Bin-Chen-Lab/GEOMeta.git
cd GEOMetaThe git clone command creates a local folder named GEOMeta in the selected workspace. The cd GEOMeta command enters the GEOMeta project folder.
GEOMeta has been tested with Python 3.11 and 3.12. A clean conda-forge environment is recommended.
If your shell inherits library paths from another Anaconda installation, unset them first:
unset DYLD_LIBRARY_PATH
unset DYLD_FALLBACK_LIBRARY_PATH
unset LD_LIBRARY_PATHCreate and activate the environment:
conda create -n geometa -c conda-forge --override-channels \
python=3.11 \
numpy \
pandas \
openpyxl \
scikit-learn \
requests \
fastparquet \
python-docx \
rapidfuzz \
openai \
expat \
libexpat \
-y
conda activate geometaOptional dependency check:
python - <<'PY'
import pandas, sklearn, fastparquet, openai, openpyxl, docx, rapidfuzz
print("GEOMeta dependencies imported successfully.")
PYInstallation took approximately 2 minutes and 19 seconds on a MacBook Pro (Apple M2 Pro, 12-core CPU, 32 GB RAM) using Python 3.11 and Conda (osx-64). This included repository cloning (2 seconds), environment creation and dependency installation (49 seconds), and dependency verification (88 seconds). Installation time may vary depending on hardware, network conditions, and Conda package caching. LLM API account setup is excluded.
GEOMeta uses an OpenAI-compatible Chat Completions interface. The backend is controlled by four environment variables:
| Variable | Description |
|---|---|
LLM_API_TYPE |
Use openai_compatible for the backends below. |
LLM_BASE_URL |
Base URL of the selected endpoint. |
LLM_API_KEY |
API key or local-server placeholder key. |
LLM_MODEL |
Exact model identifier exposed by the provider or server. |
The current GEOMeta configuration defaults to the direct OpenAI endpoint and gpt-5.
export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="https://api.openai.com/v1"
export LLM_API_KEY="your_openai_api_key"
export LLM_MODEL="gpt-5"Users may replace gpt-5 with another OpenAI model that supports Chat Completions. Model availability and model names can change over time.
For direct OpenAI use, OPENAI_API_KEY and OPENAI_MODEL are also accepted as aliases, although the generic LLM_* variables are recommended for easier provider switching.
export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="https://openrouter.ai/api/v1"
export LLM_API_KEY="your_openrouter_key"
export LLM_MODEL="google/gemini-3-flash-preview"The Gemini-3-flash-preview model above was used for the demo runtime benchmark described below. Replace it with another model identifier available through your OpenRouter account if desired.
Other OpenAI-compatible backends
For a local LiteLLM proxy running on its default port:
export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="http://localhost:4000"
export LLM_API_KEY="your_litellm_key"
export LLM_MODEL="<MODEL-NAME-CONFIGURED-IN-LITELLM>"export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="http://localhost:8000/v1"
export LLM_API_KEY="your_vllm_api_key"
export LLM_MODEL="<VLLM-MODEL-NAME>"If the server was started without authentication, GEOMeta still requires a non-empty LLM_API_KEY; a placeholder value can be used.
export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="http://localhost:11434/v1"
export LLM_API_KEY="ollama"
export LLM_MODEL="<OLLAMA-MODEL-NAME>"For local Ollama, the API-key value is required by the client but is normally ignored by the server.
export LLM_API_TYPE="openai_compatible"
export LLM_BASE_URL="http://localhost:1234/v1"
export LLM_API_KEY="lm-studio"
export LLM_MODEL="<LM-STUDIO-MODEL-ID>"Use the model identifier shown for the model loaded in LM Studio.
From the GEOMeta repository root:
python - <<'PY'
from pathlib import Path
from geo_annotation_agent.config import default_config
cfg = default_config(Path("."))
cfg.validate_env()
print("LLM_API_TYPE:", cfg.llm_api_type)
print("LLM_BASE_URL:", cfg.llm_base_url)
print("LLM_MODEL:", cfg.llm_model)
print("LLM configuration detected successfully.")
PYThis verifies that GEOMeta can read the selected backend configuration without printing the API key. Model choice can affect annotation quality, runtime, API cost, and supported context length. For local or smaller-context models, make sure the model context window is compatible with the token-budget settings in geo_annotation_agent/config.py before running very large GSEs.
Do not store API keys directly in the codebase or commit them to GitHub.
The repository includes an example input file:
input/gse_ids.csv
The file should contain a column named GSE_ID:
GSE_ID
GSE130063
GSE53779
Replace the example accessions with your own GSE list. Supported input formats also include Excel, TSV, and TXT.
For routine annotation runs:
PYTHONPATH=. python -u scripts/run_pipeline.py \
--workdir . \
--gse-file input/gse_ids.csv \
--run-version geometa_run \
--stage1-qa3-mode smartMain arguments:
--workdir .uses the current repository root as the working directory.--gse-filespecifies the GSE input file.--run-versiondefines the prefix used for generated outputs.--stage1-qa3-mode smartenables prioritized, targeted QA3 review.
smart— Recommended for routine use. Performs targeted evidence-grounded review of selected fields and samples requiring additional validation, with additional runtime and API costs.off— Disables QA3 review for faster execution and lower API costs.full— Performs broader QA3 review for comprehensive auditing or benchmarking, potentially requiring substantially more runtime and API usage.
Eligible high-confidence QA3 corrections can be applied according to the configured release policy, while unresolved or ambiguous cases are retained for review.
PYTHONPATH=. python -u scripts/run_pipeline.py \
--workdir . \
--gse GSE130063 \
--run-version geometa_GSE130063 \
--stage1-qa3-mode smartThe example input file (input/gse_ids.csv) contains two GEO studies, GSE130063 (24 samples) and GSE53779 (15 samples), comprising 39 samples. Using Gemini 3 Flash Preview (google/gemini-3-flash-preview) through OpenRouter, GEOMeta completed the demo in approximately 2 minutes and 15 seconds with QA3 disabled (--stage1-qa3-mode off) and 4 minutes and 49 seconds with targeted QA3 review enabled (--stage1-qa3-mode smart). Both runs were performed on a MacBook Pro (Apple M2 Pro, 32 GB RAM), including Conda startup and using previously retrieved GEO metadata from the local cache. Runtime may vary depending on dataset size, model selection, API latency, QA configuration, and cache availability.
To run the quick demo with QA3 disabled, use:
PYTHONPATH=. python -u scripts/run_pipeline.py \
--workdir . \
--gse-file input/gse_ids.csv \
--run-version geometa_demo \
--stage1-qa3-mode offThe pipeline generates intermediate annotations and final standardized metadata under artifacts/outputs/. The simplified final release is saved as geometa_demo_stage3_final_release.xlsx for this example. Other runs use the filename prefix specified by --run-version.
Retrieves GSE- and GSM-level metadata directly from NCBI GEO and constructs annotation-ready metadata blocks.
Key features include local caching, retry/recovery, metadata- and token-aware chunking, and structured GSE/GSM metadata preparation.
Implemented in:
geo_annotation_agent/stage0_retrieve.py
Performs structured GSM-level metadata extraction through four task-specific annotation agent calls covering experimental context, biological context, perturbation, and sample metadata.
Each call combines GEO study/sample context, task-specific instructions, restricted target fields, and a structured output contract. The outputs are merged into the canonical 27-field GEOMeta sample-level schema.
Stage 1 is followed by deterministic within-GSE consistency checks, cross-field validation, and optional evidence-grounded QA3 review.
Implemented in:
geo_annotation_agent/stage1_annotate.py
Prompts:
Annotation_Prompts/
Stage 2 applies 21 field-specific standardization agent tasks. Each combines a reusable prompt template, candidate terms from one metadata field, field-restricted instructions, and a structured output contract.
Three controlled-inference tasks are also performed:
- Age group from standardized age
- Sex from clearly sex-specific tissue context when sex is otherwise unavailable
- Perturbation type from the standardized perturbation term
If a field contains no eligible non-missing terms, GEOMeta preserves the input value without making an unnecessary LLM call.
Implemented in:
geo_annotation_agent/stage2_postprocess.py
Prompts:
postprocessing/
inference/
Stage 3 links standardized metadata to curated biomedical vocabularies and external reference resources through reviewed mapping reuse, task-specific LLM-assisted mapping or semantic matching where applicable, and deterministic reference lookup.
Major workflows include:
- Disease: CTD/MEDIC mapping and disease hierarchy information
- Tissue: curated GEOMeta tissue vocabulary
- RNA source: reviewed mappings and cell-line reference matching
- Chemical perturbations: reviewed mapping reuse and PubChem lookup with title/synonym verification
- Release validation: deterministic checks of mapped fields, category consistency, within-GSE/global consistency, and perturbation mapping integrity
Implemented in:
geo_annotation_agent/stage3_map.py
geo_annotation_agent/stage4_validate_release.py
Mapping resources:
mappings/
Runtime files are generated under:
artifacts/
Main subdirectories:
artifacts/
├── outputs/
├── ledgers/
├── mapping_cache/
├── review_queue/
├── manual_review/
├── geo_cache/
└── runs/
Representative outputs:
| File | Description |
|---|---|
*_stage0_input.parquet |
Full-fidelity GEO retrieval output |
*_stage1_raw.xlsx |
Raw Stage 1 sample-level annotations |
*_stage1_final_for_stage2.xlsx |
Stage 1 output after QA |
*_stage2_post_final.xlsx |
Final Stage 2 standardized annotations |
*_stage3_mapped.xlsx |
Full mapped dataset |
*_stage3_mapped_filtered.xlsx |
Filtered mapped dataset |
*_stage3_final_release.xlsx |
Simplified final release dataset |
*_stage3_cp_perturbation_release.xlsx |
Chemical perturbation-focused release |
*_stage4_release_mapping_qa.xlsx |
Deterministic release-level mapping QA report |
The pipeline also generates review queues, ledgers, caches, novel-term reports, and a run-level output index.
The full pipeline is recommended for most users. Downstream stages can also be rerun from saved outputs.
PYTHONPATH=. python scripts/run_stage2.py \
--workdir . \
--stage1 artifacts/outputs/<run_version>_stage1_raw.xlsx \
--run-version <run_version>PYTHONPATH=. python scripts/run_stage3.py \
--workdir . \
--stage2 artifacts/outputs/<run_version>_stage2_post_final.xlsx \
--run-version <run_version>Replace <run_version> with the identifier used for the corresponding run.
scripts/ Pipeline execution scripts
geo_annotation_agent/ Core pipeline implementation
Annotation_Prompts/ Stage 1 annotation prompts
postprocessing/ Stage 2 standardization prompts
inference/ Controlled-inference prompts
mappings/ Mapping resources and reviewed reference files
input/ Input GSE accession lists
figures/ Workflow figures and diagrams
artifacts/ Runtime outputs, caches, ledgers, and review files
- Disease: CTD/MEDIC and MeSH-compatible disease hierarchy information
- Tissue: curated GEOMeta tissue vocabulary and brain-region normalization
- Chemical perturbations: reviewed mappings and PubChem
- RNA source/cell lines: reviewed RNA-source mappings and cell-line reference metadata
GEOMeta maintains persistent runtime caches for GEO retrieval, field-specific standardization, controlled inference, and biomedical mapping.
Intermediate outputs, ledgers, caches, and review artifacts support restart, auditing, and iterative refinement. If prompt rules or mapping resources are intentionally changed, clear the corresponding runtime cache before evaluating the updated configuration.
If library paths point to another Anaconda/Python installation:
unset DYLD_LIBRARY_PATH
unset DYLD_FALLBACK_LIBRARY_PATH
unset LD_LIBRARY_PATHThen recreate the GEOMeta conda environment.
Confirm that the environment is active:
conda activate geometaInstall the missing package from conda-forge if needed.
conda install -c conda-forge fastparquet -yThe final line of a multi-line shell command should not end with \.
Wrap paths containing spaces in quotation marks:
cd "/path/containing spaces/GEOMeta"ls -lh input/gse_ids.csvIf LLM client initialization fails with a truststore error related to macOS version detection, try launching GEOMeta through conda run:
conda run --no-capture-output -n geometa \
env PYTHONPATH=. python -u scripts/run_pipeline.py \
--workdir . \
--gse-file input/gse_ids.csv \
--run-version geometa_run \
--stage1-qa3-mode smartThis workaround was successfully used during testing on macOS 26.5 with Python 3.11.
- GEO metadata completeness and quality vary substantially across studies.
- Source metadata may contain ambiguous, missing, or inconsistent descriptions that cannot always be resolved automatically.
- LLM outputs are constrained through task-specific instructions, structured output contracts, deterministic checks, and review-aware workflows.
- Large-scale processing may require substantial LLM API quota depending on dataset size, metadata complexity, selected model, and QA configuration.
GEOMeta makes use of data and reference resources from:
- NCBI Gene Expression Omnibus (GEO)
- Comparative Toxicogenomics Database (CTD/MEDIC)
- PubChem
- Human Protein Atlas
- Cancer Dependency Map (DepMap), Broad Institute
If you use GEOMeta in your work, please cite:
Zhang X, Paithankar S, Pu J, Murtaza MS, Shankar R, Leshchiner D, Koirala S, Palmer Z, Nault R, Li X, Xie Y, Chen B. Automating scientific annotations for open transcriptomic profiles via multi-stage agents. bioRxiv (2026). https://doi.org/10.64898/2026.08.19.745739
