Skip to content

Repository files navigation

OntoAgent

A multi-agent ontology search system for the NFDI4Cat ontology portfolio. Resolves terms, identifiers, synonyms, and fuzzy matches across 19 biomedical and chemical ontologies via the EBI OLS4 API, with a dark-theme web interface.

Features

  • Hybrid matching — identifier lookup (CHEBI:35255), exact match, synonym match, fuzzy match
  • Classes and properties — searches OWL classes and object/data/annotation properties (e.g. IAO:0000136 "is about"); a scope selector narrows results to Classes, Properties, or Both (default). Named individuals are excluded.
  • Parallel search — all selected ontologies queried simultaneously via asyncio
  • LangGraph orchestration — stateful pipeline: query parsing → parallel specialist search → aggregation
  • Batch search — upload a TXT/CSV file with multiple terms, download results as CSV
  • Custom ontologies — add any OLS4 ontology to the sidebar; persisted across server restarts
  • Web GUI — dark-theme single-page app with ontology filter sidebar and sortable results
  • REST API — JSON endpoints for integration into other tools

Quick Start

Requirements: Python ≥ 3.11 — tested with Anaconda 3.12 on Windows

PowerShell

# 1. Install dependencies
C:\Users\<you>\anaconda3\python.exe -m pip install -r requirements.txt

# 2. Start the server
$env:PYTHONUTF8="1"; C:\Users\<you>\anaconda3\python.exe server.py

# 3. Open in browser
# http://localhost:8000

Git Bash

# 1. Install dependencies
/c/Users/<you>/anaconda3/python.exe -m pip install -r requirements.txt

# 2. Start the server
PYTHONUTF8=1 /c/Users/<you>/anaconda3/python.exe server.py

# 3. Open in browser
# http://localhost:8000

Port already in use? If port 8000 is occupied by a previous instance, free it first:

# PowerShell
Get-NetTCPConnection -LocalPort 8000 -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
# Git Bash
netstat -ano | grep ':8000 ' | awk '{print $NF}' | sort -u | xargs -I{} taskkill //PID {} //F

Configuration

All settings live in onto_agent/config.py and are overridable via environment variables:

