Documentation

Knowledge Graph Integration

What a research paper knows is locked in prose, findable only by someone who already knows to read that particular paper. The knowledge graph is where the platform keeps published papers as data. It is a SPARQL triple store in which every paper is a named graph, so the question of which papers touch a concept, and who wrote them, is a query instead of a literature review.

The graph is maintained by BioAgents and reached through the API. It is loaded from curated JSON-LD records of published papers rather than extracted from the files you upload. What an uploaded paper gets from the graph is enrichment, described below.

SourcesGraph (Oxigraph)Access through the APIJSON-LD paper recordsone file per DOILoaderJSON-LD to N-Quadsone named graph per DOIPaper graphsdcterms, schema.org, foafHypothesis graphgenerated, with sourcesSPARQL + Graph Storequery and insert endpointstyped queries onlyTyped searchesconcept, author, DOIQuestion pipeline/bioagents/kg/askPaper enrichmentregistered papers by DOI
The knowledge graph across three tiers. JSON-LD paper records, one file per DOI, are converted to N-Quads by a loader and stored in Oxigraph as one named graph per DOI, next to a graph of generated hypotheses. The API reaches the store through typed searches by concept, author and DOI, a question pipeline that plans, queries and synthesises, and the paper enrichment that fills a registered paper's record from its DOI.

What it is built from

RDF in named graphs

The store is Oxigraph. Each paper is one named graph whose name is the paper's DOI as an IRI, and the paper itself is the subject of the triples inside it. The vocabulary is bibliographic. It uses Dublin Core terms, schema.org, FOAF, and the SPAR ontologies FaBiO, CiTO and DEO for document structure.

turtle
# Inside the named graph <https://doi.org/10.1038/s41586-021-03819-2>
<https://doi.org/10.1038/s41586-021-03819-2>
    dcterms:title        "CRISPR-Cas9 Gene Editing for Neurodegenerative Diseases" ;
    dcterms:abstract     "..." ;
    schema:datePublished "2021-08-05" ;
    dcterms:creator      _:a1 ;
    schema:keywords      "gene editing" , "neurodegeneration" ;
    schema:about         <https://example.org/term/crispr-cas9> .
_:a1 foaf:name "Jane Smith" .
<https://example.org/term/crispr-cas9> dcterms:name "CRISPR-Cas9" .

JSON-LD

Papers arrive as JSON-LD documents. That makes the graph loadable from ordinary files and readable by tools that do not care about RDF.

json
{
  "@context": {
    "dcterms": "http://purl.org/dc/terms/",
    "schema": "https://schema.org/",
    "foaf": "http://xmlns.com/foaf/0.1/"
  },
  "@id": "https://doi.org/10.1038/s41586-021-03819-2",
  "dcterms:title": "CRISPR-Cas9 Gene Editing for Neurodegenerative Diseases",
  "schema:datePublished": "2021-08-05",
  "dcterms:creator": [{ "foaf:name": "Jane Smith" }],
  "schema:keywords": ["gene editing", "neurodegeneration"]
}

SPARQL

Oxigraph answers SPARQL on its query endpoint and accepts inserts through the Graph Store Protocol. Both are internal to the BioAgents deployment, configured by KG_TRIPLE_STORE_URL. The API does not expose a raw SPARQL endpoint. Every query the plugin runs is assembled from typed inputs, and nothing a caller types becomes query text. A concept search has this shape, lightly simplified here.

sparql
SELECT DISTINCT ?doi ?title ?abstract ?date
  (GROUP_CONCAT(DISTINCT ?matched; SEPARATOR="; ") AS ?matchedTerms)
WHERE {
  GRAPH ?doi {
    ?doi dcterms:title ?title .
    OPTIONAL { ?doi dcterms:abstract ?abstract . }
    OPTIONAL { ?doi schema:datePublished ?date . }
    { ?doi schema:about ?term . ?term dcterms:name ?termName .
      FILTER(CONTAINS(LCASE(?termName), "crispr")) BIND(?termName AS ?matched) }
    UNION
    { ?doi schema:keywords ?keyword .
      FILTER(CONTAINS(LCASE(?keyword), "crispr")) BIND(?keyword AS ?matched) }
    UNION
    { ?doi dcterms:title ?t . FILTER(CONTAINS(LCASE(?t), "crispr")) BIND("title" AS ?matched) }
    UNION
    { ?doi dcterms:abstract ?a . FILTER(CONTAINS(LCASE(?a), "crispr")) BIND("abstract" AS ?matched) }
  }
}
GROUP BY ?doi ?title ?abstract ?date
ORDER BY RAND()
LIMIT 8

A concept matches a paper's subject terms, keywords, title or abstract. An author matches on the surname, which is the stable part of how papers cite people.

SOURCELOADGRAPHSEARCHESAPIJSON-LD recordsone per DOILoaderJSON-LD to N-QuadsOxigraphnamed graph per DOIConcept searchAuthor searchDOI, hypothesesEkayana API/bioagents/kg/*
How the graph is reached. JSON-LD records pass through the loader into Oxigraph. The plugin offers concept search, author search, DOI lookup and hypotheses over it. The API proxies those as routes and uses the DOI lookup to enrich registered papers.

How knowledge gets in

A loader script reads JSON-LD files, one per paper and named after the URL encoded DOI, converts each to N-Quads and stores it as that DOI's named graph, skipping papers already present. There is no other ingestion path for the graph.

Uploaded papers do not become triples. When you register a paper with a DOI, the API looks the DOI up in the graph and fills in the fields you left empty. Those are the abstract, venue, date, authors, keywords, and the paper's subject terms as its entities. The paper record then links the graph's knowledge to a DID and a CID.

Hypotheses are the one thing the platform writes into the store. Each run of the question pipeline can propose connections and stores them, with their rationale, method, keywords and source DOIs, in a named graph reserved for them.

Ekayana APIplugin-kgText modelOxigraphPOST /kg/ask (question, context)Plan the search stepsConcepts, authors, DOIsSPARQL per step, typed inputsPapersSynthesise with citationsSynthesis, hypothesesStore hypothesessynthesis, papers, hypotheses, plan
The question pipeline. The API posts a question and optional context to the plugin. The text model plans the search steps. The plugin runs each step as a SPARQL query built from typed inputs against Oxigraph and collects papers. The model writes a synthesis that cites them. New hypotheses are stored in the hypothesis graph. The plugin returns synthesis, papers, hypotheses and the plan.

Querying

Every route takes a session token. Terms are comma separated, at most ten, each three or more characters. limit runs from 1 to 50. order is random, the default, or recent.

OperationEndpoint
Papers matching conceptsGET /api/bioagents/kg/papers?concepts=crispr,neurodegeneration&limit=8&order=recent
Papers by author, optionally narrowed by conceptGET /api/bioagents/kg/authors?authors=Smith&concepts=crispr
One paper with its sectionsGET /api/bioagents/kg/paper?doi=10.1038/s41586-021-03819-2
Stored hypothesesGET /api/bioagents/kg/hypotheses?concepts=crispr
Ask a questionPOST /api/bioagents/kg/ask
Store reachable, paper countGET /api/bioagents/kg/health (public)
bash
curl "https://api.ekayana.io/api/bioagents/kg/papers?concepts=crispr&limit=2" \
  -H "Authorization: Bearer $TOKEN"
json
{
  "papers": [{
    "doi": "https://doi.org/10.1038/s41586-021-03819-2",
    "title": "CRISPR-Cas9 Gene Editing for Neurodegenerative Diseases",
    "abstract_text": "...",
    "date": "2021-08-05",
    "authors": "Jane Smith; John Doe",
    "keywords": "gene editing; neurodegeneration",
    "terms": "CRISPR-Cas9; Neurodegeneration",
    "matched_terms": "CRISPR-Cas9; title"
  }]
}

Concatenated fields use ; as the separator, which is how the store groups them.

In plain English

POST /api/bioagents/kg/ask takes a question of 3 to 2000 characters and up to six lines of context. The agent's text model decides which searches to run. The plugin runs them as the typed queries above. The model writes a synthesis that cites the papers it used. The response carries all of it.

json
{
  "question": "What links CRISPR delivery vectors to neuroinflammation?",
  "synthesis": "...",
  "papers": [{ "doi": "...", "title": "...", "matched_terms": "..." }],
  "hypotheses": [{
    "id": "...", "statement": "...", "rationale": "...", "method": "...",
    "keywords": ["..."], "sources": ["https://doi.org/..."], "creator": "...", "created": "..."
  }],
  "plan": { "...": "the steps the model chose" }
}

A run may take up to 100 seconds before the API gives up on it.

What this is for

regulatesproducesinteracts withinhibitsactivatesinfluencesassociated withhas side effectincreases risk ofGene AgeneGene BgeneGene CgeneProtein XproteinDrug MdrugPathway YpathwaySymptom QsymptomEnv. FactorexposureDisease Zdisease
A subgraph of typed entities the store can surface. Genes produce and regulate proteins, proteins activate pathways, pathways influence a disease, a drug inhibits a protein and has a side effect, and an environmental factor raises the risk of the disease. Two entities heavily connected to a third but never linked to each other are the gaps a hypothesis can name.

The first use is discovery. Find related work you did not know existed, by concept or by author, with the graph's own citations. The second is hypothesis generation. The pipeline sees which papers a question pulls together and can propose a connection to test, with the papers that suggested it. Whether a proposed connection is real is still the researcher's job. The graph makes the candidates enumerable and keeps each one attached to its sources.

The graph is also a junction. A paper you register links a DID and a file to a DOI. The DOI links to the graph. An answer's citations lead back through the DOI to your record and the file behind it. That chain lets one question span published literature and your own registered work.