Metadata-Version: 2.4
Name: aiva-agent
Version: 0.3.13
Summary: Clinical-genomics agent: ask natural-language questions over a local VCF and get literature-grounded answers.
Author-email: Tarun Mamidi <tarun@mamidi.ai>
License-Expression: LicenseRef-PolyForm-Noncommercial-1.0.0
Project-URL: Homepage, https://github.com/MHSPL/aiva-agent
Project-URL: Repository, https://github.com/MHSPL/aiva-agent
Project-URL: Issues, https://github.com/MHSPL/aiva-agent/issues
Keywords: genomics,vcf,variant-classification,acmg,bioinformatics,clinical-genomics
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Healthcare Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai-agents==0.14.8
Requires-Dist: openai==2.33.0
Requires-Dist: duckdb==1.5.2
Requires-Dist: httpx==0.28.1
Requires-Dist: requests==2.33.1
Requires-Dist: myvariant==1.0.0
Requires-Dist: python-dotenv==1.2.2
Requires-Dist: pysam==0.23.3
Provides-Extra: dev
Requires-Dist: aiva-agent[web]; extra == "dev"
Requires-Dist: pytest==9.0.3; extra == "dev"
Requires-Dist: pytest-asyncio==1.3.0; extra == "dev"
Requires-Dist: respx==0.23.1; extra == "dev"
Requires-Dist: responses==0.26.0; extra == "dev"
Provides-Extra: web
Requires-Dist: fastapi==0.136.1; extra == "web"
Requires-Dist: uvicorn[standard]==0.46.0; extra == "web"
Requires-Dist: jinja2==3.1.6; extra == "web"
Dynamic: license-file

# aiva-agent

A standalone clinical-genomics agent. Ask natural-language questions about a local pre-annotated VCF, gather variant annotations, search literature, find clinical trials, prioritize genes from HPO phenotypes, and run ACMG/AMP variant classification — using **any** OpenAI-compatible provider (OpenAI, Anthropic, xAI Grok, Together, Fireworks, OpenRouter, etc.).

```bash
export LLM_MODEL=gpt-5.5
export LLM_BASE_URL=https://api.openai.com/v1
export LLM_API_KEY=sk-...
aiva_agent --vcf data/test.vcf.gz \
  --prompt "3yo female with developmental regression, hand stereotypies, and acquired microcephaly. Find candidate variants and propose the most likely diagnosis with supporting evidence."
```

## Contents

- [What it does](#what-it-does)
- [Prerequisites](#prerequisites)
- [Quickstart](#quickstart)
- [Full configuration](#full-configuration)
- [Examples](#examples)
- [Web UI](#web-ui)
- [License](#license)

## What it does

The agent runs locally over a pre-annotated tabix-indexed VCF and orchestrates a curated set of tools to answer questions about variants, retrieve supporting literature, surface clinical trials, prioritize candidate genes from phenotype terms, and run ACMG/AMP variant classification. Tools are exposed under the following names and can be selectively turned off via `--disable`:

| Tool | What it does |
|---|---|
| `vcf` | Queries over your tabix-indexed `.vcf.gz` file. |
| `annotate` | Variant annotation. Supports human and plant species. |
| `literature` | PubMed / PMC search with gene / disease / variant / chemical entity annotations. |
| `trials` | Clinical-trials search by condition, intervention, gene/variant, phase, recruiting status; full-detail retrieval by NCT ID. |
| `rank_genes` | Rank candidate genes for a list of HPO phenotype terms. Can use negative terms to exclude genes. |
| `web` | Web search and clean content extraction from any URL. |
| `classify` | ACMG/AMP 2015 (germline) and AMP/ASCO/CAP 2017 (somatic) classification and returns a JSON classification. |
| `bash` | Run shell commands in your working directory. Lets the agent peek at any file the other tools don't cover (CSV, TSV, Excel, JSON, parquet, plain text), ask read-level questions about BAM/CRAM alignments for variant validation / phase / SV evidence (paths declared with `--bam`), and use whatever tools your environment has on PATH. Scope with `--workdir` or `AIVA_WORKDIR`; defaults to the current directory. |
| `manage_todos` | Plan and track multi-step work within a single run. The agent creates a todo list up front, marks items in progress as it starts each one, and completes them as it goes. State is per-run (not persisted across sessions). |

## Prerequisites

- **Python 3.12+** with `pip ≥ 24` (for `pip install`).
- **htslib tools** (`bgzip`, `tabix`) only needed for preparing VCFs (`brew install htslib` on macOS).
- **Linux: glibc ≥ 2.28** (Ubuntu ≥ 20.04, RHEL/Rocky ≥ 8, Debian ≥ 10) for the `vcf` tool. Older hosts can use the container image — see [Running on HPC](#running-on-hpc--older-glibc).
- **Internet connectivity** — For HPC/cloud/remote environments/jobs, ensure outbound HTTPS to your LLM provider and to the public APIs the agent queries (NCBI E-utilities, ClinicalTrials.gov, DuckDuckGo, etc.). Behind a corporate proxy, set `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` (Python clients honor these automatically); for MITM proxies, also set `REQUESTS_CA_BUNDLE` to your org's root cert. Only the local `vcf` tool runs without network.

## Quickstart

### 1. Install (pick one)

**pip** — fastest if you have Python 3.12+ on a modern Linux/macOS host:
```bash
pip install aiva-agent
```

**Docker** — works anywhere a container runs (Docker, Podman, Apptainer/Singularity). Useful when your host can't install Python or has older glibc (see [Running on HPC](#running-on-hpc--older-glibc)):
```bash
docker pull mhspl/aiva-agent:latest
```

**Python import** — call from a notebook or script (see [Notebook usage](#notebook-usage)):
```python
from aiva_agent import aiva_agent
```

### 2. Configure

Set three env vars for your model provider. Drop them in a `.env` file in your working directory and the agent loads them automatically:

```dotenv
LLM_MODEL=gpt-5.5
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=sk-...
```

For one-off shells you can `export` the same variables instead. The agent works with any OpenAI-compatible endpoint. For the full list of env vars and CLI flags, see [Full configuration](#full-configuration).

**Some tools need their own key.** `web_search` needs `WEB_API_KEY` — a [Firecrawl](https://www.firecrawl.dev) key (free tier ≈1,000 page fetches/month). Without it, the `web` tool auto-disables on startup with a stderr warning and the rest of the agent runs normally (the `vcf` tool behaves the same way when no `--vcf`/`AIVA_VCF` is set). See [Full configuration](#full-configuration) for every optional key.

**Security note:** prefer `.env` or `export LLM_API_KEY=...` over `--api-key sk-...` so the secret doesn't leak into shell history or `ps`.

### 3. Run

Prepare a tabix-indexed VCF (only once per file):
```bash
bgzip -k path/to/sample.vcf
tabix -p vcf path/to/sample.vcf.gz
```

**Tip: prefer a pre-annotated VCF.** If you've already run your VCF through a variant-effect annotator (VEP, SnpEff, ANNOVAR, …), the agent can read those annotations directly from the file. Pre-annotation is recommended for variant prioritization and classification.

Then ask a question:
```bash
aiva_agent --vcf path/to/sample.vcf.gz \
  --prompt "List pathogenic variants"
```

Same invocation with Docker (bind-mount the VCF directory):
```bash
docker run --rm --env-file .env -v "$PWD/data:/work" \
  mhspl/aiva-agent:latest \
  --vcf /work/sample.vcf.gz --prompt "List pathogenic variants"
```

The same image also serves the browser UI — see [Web UI → With Docker](#web-ui).

## Full configuration

You can drive everything via flags or env vars. Precedence: **CLI flag > shell export > `.env` value**.

| Variable | CLI flag | Purpose | Default |
|---|---|---|---|
| `LLM_MODEL` | `--model` | Model ID for the provider (e.g. `gpt-5.5`, `claude-opus-4-7`). | — |
| `LLM_BASE_URL` | `--base-url` | Provider's OpenAI-compatible base URL. | — |
| `LLM_API_KEY` | `--api-key` | Provider API key. | — |
| `AIVA_VCF` | `--vcf` | Default VCF path or `alias=path,...` spec. | unset (vcf tool auto-disables) |
| `AIVA_BAM` | `--bam` | Default BAM/CRAM path or `alias=path,...` spec. Read-level queries go through the `bash` tool, so keep bash enabled if you want to ask BAM questions. Requires a `.bai`/`.csi`/`.crai` index next to each file. | unset |
| `AIVA_WORKDIR` | `--workdir` | Working directory for the bash tool. | current directory at invocation |
| `AIVA_DISABLE` | `--disable` | Comma-separated tools to disable. | unset (all tools on) |
| `AIVA_MAX_TURNS` | — | Max agent turns per run. | `25` |
| `AIVA_FORCE` | `--force` | Overwrite `-o` destination without passing `--force`. Accepts `1/true/yes/on`. | unset |
| `AIVA_SESSION_ID` | `--session-id` | Conversation session ID; persist history across runs in `<workdir>/.aiva/sessions.db`. | new UUID per run |
| `AIVA_STREAM` | `--stream` | Force streaming on (`1/true/yes/on`) or off (`0/false/no/off`). Also honored by the notebook/script entrypoint; default is off there since Jupyter has no TTY. | auto (on if stdout is a TTY and `--output` is unset) |
| `AIVA_STREAM_TOOL_OUTPUT` | — | When streaming, also dump each tool's output to stderr (debug aid). Honored in notebook mode too whenever streaming is on. Accepts `1/true/yes/on`. | unset (off) |
| `AIVA_STREAM_TOOL_OUTPUT_MAX` | — | Cap on chars per tool output when the dump above is on. Honored in notebook mode too. `0` means unlimited. | `2000` |
| `WEB_API_KEY` | — | Key for the `web_search` tool (web search + URL/PDF scrape). See `.env.example` for sign-up details. Without the key, the web tool auto-disables on startup (warning to stderr) — same pattern as `AIVA_VCF`. | unset (web tool auto-disables) |
| `AIVA_HOST` | `--host` | Web UI only (`aiva_agent_serve`): interface to bind. Set to `0.0.0.0` to reach the server from outside its host/container. | `127.0.0.1` |
| `AIVA_PORT` | `--port` | Web UI only (`aiva_agent_serve`): port to bind. | `8765` |

Copy-paste template to your `.env` file:

```dotenv
# aiva-agent uses any OpenAI-compatible LLM provider.

# Model ID exactly as the provider expects.
# Examples: gpt-5.5, claude-opus-4-7, grok-2, meta-llama/Llama-3.1-70B-Instruct
LLM_MODEL=gpt-5.5

# Provider base URL — examples:
#   OpenAI:     https://api.openai.com/v1
#   Anthropic:  https://api.anthropic.com/v1
#   xAI Grok:   https://api.x.ai/v1
#   OpenRouter: https://openrouter.ai/api/v1
#   Together:   https://api.together.xyz/v1
#   Fireworks:  https://api.fireworks.ai/inference/v1
#   DeepSeek:   https://api.deepseek.com/v1
#   Groq:       https://api.groq.com/openai/v1
LLM_BASE_URL=https://api.openai.com/v1

# API key for the chosen provider. Keep the real .env OUT of source control.
LLM_API_KEY=your-provider-api-key

# Optional: default VCF spec. Two forms:
#   single:  AIVA_VCF=data/sample.vcf.gz
#   multi:   AIVA_VCF=proband=p.vcf.gz,father=f.vcf.gz,mother=m.vcf.gz
# For trios/cohorts, prefer a joint-called multisample VCF (single path) when you have one.
# --vcf overrides this per-run; pass --disable vcf to skip the tool entirely.
# AIVA_VCF=data/sample.vcf.gz

# Optional: default BAM/CRAM spec. Same syntax as AIVA_VCF:
#   single:  AIVA_BAM=data/sample.bam
#   multi:   AIVA_BAM=tumor=t.bam,normal=n.bam
# Each BAM must have a `.bai`/`.csi`/`.crai` index next to it for region queries.
# Read-level queries go through the bash tool, so keep bash enabled if you
# want to ask BAM questions. --bam overrides this per-run.
# AIVA_BAM=data/sample.bam

# Optional: working directory for the bash tool. The agent's shell commands run
# inside this directory, so scope it to the folder containing the side files
# (CSVs, panels, notes) you want it to read. --workdir overrides per-run; pass
# --disable bash to drop the tool entirely.
# AIVA_WORKDIR=./data

# Optional: comma-separated tools to disable by default.
# Choices: vcf, annotate, literature, trials, rank_genes, web, classify, bash, manage_todos
# --disable on the CLI replaces (does not merge with) this value.
# AIVA_DISABLE=vcf                #single-tool disable
# AIVA_DISABLE=web,annotate       #multi-tool disable

# Optional: max agent turns per run. Default 25.
# AIVA_MAX_TURNS=40

# Required for the `web_search` tool (web search + URL/PDF scrape).
# Backed by Firecrawl — sign up at https://www.firecrawl.dev to get a key.
# Free tier covers 1,000 page fetches/month, no credit card required.
# Without this, the web tool auto-disables on startup (with a stderr
# warning) — like the vcf tool when AIVA_VCF is unset.

# Optional: always overwrite --output destination without --force. Useful in
# CI / scripted pipelines where re-runs are expected. Accepts 1/true/yes/on.
# AIVA_FORCE=1

# Optional: conversation session ID. Reuse the same value across `aiva` runs to
# continue a conversation; history is stored in <workdir>/.aiva/sessions.db (local
# sqlite database, no server) so each workdir gets its own session namespace. If
# unset, each run gets a fresh UUID and is effectively stateless.
# AIVA_SESSION_ID=my-case-2026-05

# Optional: stream the agent's response live as it runs tools and replies,
# instead of waiting for the full answer. Also toggled by the --stream CLI flag.
# Defaults on in a terminal, off when piping or using --output.
# Accepts 1/true/yes/on or 0/false/no/off.
# AIVA_STREAM=1

# Optional: when streaming, also dump each tool's output after the
# `[tool] done` marker. Off by default since outputs can be very large
# (multi-MB JSON). Accepts 1/true/yes/on. Useful for debugging tool behavior.
# AIVA_STREAM_TOOL_OUTPUT=1

# Optional: cap on chars per tool output when AIVA_STREAM_TOOL_OUTPUT is on.
# Default 2000. Set to 0 for unlimited (warning: may flood your terminal).
# AIVA_STREAM_TOOL_OUTPUT_MAX=2000
```

CLI-only flags (per-invocation, no env equivalent):

| Flag | Purpose |
|---|---|
| `--prompt TEXT` / `--prompt-file PATH` | The question to ask. One of these is required. `--prompt -` reads from stdin. |
| `-o OUTPUT` | Write the agent's final answer to a file instead of stdout. |

## Examples

For an end-to-end walkthrough in a notebook, see the example analysis on Colab:

[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1G_4dG1XkBeulH3uJajseGDl3wUjyByJF?usp=sharing)

CLI one-liners:

```bash
# All tools are on by default — pass --disable vcf for prompts that don't need
# a local VCF, or set AIVA_DISABLE / AIVA_VCF in .env to make it permanent.

# Variant annotation
aiva_agent --disable vcf --model gpt-5.5 \
  --prompt "Annotate rs113488022. Report ClinVar significance and population AF."

# Literature search
aiva_agent --disable vcf --model gpt-5.5 \
  --prompt "Find 3 recent papers on TP53 R175H in lung cancer."

# Clinical trials
aiva_agent --disable vcf --model gpt-5.5 \
  --prompt "Find phase 2 recruiting BRAF V600E melanoma trials."

# HPO -> genes
aiva_agent --disable vcf --model gpt-5.5 \
  --prompt "Rank candidate genes for HP:0001250 + HP:0001263."

# Web search + scrape (requires WEB_API_KEY; handles PDFs)
aiva_agent --disable vcf --model gpt-5.5 \
  --prompt "Find and scrape the latest NCCN melanoma guideline summary."

# ACMG/AMP classification — `classify` subcommand prints JSON (rs113488022 = BRAF V600E)
aiva_agent classify --type amp --assembly GRCh38 \
  --rsid rs113488022 --phenotype melanoma

# VCF query + literature (vcf tool turns on automatically once a path is provided)
aiva_agent --vcf data/test.vcf.gz --model gpt-5.5 \
  --prompt "Find any TP53 variants and pull supporting literature."
```

## Web UI

A local browser chat for the same agent — useful when you want to drag-and-drop VCFs, browse multiple chats side-by-side, and read structured tabular results inline instead of as TSV in a terminal. Everything still runs on your machine; no data leaves the host.

### Install

The web UI ships behind an optional extra so the terminal CLI stays lean. Add it on top of the base install:

```bash
pip install 'aiva-agent[web]'
```

### Run

Point the server at the directory that holds your VCFs / BAMs:

```bash
aiva_agent_serve --workdir ./data
```

Open `http://127.0.0.1:8765` in a browser. Then **+ New chat** → pick one or more VCF (and optional BAM) files from `--workdir`, give it a name, and ask questions.

Make sure to set `AIVA_WORKDIR` environment variable to the directory that holds your VCFs / BAMs.

**With Docker.** The published image ships the `[web]` extra, so the server is available by overriding the entrypoint. Bind `0.0.0.0` so the container is reachable from the host, publish the port, and let your `.env` supply the model credentials:

```bash
docker run --rm --env-file .env -p 8765:8765 -v "$PWD/data:/work" \
  -e AIVA_HOST=0.0.0.0 \
  --entrypoint aiva_agent_serve \
  mhspl/aiva-agent:latest --workdir /work
```

Then open `http://127.0.0.1:8765`. The default entrypoint stays the CLI, so the same image runs either mode — see [Quickstart → Run](#3-run) for the CLI form.

### What's different from the CLI

- **Per-workdir history.** Conversation history lives at `<workdir>/.aiva/sessions.db` (instead of `~/.aiva/sessions.db`), so switching workdirs gives you a clean session namespace and you can keep one history per dataset.
- **Multi-session.** Each chat is its own session with its own VCF/BAM bindings — useful for keeping cases separate.

### Notebook usage

Set env vars in one cell, call `aiva_agent(...)` in the next; calls within the same kernel automatically share a session, so follow-up questions remember the prior turn.

```python
# cell 1
import os
os.environ["LLM_API_KEY"] = "sk-..."
os.environ["LLM_BASE_URL"] = "https://api.openai.com/v1"
os.environ["LLM_MODEL"] = "gpt-5.5"
os.environ["AIVA_VCF"] = "data/sample.vcf.gz"

# cell 2
from aiva_agent import aiva_agent
print(aiva_agent("List 3 likely-pathogenic variants from vcf."))
print(aiva_agent("Of those, which is in a recessive disease gene?"))  # remembers
```

To start a fresh conversation mid-notebook, call `reset_session()`. To pin a specific ID (e.g. resume across kernel restarts), set `AIVA_SESSION_ID` in env or pass `session_id="my-case"` to `aiva_agent`. Per-call kwargs `vcf=`, `disable=`, `model=`, `base_url=`, `api_key=` override the corresponding env vars.

### Classify a single variant (for embedding / integrations)

To run just ACMG/AMP classification on one variant — without the conversational agent — call `classify(...)`. It returns the classification as a `dict`, so other tools can consume it directly.

```python
import asyncio
from aiva_agent import classify

result = asyncio.run(classify(
    {"hgvs": "NM_004333.6:c.1799T>A"},   # or {"rsid": ...} or {"chrom","pos","ref","alt"}
    classification_type="acmg",          # "acmg" (germline) or "amp" (somatic)
    assembly="GRCh38",                   # "GRCh37" or "GRCh38"
    phenotype_terms="melanoma",          # optional: disease / HPO context
    description="42yo, family history of breast cancer",   # optional: sample/patient context
    additional_context="de novo, confirmed by trio sequencing; heterozygous",  # optional
    model="gpt-5.5",                     # optional, falls back to LLM_MODEL
    base_url="https://api.openai.com/v1",  # optional, falls back to LLM_BASE_URL
    api_key="sk-...",                    # optional, falls back to LLM_API_KEY
))
print(result["classification"], result.get("criteria_met"))
```

| Parameter | Required | Description |
|---|---|---|
| `variant` | yes | The variant as `{"hgvs": ...}`, `{"rsid": ...}`, or `{"chrom","pos","ref","alt"}`. |
| `classification_type` | yes | `"acmg"` (germline, ACMG/AMP 2015) or `"amp"` (somatic, AMP/ASCO/CAP 2017). |
| `assembly` | yes | `"GRCh37"` or `"GRCh38"`. |
| `phenotype_terms` | no | Disease / phenotype context, e.g. `"melanoma"`, `"hereditary breast cancer"`. |
| `description` | no | Free-text sample/patient context. |
| `additional_context` | no | Verified clinical context (de novo status, zygosity, segregation, family history). Criteria are applied strictly to what's stated here. |
| `model` / `base_url` / `api_key` | no | Override the `LLM_MODEL` / `LLM_BASE_URL` / `LLM_API_KEY` env vars. |
| `workdir` | no | Scopes the classifier's `bash` tool (defaults to the current working directory). The classifier gets the same tool palette here as inside the agent — including shell access for evidence gathering — so pass a path to sandbox it. |

The result is schema-validated via the Agents SDK's structured output (`output_type`), so it's always a well-formed `dict`: for `acmg` it contains `classification`, `confidence`, `acmg_score`, `criteria_met`, `evidence_summary` (an entry per ACMG criterion), `classification_rationale`, and `sources`; for `amp` it contains `classification`, `confidence`, `criteria_met`, `evidence_summary`, `therapeutic_summary`, `diagnostic_summary`, `prognostic_summary`, and `sources`. (Structured output requires a provider that supports OpenAI `json_schema` response format.)

The same is available from the command line via the `classify` subcommand, which prints the JSON result to stdout:

```bash
aiva_agent classify --type acmg --assembly GRCh38 \
  --hgvs "NM_004333.6:c.1799T>A" --phenotype melanoma
```

Specify the variant with `--hgvs`, `--rsid`, or all of `--chrom/--pos/--ref/--alt`. Optional flags mirror the Python kwargs: `--description`, `--context` (verified clinical context; maps to `additional_context`), `--workdir` (scopes the classifier's `bash` tool), and `--model/--base-url/--api-key` (fall back to the `LLM_*` env vars).

### Running on HPC / older glibc

If your host has glibc < 2.28 (typical on CentOS/RHEL 7-era HPC nodes), a native library used by the `vcf` tool won't load and the tool will refuse to start with a hint to use the container image instead. The image (`mhspl/aiva-agent:latest`) ships with the right binaries pre-baked. Apptainer/Singularity can pull it directly:

```bash
apptainer pull aiva-agent.sif docker://mhspl/aiva-agent:latest
apptainer exec --bind "$PWD:/work" aiva-agent.sif \
  aiva_agent --vcf /work/sample.vcf.gz --prompt "..."
```

Submit the job on a node that satisfies the [Outbound HTTPS](#prerequisites) prerequisite — the container doesn't change network requirements.

## License

aiva-agent is licensed under the **PolyForm Noncommercial License 1.0.0**.

- **Free for noncommercial use** — personal use, academic research, education, and use by charitable, public-research, public-health, and government institutions.
- **Commercial use requires a separate license.** Contact tarun@mamidi.ai for commercial licensing terms.