Variable Default Purpose
ONTOAGENT_HOST 127.0.0.1 Bind address. The server is loopback-only by default; set 0.0.0.0 to expose it on the network (note: there is no authentication).
ONTOAGENT_PORT 8000 Listen port
ONTOAGENT_OLS4_BASE EBI OLS4 Upstream API base URL
ONTOAGENT_MAX_UPLOAD_BYTES 1000000 Max batch-upload size (DoS guard)
ONTOAGENT_MAX_TERMS 500 Max terms per batch (DoS guard)
ONTOAGENT_OLS4_CONCURRENCY 8 Process-wide cap on OLS4 search requests
ONTOAGENT_ANCESTOR_CONCURRENCY 4 Separate cap on superclass/ancestor fetches (so they can't starve searches)
ONTOAGENT_BATCH_CONCURRENCY 5 Concurrent terms per batch run
ONTOAGENT_MAX_CUSTOM 50 Max number of user-added custom ontologies
ONTOAGENT_SEARCH_TIMEOUT 60 Wall-clock deadline (seconds) for a single search
ONTOAGENT_LOG_LEVEL INFO Log level

Integer settings are clamped to >= 1, so a stray 0 or negative value falls back to a safe minimum instead of hanging or crashing the server.

⚠️ Security: there is no authentication. Every response carries hardening headers (X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer) and request inputs are bounded (query ≤ 200 chars, batch upload/term caps), but these do not replace auth. Do not bind to 0.0.0.0 on an untrusted network without putting an authenticating reverse proxy in front.

Tests

python -m pip install -r requirements-dev.txt
PYTHONUTF8=1 python -m pytest

All tests are network-free (pure-logic unit tests, TestClient endpoint guards, and respx-mocked OLS4 paths), so they run offline and in CI. GitHub Actions (.github/workflows/ci.yml) runs the suite on Python 3.12/3.13 plus pip-audit.

Dependencies are locked with pip-tools. Edit the ranges in requirements.in / requirements-dev.in, then regenerate the pinned, hashed locks:

pip-compile --generate-hashes requirements.in
pip-compile --generate-hashes --allow-unsafe requirements-dev.in

requirements.txt / requirements-dev.txt are generated — don't hand-edit them. The pip-audit CI job fails on known vulnerabilities, so keep the locks current (langgraph is held at >=1.0.10 because the 0.x line carries known CVEs). The lock is generated on Python 3.13; regenerate it on the lowest target before adding older interpreters to CI.

Usage

Web Interface

Open http://localhost:8000 in any browser.

Search tab

  • Sidebar — toggle individual ontologies on/off; green dot = OLS4 available, * = not available
  • Search bar — enter any term, OBO identifier, or synonym and press Enter or click Search
  • Sort — switch between Relevance, Match type, Alphabetical, or grouped By ontology

Example queries:

Input What happens
catalyst Exact and fuzzy matches across all active ontologies
CHEBI:35255 Identifier lookup — finds the exact OBO class
IAO:0000136 Identifier lookup for a property (is about)
hydroxyl Synonym and fuzzy search

Use the scope selector next to the search bar to restrict results to Classes only, Properties only, or both (default).

Batch tab

Upload a TXT or CSV file with multiple terms to search in one go:

  • TXT — one term per line
  • CSV — terms in the first column (optional header row is auto-detected)

Click Search & Download CSV — the server searches all selected ontologies for every term in parallel and returns a CSV file with one row per ontology per term (best match only).

CSV columns: input_term, ontology_id, ontology_name, match_type, entity_type, matched_label, obo_id, uri, definition

Adding custom ontologies

At the bottom of the sidebar, click ▶ Add ontology to expand the form:

  1. Enter the OLS4 ID (e.g. efo) — the system validates it against the live OLS4 API
  2. Optionally set a Display name and Prefix
  3. Click Add ontology — it appears immediately in the sidebar and is persisted to custom_ontologies.json

To remove a custom ontology, click the × button on its sidebar chip. Base NFDI4Cat ontologies cannot be removed.

Result Cards

Each result shows:

  • Match type badge — ID (blue) / Exact (green) / Synonym (amber) / Fuzzy (gray)
  • Entity type badge — Class (gray) / Property (purple)
  • Term label and ontology name
  • OBO ID and clickable URI
  • Definition (when available)
  • Superclasses (up to 5 ancestors from OLS4 hierarchy)
  • TTL excerpt — collapsible Turtle/RDF snippet

REST API

List ontologies

GET /api/ontologies

Returns all ontologies (NFDI4Cat base set + any custom additions) with OLS4 support status.

Search

POST /api/search
Content-Type: application/json

{
  "query": "catalyst",
  "ontologies": ["chebi", "chmo", "afo"],  // optional — omit to search all
  "scope": "both"                          // optional — "class" | "property" | "both" (default)
}

query must be 1–200 characters, ontologies at most 128 ids, and scope one of class / property / both; oversized or invalid payloads return 422.

Response:

{
  "query": "catalyst",
  "matches": [
    {
      "id": "BAO:0002000",
      "label": "catalyst",
      "synonyms": [],
      "ontology": "bao",
      "ontology_name": "BioAssay Ontology",
      "source": "NFDI4Cat",
      "uri": "http://www.bioassayontology.org/bao#BAO_0002000",
      "definition": "A substance that increases the rate of a reaction...",
      "superclasses": ["role", "chemical entity"],
      "match_type": "exact_match",
      "entity_type": "class",
      "ttl_excerpt": "@prefix rdfs: ..."
    }
  ],
  "errors": {}
}

Match type priority: identifier_match > exact_match > synonym_match > fuzzy_match

Batch search

POST /api/batch-search
Content-Type: multipart/form-data

file=<TXT or CSV file>
ontologies=["chebi","chmo"]   // optional JSON array — omit to use all
scope=both                    // optional — "class" | "property" | "both" (default)

Returns a CSV file attachment (onto_results.csv).

Add custom ontology

POST /api/ontologies
Content-Type: application/json

{
  "ols4_id": "efo",
  "name": "Experimental Factor Ontology",
  "prefix": "EFO"
}

Validates against OLS4, returns 201 with the new entry. Persisted to custom_ontologies.json. Limited to ONTOAGENT_MAX_CUSTOM entries (default 50); exceeding the cap returns 409, as does a duplicate id.

Remove custom ontology

DELETE /api/ontologies/{ols4_id}

Returns 204. Base NFDI4Cat ontologies are protected (returns 403).

Architecture

server.py                    FastAPI — serves GUI + REST endpoints
│                            POST /api/batch-search (multipart upload → CSV)
│                            POST /api/ontologies   (add custom)
│                            DELETE /api/ontologies/{id}
└── onto_agent/
    ├── orchestrator.py      LangGraph StateGraph (3 nodes) + shared httpx client
    │     parse_query  →  search_all (asyncio.gather)  →  aggregate
    ├── ols_client.py        Async OLS4 API client (httpx)
    ├── batch.py             Term parsing + CSV assembly for batch search
    ├── registry.py          27 NFDI4Cat ontologies + custom persistence
    ├── config.py            Env-driven settings (ONTOAGENT_*)
    └── models.py            Pydantic models + LangGraph TypedDict

gui/
└── index.html              Single-file dark-theme SPA (vanilla JS)
                            Search tab + Batch tab + Add ontology form

custom_ontologies.json       Runtime state (git-ignored); stores custom ontology entries

OLS4 Client

  • Base URL: https://www.ebi.ac.uk/ols4/api
  • One pooled httpx.AsyncClient is shared process-wide (created at app startup, closed at shutdown) instead of one per request
  • Concurrency: searches via asyncio.Semaphore(8), ancestor fetches via a separate asyncio.Semaphore(4) so enrichment can't starve searches
  • Timeout: 15 s per upstream request; 60 s overall deadline per /api/search
  • Entity scope: class/property map to the OLS4 type filter; both omits it (and drops individuals). The combined type=class,property form is avoided — OLS4 parses it loosely and leaks individuals.
  • Ancestor fetch: double-URL-encoded IRI — classes via /terms/{iri}/hierarchicalAncestors, properties via /properties/{iri}/ancestors (the property /hierarchicalAncestors path 404s)

Ontology Coverage

Available via OLS4 (19)

ID Name
afo Allotrope Foundation Ontology
bao BioAssay Ontology
bfo Basic Formal Ontology
chebi ChEBI
cheminf Chemical Information Ontology
chmo Chemical Methods Ontology
envo Environmental Ontology
iao Information Artifact Ontology
ms Mass Spectrometry Ontology
obi Ontology for Biomedical Investigations
pato Phenotype and Trait Ontology
po Plant Ontology
rex Physico-chemical process ontology
ro Relations Ontology
rxno Named Reaction Ontology
sbo Systems Biology Ontology
sio Semanticscience Integrated Ontology
uo Units of Measurement Ontology
xlmod HUPO-PSI XLMOD

Not available via OLS4 (8) *

ID Name
cao Chemical Analysis Ontology
cif Crystallographic Information Framework
enmo Engineering Methods Ontology
m3 M3
metadata4ing metadata4ing
osmo OSMO
ontocape OntoCAPE
vimmp VIMMP

* Shown in the sidebar but not queried.

Known Issues

Issue Workaround
Windows cp1252 encoding errors Set $env:PYTHONUTF8="1" before starting the server (handled automatically in server.py)
OLS4 rate limiting (HTTP 429) Search semaphore caps concurrent requests to 8 (+4 for ancestor fetches); retries not yet implemented
Superclass fetch may be slow Only fetched for the top 3 results per ontology, under a separate concurrency budget; a slow search is bounded by the 60 s overall deadline

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages