Skip to content

🧑‍💻 Agent-supported hands-on Building Graphs with BioCypher (offline mode) and Neo4j

Level: Beginner (LLM-assisted UI track)
Who is this for? New BioCypher users who prefer to work with an AI coding agent in their IDE instead of writing all code by hand.
What you will do: Connect the BioCypher MCP to your IDE, explore and model a synthetic protein interaction dataset, instruct an agent to create, test and document an adapter and a graph script, build the graph in offline mode, and import and query it in Neo4j.
Estimated time: 60–90 minutes.

Overview

This tutorial will help you get started with BioCypher in offline mode. You will learn how to use the BioCypher MCP and instruct an agent to create a simple knowledge graph with a synthetic dataset that contains information about proteins and its interactions.

By the end of this tutorial, you will be able to:

  • Set up the BioCypher MCP.
  • Explore a synthetic dataset and learn how to instruct an agent in BioCypher to process it and build a graph structure.
  • Build a small knowledge graph from the data.
  • View and query the graph using Neo4j.

Pre-requisites

Note: Ensure you have the following prerequisites before continue with the tutorial.

Tool Version/Requirement Installation Link Notes
VSCode or other IDE Any VSCode IDE For interacting with the AI agent
Git Any Git Docs For version control
Neo4j >=1.6 Neo4j Desktop For querying graphs
uv >=0.7.x uv Docs For dependency management
Python >= 3.11 Python.org Required for BioCypher
Jupyter (optional) Any Jupter Required for exploring the sample data

Setup

Setup AI agent and the BioCypher MCP

As a first step, you need to install VSCode or another IDE of your choice that allows the use of an MCP. You then need to connect your IDE to a model provider like GitHub Copilot, OpenAI, Claude, or by using a local model. For example, if you are using VSCode, you can install the extension for GitHub Copilot, and add the BioCypher MCP to the IDE.

To make sure that the agent and MCP are configured correctly, you can ask in the chat

You

you> Show biocypher-mcp tools

and the agent should respond with a list of available tools, such as

Agent

copilot> Here are the available BioCypher MCP tools:

  1. Project Creation

    • check_project_exists
    • get_cookiecutter_instructions
  2. Adapter Creation (5-phase workflow) ...

Setting up the working environment and starter repository using an AI agent

In this section, you will set up your working environment and folder structure for the BioCypher adapter.

Steps:

  1. Instruct the AI agent to use the get_cookiecutter_instructions tool:

    You

    you> /get_cookiecutter_instructions

    This will result in a response from the agent containing instructions on how to set up the project using cookiecutter:

    Agent

    copilot> [tool-start] biocypher-mcp-get_cookiecutter_instructions (mcp_server=biocypher-mcp, mcp_tool=get_cookiecutter_instructions)

    [tool.execution_complete] (unknown) Here’s how to create a BioCypher project using cookiecutter:

    1. Install cookiecutter (if not already installed):

    ...

    Would you like to proceed? If so, please provide your desired project name and a short description.

    You will then need to provide a project name and project description as instructed.

    You

    you> Project name: tutorial-basics; project description: Hands-on basic graph creation with BioCypher using a small synthetic dataset.

    Agent

    copilot> ...

    [tool.execution_complete] (unknown) The BioCypher project "tutorial-basics" was created successfully using the cookiecutter template. Next steps:

    1. cd tutorial-basics
    2. pip install -e . (or uv sync)
    3. Configure your data source in create_knowledge_graph.py
    4. Update config/schema_config.yaml if needed
    5. Run: python create_knowledge_graph.py

    Let me know if you want to proceed with setup, dependency installation, or further customization!

  2. Check the current project structure. Below, we list the most important folders and files for this tutorial.

    /tutorial-basics
        ├── config
        │   ├── biocypher_config.yaml
        │   └── schema_config.yaml
        ├── create_knowledge_graph.py
        ├── pyproject.toml
        ├── src
        │   └── tutorial_basics
        │       └── adapters
        │           ├── __init__.py
        │           └── tutorial_basics_adapter.py
        └── tests
           └── test_tutorial_basics_adapter.py
        ...
    
  3. Install the dependencies using your preferred package manager (e.g. uv or pip):

    You should always first create a dedicated Python environment for your project, and then install the dependencies into the environment. Environments can be managed by conda, uv ,poetry or venv, for example.

    conda create --name bc-basic python=3.13
    conda activate bc-basic
    

    After you have created your environment, activate the environment, for example, in the terminal

    conda activate biocypher-tutorial
    
    Now you are ready to install the required packages using your preferred package manager as described in the agent chat response. Go to the terminal and type

    cd tutorial-basics
    
    to change into the tutorial-basics directory. Then install the project dependencies either using pip:
    pip install -e .
    
    or using uv:
    uv sync
    

    You also need to install Jupyter into your environment, i.e. pip install jupyter, if later you want to explore the sample data in a Jupyter notebook.

Setup Neo4j

In this section, we will create a Neo4j instance to use later in the tutorial. It is important to set this up now. For more information about Neo4j, please take a look at our Explanations.

  1. Execute Neo4j Desktop, if this the first time you should see a window like this one.

    Neo4j Desktop start screen
    Figure 1. Neo4j Desktop start screen.

  2. Create a new instance in Neo4j. For this tutorial, name it neo4j-tutorial-instance and choose a password you can remember.

    Create Instance window in Neo4j Desktop
    Figure 2. Create Instance window. This may vary depending on your Neo4j version.

  3. Access details in the option Overview.

    Overview option for a Neo4j instance
    Figure 3. Overview option to check details related to your Neo4j instance.

  4. Save the path to your Neo4j instance, we are going to use this path later in this tutorial.

    Neo4j instance with its path location highlighted
    Figure 4. Neo4j instance with its path location highlighted.

Section 1. Exploratory Data Analysis

For this tutorial we are going to use a synthetic dataset that contains information about the interaction between proteins. The dataset is contained in a tsv file, similar to a csv file but using tabs instead of commas as delimiters.

  • First, download the dataset:

    curl -o ./data/synthetic_protein_interactions.tsv \
    https://zenodo.org/records/16902349/files/synthetic_protein_interactions.tsv
    
  • Create a folder called notebooks under tutorial-basics

    mkdir -p ./notebooks/
    

  • Create and run either a Python file or a Jupyter notebook containing the following code.

    File: notebooks/eda_synthetic_data.py
    import pandas as pd
    
    # Load the dataset
    df = pd.read_table('../data/synthetic_protein_interactions.tsv', sep='\t')
    
    # Show the first few rows
    print("\n---- First 10 rows in the dataset")
    print(df.head(10))
    
    # List the columns in the dataset
    print("\n---- Columns in the dataset")
    for column in df.columns:
        print(f"\t{column}")
    
    # Get basic info about the datasets
    print("\n---- Summary Dataframe")
    print(df.info())
    
    # Check for missing values
    print("\n---- Check missing values")
    print(df.isnull().sum())
    
    # Show summary statistics for numeric columns
    print("\n---- Describe Dataframe statistics")
    print(df.describe())
    
    # Count the unique number of proteins in each column
    print("\n---- Number of unique proteins per column.")
    print('Unique proteins in column protein_a:', df['source'].nunique())
    print('Unique proteins in column protein_b:', df['target'].nunique())
    

📝 Exercise:

a. How many unique proteins do we have in the dataset? Hint: count unique proteins in the source and target columns.

b. How many interactions exist in our dataset? Hint: count unique interactions between sources and targets.

c. Some columns contain boolean values represented as "1" and "0". Can you detect which ones?

Answer:

a. Number of unique proteins: 15.

b. Number of unique interactions: there are 22 unique interactions. One interaction is repeated, with a difference in one of its properties.

c. is_directed, is_stimulation, is_inhibition, consensus_direction, consensus_stimulation,consensus_inhibition.

Section 2. Graph Modeling

Graph Modeling

By looking at the tsv file, we can see that there are two columns called source and target, which represent proteins. This means that each row represents an interaction between a source protein and a target protein. For now, our graph could look like this.

Simple graph model for representing interactions between proteins
Figure 5. Simple graph model for representing interactions between proteins.

Can we improve the graph? Absolutely! Understanding the data is essential for building an effective graph. By examining the other columns in the table, we can identify additional details:

  • The source and target columns represent nodes in the graph, with each node corresponding to a protein.

  • Each protein listed in the source column has associated properties found in other columns:

    • source_genesymbol: the gene symbol of the source protein.
    • ncbi_tax_id_source: the NCBI taxonomy identifier of the source protein.
    • entity_type_source: the type of entity for the source protein.
  • Each protein in the target column has associated properties found in other columns:

    • target_genesymbol
    • ncbi_tax_id_target
    • entity_type_target
Simple protein interaction graph with properties in nodes
Figure 6. Simple protein interaction graph with properties in nodes.

We know that a source protein interacts with a target protein, but do we know how?

Remaining columns in the table describe properties of these protein-protein interactions:

Interaction properties

  • is_directed
  • is_stimulation
  • is_inhibition
  • consensus direction
  • consensus stimulation
  • consensus inhibition
  • type

It is these protein-protein interactions that form the edges in the graph. Here, is_directed, is_stimulation, and is_inhibition describe properties that characterize each interaction type, while consensus direction, consensus stimulation, and consensus inhibition indicate the aggregated or consensus value derived from multiple sources in OmniPath for each property.

We are ready to model our second version of our graph. It is like follows:

Protein interaction graph showing node and edge properties
Figure 7. Protein interaction graph showing node and edge properties.

Finally, we can model a more detailed graph using our dataset. Rather than representing all interactions in a generic way, we can use the type field to show the specific type of interaction occurring between each pair of proteins.

Graph model for representing different interactions between proteins
Figure 8. Graph model for representing different interactions between proteins.

Exercise 1. Example of a graph we expect with our data

📝 Exercise: Sketch a portion of the knowledge graph using the provided dataset.

Answer:

If you include all the nodes and edges from your TSV file, your sketch should look like the following example:

graph LR
    SOD1((SOD1))   -- binding --> EGFR((EGFR))
    CDK1((CDK1))   -- activation --> TP53((TP53))
    MYC((MYC))     -- phosporylation --> GAPDH((GAPDH))
    MTOR((MTOR))   -- ubiquitination --> NFKB1((NFKB1))
    NFKB1((NFKB1)) -- activation --> CDK1((CDK1))
    MAPK1((MAPK1)) -- ubiquitination --> HSP90((HSP90))
    TP53((TP53))     -- ubiquitination --> CREB1((CREB1))
    HSP90((HSP90)) -- activation --> APP((APP))
    HSP90((HSP90)) -- ubiquitination --> RHOA((RHOA))
    SOD1((SOD1))   -- inhibition --> TP53((TP53))
    AKT1((AKT1))   -- ubiquitination --> HSP90((HSP90))
    HSP90((HSP90)) -- ubiquitination --> MYC((MYC))
    MAPK1((MAPK1)) -- ubiquitination --> BRCA1((BRCA1))
    NFKB1((NFKB1)) -- inhibition --> RHOA((RHOA))
    NFKB1((NFKB1)) -- phosphorylation --> APP((APP))
    HSP90((HSP90)) -- binding --> GAPDH((GAPDH))
    GAPDH((GAPDH)) -- activation --> TP53((TP53))
    AKT1((AKT1))   -- phosphorylation --> TP53((TP53))
    GAPDH((GAPDH)) -- activation --> APP((APP))
    TP53((TP53))     -- activation --> MAPK1((MAPK1))
    TP53((TP53))     -- ubiquitination --> CREB1((CREB1))
    MYC((MYC))     -- phosphorylation --> GAPDH((GAPDH))
    EGFR((EGFR))   -- binding --> SOD1((SOD1))

Section 3. Graph creation with BioCypher

We aim to create a knowledge graph using the data we found in the tsv file. Let's recap our exercise:

  • Create a graph with the following characteristics:

    • One node type: Protein.
    • Five edge types: activation, binding, inhibition, phosphorylation, ubiquitination.
  • Each node has properties:

    • genesymbol
    • ncbi_tax_id
    • entity_type
  • Each edge has properties:

    • is_directed
    • is_stimulation
    • is_inhibition
    • consensus_direction
    • consensus_stimulation
    • consensus_inhibition
  • We must export the knowledge graph to Neo4j.

To achieve this, we can divide the process into five sections using the BioCypher MCP:

  1. Use get_adapter_creation_workflow for an overview.
  2. Use get_phase_guidance for step-by-step help in each phase.
  3. Use get_implementation_patterns for code examples.
  4. Use get_schema_configuration_guidance for schema setup.
  5. Use get_resource_management_guidance for data handling.

Step 1. get_adapter_creation_workflow for an overview and understanding of the process

Configuration step in the BioCypher pipeline
Figure 9. Configuration step in the BioCypher pipeline.

You

you> /get_adapter_creation_workflow

Agent

copilot> The BioCypher adapter creation workflow has 5 phases: 1. Data Analysis and Understanding: Analyze your input data’s structure and schema needs. 2. Implementation Strategy Design: Choose the adapter architecture and extraction strategy. 3. Implementation: Write the adapter code and map fields. 4. Quality Assurance: Test and validate your adapter. 5. Documentation and Maintenance: Document usage and plan for future updates.

Create a schema for your graph

Rationale: the schema file allows us to define the skeleton for our knowledge graph. Nodes, edges, properties are defined here.

First we need to understand the specific requirements of the data that we would like to represent as graph. In the previous section, we have already learned what our data looks like at the moment and how it will be represented after processing with BioCypher. Now we can interact with the AI agent to understand the aspects about the data that are important for BioCypher.

You

you> Start with phase 1: Data Analysis and Understanding: Analyze your input data’s structure and schema needs.

Agent

copilot> Phase 1: Data Analysis and Understanding involves:

  1. Resource Structure Analysis: Identify your data source type (file, API, database, etc.) and analyze its structure.
  2. Metadata Pattern Recognition: Extract metadata fields, identify shared/unique patterns, and map relationships.
  3. Schema Assessment: Check if an existing schema covers all concepts or create/extend one as needed.

Expected outputs: data source type, structure analysis report, and schema requirements assessment.

Please provide a sample or description of your input data so I can guide you through the analysis.

We now need to tell the agent where to find the data:

You

you> The data lives in the data folder in file synthetic_protein_interactions.tsv.

The agent will give you information on the file, please check that this is what you expected. You can ask for a summary for a more in-depth comparison. You should then ask the agent for a detailed schema proposal:

You

you> detailed schema proposal

Agent

copilot> Here’s a detailed schema proposal for your protein interaction data:

Node: Protein
- id: UniProt accession (source/target)
- gene_symbol: source_genesymbol / target_genesymbol
- ncbi_tax_id: ncbi_tax_id_source / ncbi_tax_id_target
- entity_type: entity_type_source / entity_type_target

Edge: ProteinInteraction
- source: Protein.id
- target: Protein.id
- type: interaction type (e.g., binding, activation)
- is_directed: boolean
- is_stimulation: boolean
- is_inhibition: boolean
- consensus_direction, consensus_stimulation, consensus_inhibition: booleans

Let me know if you want this as a YAML/JSON schema or further customized!

You can ask the agent to create a yaml schema for you. In the end it should look like this.

protein:
    represented_as: node
    preferred_id: uniprot
    input_label: uniprot_protein

protein protein interaction:
    is_a: pairwise molecular interaction
    represented_as: edge
    input_label: protein_protein_interaction
    properties:
        is_directed: bool
        is_stimulation: bool
        is_inhibition: bool
        consensus_direction: bool
        consensus_stimulation: bool
        consensus_inhibition: bool

activation:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: activation

The protein top-level key in the YAML snippet identifies our entity and connects it to the ontological backbone.

Key Value Description
represented_as node Specifies how BioCypher should represent each entity in the graph; in this case, as a node.
preferred_id uniprot Defines a namespace for our proteins. In this example, all proteins follow the UniProt convention—a 5-character alphanumeric string (e.g., P00533).
input_label uniprot_protein Indicates the expected label in the node tuple. All other input nodes without this label are ignored unless they are defined in the schema configuration.

For more information about which other keywords you can use to configure your nodes in the schema file consult Fields reference.

Edges (relationships)

As shown in Figure 7, each edge has the same set of properties (is_directed, consensus_direction, etc.). At this stage, we have two options for defining the edges:

  • Option 1: Create each edge and explicitly define the same set of property fields for every edge.
  • Option 2 (recommended): Create a base edge with the properties, and then create edges that inherit the behavior of this base edge. This approach reduces lines of code and avoids repetition. For example, if you have more than 20 edges, Option 1 would likely not be practical.

Base Edge The protein protein interaction top-level key in the YAML snippet identifies our edge entity.

Key Value Description
is_a pairwise molecular interaction Defines the type of entity based on the ontology.
represented_as edge Explicitly specifies that this entity is an edge.
input_label protein_protein_interaction Defines a namespace for our relationships.
properties property: datatype (i.e. is_directed: bool) Contains all properties associated with this edge; each property has a name and an associated datatype.

Inherited Edges The activation: top-level key in the YAML snippet identifies our edge entity.

Key Value Description
is_a protein protein interaction Defines the type of entity; in this case, it is a child of the base edge we defined previously.
inherit_properties true Indicates whether all properties defined in the base edge should be inherited.
represented_as edge Specifies that BioCypher will treat this entity (activation) as an edge.
input_label activation Specifies the expected edge label; edges without this label are ignored unless defined in the schema.

A comment about the connection between BioCypher and Ontologies

In BioCypher, ontologies are integrated through the schema configuration file. This YAML file defines the structure of the graph by specifying which entities and relationships should be included. At the same time, it links those entities to the biomedical domain by aligning them with an ontological hierarchy. In this tutorial, we use the Biolink Model as the backbone of that hierarchy. The guiding principle is simple: only entities that are defined in the schema configuration and present in the input data are incorporated into the final knowledge graph.

Figure 10 illustrates the Biolink Model and some of its components organized in a hierarchy. Notice that entities such as protein (nodes) and pairwise molecular interaction (edges) appear both in the schema configuration and in the ontology. This alignment ensures that BioCypher graphs are not only structured consistently but also grounded in standardized biomedical concepts. For a deeper exploration of ontologies in BioCypher, see our ontology tutorial.

The Biolink Model as an ontology backbone, showing protein as an entity and pairwise molecular interaction as an association
Figure 10. The Biolink Model as an ontology backbone. On the right, protein is represented as an entity; on the left, pairwise molecular interaction is defined as an association. Together, these demonstrate how the schema anchors graph components to standardized biomedical concepts.

📝 Exercise: Revise and complete the schema_config.yaml file, and make sure it is located in the config folder.

Answer:

See the example below for a completed schema_config.yaml.

File: config/schema_config.yaml

#-------------------------------------------------------------------
#-------------------------      NODES      -------------------------
#-------------------------------------------------------------------
#====   PARENT NODES
protein:
    represented_as: node
    preferred_id: uniprot
    input_label: uniprot_protein

#-------------------------------------------------------------------
#------------------      RELATIONSHIPS (EDGES)     -----------------
#-------------------------------------------------------------------
#====   PARENT EDGES
protein protein interaction:
    is_a: pairwise molecular interaction
    represented_as: edge
    input_label: protein_protein_interaction
    properties:
        is_directed: bool
        is_stimulation: bool
        is_inhibition: bool
        consensus_direction: bool
        consensus_stimulation: bool
        consensus_inhibition: bool

#====   INHERITED EDGES
activation:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: activation

binding:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: binding

inhibition:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: inhibition

phosphorylation:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: phosphorylation

ubiquitination:
    is_a: protein protein interaction
    inherit_properties: true
    represented_as: edge
    input_label: ubiquitination

Configure BioCypher behavior

Rationale: The purpose of writing a biocypher_config.yaml is to define how BioCypher should operate for your project—specifying settings for data import, graph creation, and database interaction—all in one place for clarity and easy customization.

File: config/biocypher_config.yaml
#---------------------------------------------------------------
#--------        BIOCYPHER GENERAL CONFIGURATION        --------
#---------------------------------------------------------------
biocypher:
    offline: true
    debug: false
    schema_config_path: config/schema_config.yaml
    cache_directory: .cache

#----------------------------------------------------
#--------        OUTPUT CONFIGURATION        --------
#----------------------------------------------------
neo4j:
    database_name: neo4j
    csv_column_delimiter: '\t'
    csv_array_delimiter: '|'
    file_format: csv
    skip_duplicate_nodes: true
    skip_bad_relationships: true
    import_call_bin_prefix: <path to your Neo4j instance from Setup Neo4j section>/bin/

The first block is the BioCypher Core Settings, which starts with biocypher:

key value description
offline true Whether to run in offline mode (no running DBMS or in-memory object)
debug false Whether to enable debug logging
schema_config_path config/schema_config.yaml Path to the schema configuration file
cache_directory .cache Directory where downloaded resources are cached

The second block is the Database Management System Settings, which starts with the name of the DBMS, in this case it's neo4j:

key value description
csv_column_delimiter '\t' Field delimiter for TSV import files
csv_array_delimiter '|' Delimiter for array values
file_format csv Output file format; BioCypher 0.17+ defaults to parquet, so set this explicitly for CSV output
skip_duplicate_nodes true Whether to skip duplicate nodes during import
skip_bad_relationships true Whether to skip relationships with missing endpoints
import_call_bin_prefix i.e., /usr/bin/ Prefix for the import command binary (optional)

The import_call_bin_prefix is the path to your Neo4j instance that you looked up in section Setup Neo4j together with the prefix /bin.

The default configuration that comes with BioCypher and more configuration parameters for the Settings are listed in BioCypher Configuration Reference.

📝 Exercise: Revise and complete the biocypher_config.yaml file, and make sure it is located in the config folder.

Answer:

See the example below for a completed biocypher_config.yaml. Note, the path in the import_call_bin_prefix correspond to my personal instance, you MUST update this path with yours, do not forget to add /bin/ as in my example

File: biocypher_config.yaml

#---------------------------------------------------------------
#--------        BIOCYPHER GENERAL CONFIGURATION        --------
#---------------------------------------------------------------
biocypher:
    offline: true
    debug: false
    schema_config_path: config/schema_config.yaml
    cache_directory: .cache

#----------------------------------------------------
#--------        OUTPUT CONFIGURATION        --------
#----------------------------------------------------
neo4j:
    database_name: neo4j
    csv_column_delimiter: '\t'
    csv_array_delimiter: '|'
    file_format: csv
    skip_duplicate_nodes: true
    skip_bad_relationships: true
    import_call_bin_prefix: <path-to-neo4j-dbms>/bin/

Step 2. Create an adapter

Adapter creation step in the BioCypher pipeline
Figure 11. Adapter creation in the BioCypher pipeline.

Rationale: An adapter allows you to efficiently transform, integrate, combine data from different sources ensuring compatibility with BioCypher's schema and streamlining the import process.

You

you> let's move to phase 2. Implementation Strategy Design: Decide on adapter architecture and extraction strategy.

Agent

copilot> For your TSV protein interaction data and schema, the recommended approach is:

Adapter Architecture:
- Use a Simple Adapter (single resource, flat structure).

Extraction Strategy:
- Extract protein nodes and interaction edges directly from the TSV. - Map columns to schema fields (e.g., uniprot, gene_symbol, interaction type). - Add error handling for missing/invalid fields. - Validate that all required columns are present.

Would you like a code template or a step-by-step extraction plan?

You

you> look at the code in src/tutorial_basics/adapters and evaluate what is needed for a simple adapter.

Agent

copilot> The current TutorialBasicsAdapter is a stub with hardcoded (dummy) nodes and edges. To become a functional simple adapter for your TSV protein interaction data, you need to:

  1. Update get_nodes to parse proteins from the TSV (using columns like source, target, source_genesymbol, etc.).
  2. Update get_edges to yield interaction edges from each TSV row, mapping columns to schema fields.
  3. Ensure the adapter reads the TSV (not CSV) and handles all relevant columns.
  4. Optionally, improve error handling and validation for TSV format.

Would you like a code template or a step-by-step guide for these changes?

You

you> update get_nodes and get_edges to be consistent with the schema and tsv file

Agent

copilot> I have the TSV columns and the schema structure. I’ll update get_nodes to yield unique protein nodes (with uniprot, gene_symbol, ncbi_tax_id, entity_type) and get_edges to yield edges (binding, activation, etc.) with all required properties.

I’ll now implement these changes in tutorial_basics_adapter.py.

You

you> check that the config schema yaml and the adapter file match

Agent

copilot> The adapter and schema config match:

  • Nodes: The adapter yields nodes with label "uniprot_protein" and properties matching the proteins section in the YAML.
  • Edges: The adapter yields edges with labels (binding, activation, inhibition, phosphorylation, ubiquitination) that match the input_label fields in the YAML, and the properties align.

Conclusion: The adapter and schema config are consistent and compatible. No changes needed.

BioCypher expects each node or edge being a tuple (datastructure) as explained in the next section.

Nodes Convention

BioCypher expects each node being a 3-element tuple, with elements in the following order: 1. [mandatory] ID: this is a unique identifier for the node (i.e., P53) 2. [mandatory] LABEL: this is a namespace for the node (i.e, uniprot) 3. [mandatory] PROPERTIES: a dictionary containing properties for the node, which may be empty (i.e., have no entries).

For example:

#  ✅ This is a 3-element tuple
# 1     2          3
("TP53", "uniprot", {"entity_type": "protein", "ncbi_tax_id":9096})
# ✖️ This is NOT a 3-element tuple
#1      2          3              4          5              6
("TP53", "uniprot", "entity_type", "protein", "ncbi_tax_id", 9096)

Edges Convention

BioCypher expects each edge being a 5-element tuple, with elements in the following order:

  1. [optional] ID: this is a unique identifier for the edge (i.e., P53CREB1)
  2. [mandatory] SOURCE: this is the ID for the source node (i.e, TP53)
  3. [mandatory] TARGET: this is the ID for the source node (i.e, CREB1)
  4. [mandatory] LABEL: this is a namespace for the edge (i.e, activation, inhibition)
  5. [mandatory] PROPERTIES: a dictionary containing properties for the edge, which may be empty (i.e., have no entries).
#  ✅ This is a 5-element tuple for BioCypher
#1   2      3        4                 5
(None, "TP53", "CREB1", "ubiquitination", {"is_directed": True})
# ✖️ This is NOT the 5-element tuple format that BioCypher expects. Can you explain why?
#1           2      3                 4              5
("TP53CREB1", "TP53", "ubiquitination", "is_directed", True)

By using the AI agent, we have created functions that read the data as a DataFrame and override the functions to extract nodes and edges in the formats expected by BioCypher. This is illustrated in the below snippet.

File: src/tutorial_basics/adapters/tutorial_basics_adapter.py
def get_nodes(self):
    """
    Extract unique protein nodes from the TSV data source.
    Yields:
        Tuples of (uniprot, 'uniprot_protein', properties_dict) for each protein
    """
    logger.info("Extracting protein nodes from TSV data source")
    df = pd.read_csv(self.data_source, sep='\t')
    proteins = {}
    for idx, row in df.iterrows():
        for prefix in ["source", "target"]:
            uniprot = row[f"{prefix}"]
            gene_symbol = row[f"{prefix}_genesymbol"]
            ncbi_tax_id = str(row[f"ncbi_tax_id_{prefix}"])
            entity_type = row[f"entity_type_{prefix}"]
            if uniprot not in proteins:
                proteins[uniprot] = {
                    "uniprot": uniprot,
                    "gene_symbol": gene_symbol,
                    "ncbi_tax_id": ncbi_tax_id,
                    "entity_type": entity_type
                }
    for prot in proteins.values():
        yield (prot["uniprot"], "uniprot_protein", prot)
    logger.info(f"Extracted {len(proteins)} protein nodes")

You

you> check with 3. Implementation: Develop the adapter using the chosen strategy. that the implementation of the adapter seems correct

Agent

copilot> Based on the "Implementation" phase guidance and your code:

  • The adapter implements the required methods: initialization, node extraction (get_nodes), edge extraction (get_edges), metadata, and validation.
  • Field mapping is direct and matches the schema.
  • Extraction logic is clear and fits a simple adapter pattern.
  • No complex conditional or fallback logic is needed for this flat CSV structure.

Conclusion: The adapter implementation is correct for the chosen simple extraction strategy and meets BioCypher requirements.

📝 Exercise: Integrate the aforementioned snippets in a single file called tutorial_basics_adapter.py.

Answer:

See the example below for a completed tutorial_basics_adapter.py.

File: src/tutorial_basics/adapters/tutorial_basics_adapter.py

"""
TutorialBasicsAdapter Adapter

This adapter handles CSV data source for BioCypher.
"""

import logging
from pathlib import Path
import pandas as pd

logger = logging.getLogger(__name__)


class TutorialBasicsAdapter:
    """
    Adapter for CSV data source.

    This adapter implements the BioCypher adapter interface for CSV data.
    """

    def __init__(self, data_source: str | Path, **kwargs):
        """
        Initialize the adapter.

        Args:
            data_source: Path to the CSV data source
            **kwargs: Additional configuration parameters
        """
        self.data_source = data_source
        self.config = kwargs
        logger.info(f"Initialized TutorialBasicsAdapter with data source: {data_source}")

    def get_nodes(self):
        """
        Extract unique protein nodes from the TSV data source.
        Yields:
            Tuples of (uniprot, 'uniprot_protein', properties_dict) for each protein
        """
        logger.info("Extracting protein nodes from TSV data source")
        df = pd.read_csv(self.data_source, sep='\t')
        proteins = {}
        for idx, row in df.iterrows():
            for prefix in ["source", "target"]:
                uniprot = row[f"{prefix}"]
                gene_symbol = row[f"{prefix}_genesymbol"]
                ncbi_tax_id = str(row[f"ncbi_tax_id_{prefix}"])
                entity_type = row[f"entity_type_{prefix}"]
                if uniprot not in proteins:
                    proteins[uniprot] = {
                        "uniprot": uniprot,
                        "gene_symbol": gene_symbol,
                        "ncbi_tax_id": ncbi_tax_id,
                        "entity_type": entity_type
                    }
        for prot in proteins.values():
            yield (prot["uniprot"], "uniprot_protein", prot)
        logger.info(f"Extracted {len(proteins)} protein nodes")

    def get_edges(self):
        """
        Extract interaction edges from the TSV data source.
        Yields:
            Tuples of (None, source_uniprot, target_uniprot, edge_label, properties_dict) for each edge
        """
        logger.info("Extracting edges from TSV data source")
        import pandas as pd
        df = pd.read_csv(self.data_source, sep='\t')
        for idx, row in df.iterrows():
            edge_label = row["type"]
            properties = {
                "source_uniprot": row["source"],
                "target_uniprot": row["target"],
                "is_directed": bool(row["is_directed"]),
                "is_stimulation": bool(row["is_stimulation"]),
                "is_inhibition": bool(row["is_inhibition"]),
                "consensus_direction": bool(row["consensus_direction"]),
                "consensus_stimulation": bool(row["consensus_stimulation"]),
                "consensus_inhibition": bool(row["consensus_inhibition"])
            }
            yield (
                None,
                row["source"],
                row["target"],
                edge_label,
                properties
            )
        logger.info(f"Extracted {len(df)} edges")

    def get_metadata(self) -> dict[str, any]:
        """
        Get metadata about the data source.

        Returns:
            Dictionary containing metadata
        """
        return {
            'name': 'TutorialBasicsAdapter',
            'data_source': str(self.data_source),
            'data_type': 'csv',
            'version': '0.1.0',
            'adapter_class': 'TutorialBasicsAdapter'
        }

    def validate_data_source(self) -> bool:
        """
        Validate that the CSV data source is accessible and properly formatted.

        Returns:
            True if data source is valid, False otherwise
        """
        try:
            data_path = Path(self.data_source)
            if not data_path.exists() or not data_path.is_file():
                return False

            # Try to read the CSV to validate format
            df = pd.read_csv(data_path, nrows=1)  # Read just first row
            return len(df.columns) > 0

        except Exception as e:
            logger.error(f"Data source validation failed: {e}")
            return False

Step 3. Test and validate the adapter

Rationale: Validate the adapter using testing strategies - this way, the AI agent can support you in determining if any changes to the schema or adapter source code need to be made, and your adapter becomes more robust.

You

you> move to step 4. Quality Assurance: Test and validate the adapter.

Agent

copilot> Step 4: Quality Assurance involves two main tasks:

  1. Adaptive Testing Strategy:

    • Create a test suite based on your adapter’s data characteristics.
    • Implement schema compliance tests and add data-specific tests (e.g., for relationships, temporal, or hierarchical data).
  2. Validation Framework:

    • Build an adaptive validation framework with rules tailored to your data.
    • Apply these validations to your adapter’s output.

Would you like help scaffolding test files, implementing example tests, or setting up a validation framework for your adapter? If so, please specify your adapter’s data characteristics.

You

you> the adapter file is located in src/tutorial_basics/adapters and the tests are in tests. update the tests to match the adapter file

Agent

copilot> Your adapter now expects a TSV (tab-separated) file with specific columns (e.g., source, target, source_genesymbol, ncbi_tax_id_source, entity_type_source, etc.), but your tests use simple CSVs with unrelated columns (id, name, type). This mismatch means the tests won’t properly validate the adapter.

I will update the tests to:
- Generate a temporary TSV file with the correct columns and data.
- Ensure get_nodes and get_edges tests match the adapter’s expected input/output.

Proceeding to update the test file accordingly.

You

you> Review the tests and make sure you have covered all necessary items for biocypher.

Agent

copilot> Your tests cover:
- Adapter initialization (with/without config)
- Metadata retrieval
- Node extraction from a TSV with the correct schema
- Edge extraction from a TSV with the correct schema
- Data source validation (existing and non-existent files)

Recommendations for full BioCypher adapter QA:
1. Schema Compliance: Covered (tests expect correct columns).
2. Edge/Node Property Validation: Covered (asserts on all expected properties).
3. Error Handling: Test for missing/invalid columns or malformed files.
4. Edge Cases: Test empty files, duplicate nodes/edges, and unexpected types.
5. Data Types: Test with non-string IDs, missing values, or mixed types.
6. Logging/Warnings: Optionally, test that errors are logged.

Would you like to add tests for error handling, edge cases, or data type robustness?

You should review the tests that have been generated and make sure that error handling and edge cases are covered by the tests. This ensures that when you run BioCypher on the data, errors are spotted and you are notified of any missing or incomplete data.

Run the tests yourself to verify that they are passing:

pip install pytest
python -m pytest
If the tests are not passing, prompt the AI agent to fix the errors and retry.

Step 4. Create documentation for your adapter

Rationale: By documenting what your adapter does and how, you and others will be able to understand the rationale behind it, and it will be usable by others and extendable in the future.

You

you> Move on to phase 5. Documentation and Maintenance: Document usage and plan for future updates.

Agent

copilot> Phase 5 guidance expects:

  • Adaptive Documentation: Overview, data structure, extraction strategy, usage examples, and troubleshooting guide.
  • Maintenance Planning: Plan for future updates and document decision rationale.

Your tutorial-basics/README.md already covers overview, usage, config, testing, and development. To fully meet phase 5, you should:
- Add a section on the adapter’s extraction strategy and data structure.
- Add a troubleshooting section (e.g., common errors, empty files).
- Briefly describe how to update/extend the adapter and document key design decisions.

Would you like me to draft these additions for your README?

The AI agent can help you to draft a comprehensive documentation, as well as docstrings in your Python code.

You

you> Also add docstrings in the adapter Python file and test files.

Now you can move on to importing your data into BioCypher to create a knowledge graph.

Step 5. Create a knowledge graph script

BioCypher pipeline overview
Figure 12. BioCypher pipeline

Rationale: Integrating all steps—downloading the dataset, loading the data, extracting nodes and edges, and exporting graph assets—into a single script streamlines the entire process. This approach makes it easier to build and manage the knowledge graph pipeline efficiently and reproducibly.

You

you> look at the knowledge graph script in tutorial-basics/create_knowledge_graph.py. Does it check out with the current adapter, data source location and data schema / biocypher config?

Agent

copilot> Here’s the compatibility check:

  • Adapter: Expects a TSV/CSV with columns: source, source_genesymbol, ncbi_tax_id_source, entity_type_source, target, target_genesymbol, ncbi_tax_id_target, entity_type_target, type, and various boolean flags.
  • Script: The data source path is set to "data/your_data.csv" (placeholder). No actual file found in tutorial-basics/data/.
  • Schema: schema_config.yaml expects node properties (uniprot, gene_symbol, etc.) and edge properties (source_uniprot, target_uniprot, etc.), matching the adapter’s output.
  • Config: biocypher_config.yaml points to the correct schema and is set up for Neo4j.

Action needed:
- Update data_source in the script to the actual data file path (ensure the file exists).
- Ensure your data file columns match the adapter and schema expectations.

Otherwise, the script, adapter, and configs are compatible.

You

you> the data source should be set to data/synthetic_protein_interactions.tsv

Agent

copilot> The data source path in create_knowledge_graph.py is now set to data/synthetic_protein_interactions.tsv. This matches your adapter and schema expectations. No further action is needed unless the file or columns are missing.

Take a look at the script to understand what it does:

  1. Create a BioCypher object

    File: create_knowledge_graph.py
    # Initialize BioCypher
    bc = BioCypher(
        biocypher_config_path="config/biocypher_config.yaml",
        schema_config_path="config/schema_config.yaml"
    )
    
  2. Use BioCypher to load the file.

    File: create_knowledge_graph.py
    # TODO: Configure your CSV data source path here
    data_source = "data/synthetic_protein_interactions.tsv"  # Updated to match actual data file
    
  3. Write data from your adapter to BioCypher

    File: create_knowledge_graph.py
    # Create a knowledge graph from the adapter
    bc.write_nodes(adapter.get_nodes())
    bc.write_edges(adapter.get_edges())
    
  4. Export your graph to Neo4j (generation of the import script)

    File: create_knowledge_graph.py
    # Generate assets for Neo4j exportation
    bc.write_import_call()
    
  5. Print summary

    File: create_knowledge_graph.py
    # Print a summary when
    bc.summary()
    

📝 Exercise: Integrate the aforementioned snippets in a single file called create_knowledge_graph.py script and RUN IT!

Answer:

See the example below for a completed create_knowledge_graph.py.

File: create_knowledge_graph.py

#!/usr/bin/env python3
"""
tutorial-basics - Hands-on basic graph creation with BioCypher using a small synthetic dataset.

This script creates a knowledge graph using BioCypher and the TutorialBasicsAdapter.
"""

from biocypher import BioCypher
from tutorial_basics.adapters.tutorial_basics_adapter import TutorialBasicsAdapter


def main():
    """Main function to create the knowledge graph."""
    # Initialize BioCypher
    bc = BioCypher(
        biocypher_config_path="config/biocypher_config.yaml",
        schema_config_path="config/schema_config.yaml"
    )

    # Initialize the adapter
    # TODO: Configure your CSV data source path here
    data_source = "data/synthetic_protein_interactions.tsv"  # Updated to match actual data file

    adapter = TutorialBasicsAdapter(
        data_source=data_source,
        # Add any additional configuration parameters here
    )

    # Create the knowledge graph
    bc.write_nodes(adapter.get_nodes())
    bc.write_edges(adapter.get_edges())

    # Generate assets for Neo4j exportation
    bc.write_import_call()

    # Create final summary
    bc.summary()


if __name__ == "__main__":
    main()

Run the script

You can execute the entire pipeline that loads, processes, and builds the graph by running the following command from the root folder of your project.

python create_knowledge_graph.py

🏆 Note: Once you complete the process, your terminal output should look similar to the following:

Terminal output:
INFO -- This is BioCypher v0.17.0.
INFO -- Logging into `biocypher-log/biocypher-<timestamp>.log`.
INFO -- Running BioCypher with schema configuration from config/schema_config.yaml.
INFO -- Loading ontologies...
INFO -- Instantiating OntologyAdapter class for https://github.com/biolink/biolink-model/raw/v3.2.1/biolink-model.owl.ttl.
INFO -- Creating output directory `biocypher-out/<timestamp>`.
INFO -- `labels_order`=`Ascending` superseded by either `node_labels_order`=`None` or `edge_labels_order`=`None`.
INFO -- `node_labels_order` set to `labels_order`=`Ascending`.
INFO -- `edge_labels_order` set to `labels_order`=`Ascending`.
WARNING -- Neo4j supports only edge_labels_order: 'Leaves', I'll set it for you, but you should fix your configuration file in the `neo4j` section.
INFO -- Writing 15 entries to Protein-part000.csv
WARNING -- Duplicate edge type ubiquitination found.
WARNING -- Duplicate edge type phosphorylation found.
INFO -- Writing 3 entries to Binding-part000.csv
INFO -- Writing 6 entries to Activation-part000.csv
INFO -- Writing 3 entries to Phosphorylation-part000.csv
INFO -- Writing 7 entries to Ubiquitination-part000.csv
INFO -- Writing 2 entries to Inhibition-part000.csv
INFO -- Writing neo4j import call to `biocypher-out/<timestamp>/neo4j-admin-import-call.sh`.
INFO -- Showing ontology structure based on https://github.com/biolink/biolink-model/raw/v3.2.1/biolink-model.owl.ttl
INFO --
entity
├── association
│   └── gene to gene association
│       └── pairwise gene to gene interaction
│           └── pairwise molecular interaction
│               └── protein protein interaction
│                   ├── activation
│                   ├── binding
│                   ├── inhibition
│                   ├── phosphorylation
│                   └── ubiquitination
└── named thing
    └── biological entity
        └── polypeptide
            └── protein

INFO -- No duplicate nodes in input.
INFO -- Duplicate edge types encountered (IDs in log):
    ubiquitination
    phosphorylation

INFO -- No missing labels in input.

Note that BioCypher creates logging information and output files in a subdirectory relative to where it is executed, biocypher-log/biocyper-<timestamp>.log and biocypher-out/<timestamp>. This allows you to look up details from the biocypher run and compare with the generated output.

Section 4. Interacting with your graph using Neo4j

Load the graph using an import script

When you run the create_knowledge_graph.py script and it completes successfully, it generates several CSV files and an import script to load the graph data into Neo4j.

a. Look for a folder whose name starts with biocypher-out. Each time you run the script, a new folder is created inside biocypher-out with a timestamp. Inside this folder, you should see the following:

🟨 CSV files associated to nodes.

🟦 CSV files associated to edges.

🟥 admin import script

/biocypher-out
└── <timestamp>
    ├── 🟦 Activation-header.csv
    ├── 🟦 Activation-part000.csv
    ├── 🟦 Binding-header.csv
    ├── 🟦 Binding-part000.csv
    ├── 🟦 Inhibition-header.csv
    ├── 🟦 Inhibition-part000.csv
    ├── 🟥 neo4j-admin-import-call.sh
    ├── 🟦 Phosphorylation-header.csv
    ├── 🟦 Phosphorylation-part000.csv
    ├── 🟨 Protein-header.csv
    ├── 🟨 Protein-part000.csv
    ├── 🟦 Ubiquitination-header.csv

b. Stop the neo4j instance. You can do this on the GUI or in terminal. In terminal, you must locate the neo4j executable in the Neo4j instance path.

<path of your Neo4j instance>/bin/neo4j stop

c. Run the neo4j-admin-import-call.sh script in your biocypher-out/:

bash ./biocypher-out/<timestamp>/neo4j-admin-import-call.sh

d. If everything has been successfully, you should see in terminal something similar to this:

Terminal output:
Starting to import, output will be saved to: <path-to-neo4j-dbms>/logs/neo4j-admin-import-<timestamp>.log
Neo4j version: 2025.07.1
Importing the contents of these files into <path-to-neo4j-dbms>/data/databases/neo4j:
Nodes:
<path-to-project>/biocypher-out/<timestamp>/Protein-header.csv
<path-to-project>/biocypher-out/<timestamp>/Protein-part000.csv

Relationships:
null:
<path-to-project>/biocypher-out/<timestamp>/Phosphorylation-header.csv
<path-to-project>/biocypher-out/<timestamp>/Phosphorylation-part000.csv
<path-to-project>/biocypher-out/<timestamp>/Ubiquitination-header.csv
<path-to-project>/biocypher-out/<timestamp>/Ubiquitination-part000.csv
<path-to-project>/biocypher-out/<timestamp>/Inhibition-header.csv
<path-to-project>/biocypher-out/<timestamp>/Inhibition-part000.csv
<path-to-project>/biocypher-out/<timestamp>/Activation-header.csv
<path-to-project>/biocypher-out/<timestamp>/Activation-part000.csv
<path-to-project>/biocypher-out/<timestamp>/Binding-header.csv
<path-to-project>/biocypher-out/<timestamp>/Binding-part000.csv


Available resources:
Total machine memory: 15.27GiB
Free machine memory: 1.112GiB
Max heap memory : 910.5MiB
Max worker threads: 12
Configured max memory: 541.5MiB
High parallel IO: true

Import starting
Page cache size: 539.4MiB
Number of worker threads: 12
Estimated number of nodes: 15
Estimated number of relationships: 21

Importing nodes
.......... .......... .......... .......... ..........   5% ∆295ms [295ms]
.......... .......... .......... .......... ..........  10% ∆2ms [298ms]
.......... .......... .......... .......... ..........  15% ∆0ms [298ms]
.......... .......... .......... .......... ..........  20% ∆0ms [299ms]
.......... .......... .......... .......... ..........  25% ∆0ms [299ms]
.......... .......... .......... .......... ..........  30% ∆0ms [299ms]
.......... .......... .......... .......... ..........  35% ∆0ms [300ms]
.......... .......... .......... .......... ..........  40% ∆0ms [300ms]
.......... .......... .......... .......... ..........  45% ∆0ms [300ms]
.......... .......... .......... .......... ..........  50% ∆0ms [301ms]
.......... .......... .......... .......... ..........  55% ∆0ms [301ms]
.......... .......... .......... .......... ..........  60% ∆0ms [301ms]
.......... .......... .......... .......... ..........  65% ∆0ms [302ms]
.......... .......... .......... .......... ..........  70% ∆0ms [302ms]
.......... .......... .......... .......... ..........  75% ∆0ms [302ms]
.......... .......... .......... .......... ..........  80% ∆0ms [303ms]
.......... .......... .......... .......... ..........  85% ∆0ms [303ms]
.......... .......... .......... .......... ..........  90% ∆0ms [303ms]
.......... .......... .......... .......... ..........  95% ∆0ms [304ms]
.......... .......... .......... .......... .......... 100% ∆0ms [304ms]
Prepare ID mapper
.......... .......... .......... .......... ..........   5% ∆33ms [33ms]
.......... .......... .......... .......... ..........  10% ∆0ms [33ms]
.......... .......... .......... .......... ..........  15% ∆0ms [33ms]
.......... .......... .......... .......... ..........  20% ∆0ms [34ms]
.......... .......... .......... .......... ..........  25% ∆0ms [34ms]
.......... .......... .......... .......... ..........  30% ∆0ms [34ms]
.......... .......... .......... .......... ..........  35% ∆46ms [81ms]
.......... .......... .......... .......... ..........  40% ∆0ms [81ms]
.......... .......... .......... .......... ..........  45% ∆0ms [81ms]
.......... .......... .......... .......... ..........  50% ∆0ms [82ms]
.......... .......... .......... .......... ..........  55% ∆0ms [82ms]
.......... .......... .......... .......... ..........  60% ∆0ms [82ms]
.......... .......... .......... .......... ..........  65% ∆0ms [82ms]
.......... .......... .......... .......... ..........  70% ∆0ms [82ms]
.......... .......... .......... .......... ..........  75% ∆0ms [83ms]
.......... .......... .......... .......... ..........  80% ∆0ms [83ms]
.......... .......... .......... .......... ..........  85% ∆0ms [83ms]
.......... .......... .......... .......... ..........  90% ∆0ms [83ms]
.......... .......... .......... .......... ..........  95% ∆1ms [85ms]
.......... .......... .......... .......... .......... 100% ∆0ms [85ms]
Imported 15 nodes in 458ms
using configuration:Configuration[numberOfWorkers=12, temporaryPath=<path-to-neo4j-dbms>/data/databases/neo4j/temp, applyBatchSize=64, sorterSizeSwitchFactor=0.3]
Importing relationships
.......... .......... .......... .......... ..........   5% ∆35ms [35ms]
.......... .......... .......... .......... ..........  10% ∆0ms [35ms]
.......... .......... .......... .......... ..........  15% ∆0ms [35ms]
.......... .......... .......... .......... ..........  20% ∆0ms [35ms]
.......... .......... .......... .......... ..........  25% ∆0ms [35ms]
.......... .......... .......... .......... ..........  30% ∆0ms [36ms]
.......... .......... .......... .......... ..........  35% ∆28ms [64ms]
.......... .......... .......... .......... ..........  40% ∆0ms [64ms]
.......... .......... .......... .......... ..........  45% ∆0ms [64ms]
.......... .......... .......... .......... ..........  50% ∆0ms [64ms]
.......... .......... .......... .......... ..........  55% ∆0ms [64ms]
.......... .......... .......... .......... ..........  60% ∆0ms [64ms]
.......... .......... .......... .......... ..........  65% ∆0ms [64ms]
.......... .......... .......... .......... ..........  70% ∆0ms [64ms]
.......... .......... .......... .......... ..........  75% ∆0ms [64ms]
.......... .......... .......... .......... ..........  80% ∆0ms [64ms]
.......... .......... .......... .......... ..........  85% ∆0ms [64ms]
.......... .......... .......... .......... ..........  90% ∆0ms [64ms]
.......... .......... .......... .......... ..........  95% ∆0ms [65ms]
.......... .......... .......... .......... .......... 100% ∆0ms [65ms]
Imported 21 relationships in 248ms
Flushing stores
Flush completed in 18ms
IMPORT DONE in 1s 488ms.

Visualize the graph

a. Connect to your instance by running Neo4j desktop again. Select your instance and click on "Connect" - the little arrow on the button allows you to expand a menu. Select the option Query.

Query and Explore options in a Neo4j instance
Figure 13. Query and Explore options to run on a Neo4j instance.

b. Now, click on the asterisk under the Relationships category. You now should see your graph! Compare to the sketch you did previosly in this tutorial

Neo4j graph built from the tutorial data
Figure 14. Neo4j graph based on our data.

Execute cypher queries

Try the following queries:

  1. Find relationships between two nodes
    MATCH (a)-[r]->(b)
    RETURN a, r, b;
    
    Result:
Neo4j browser result for a query finding relationships between two nodes
  1. Find all the nodes

MATCH (n)
RETURN n;
Result:

Neo4j browser result for a query finding all nodes
  1. Find all nodes of a specific type(e.g. Protein in the following query)

MATCH (n:Protein)
RETURN n;
Result:

Neo4j browser result for a query finding all Protein nodes
  1. Find all relationships of a specific type(e.g. Binding in the following query)

MATCH (a)-[r:Binding]->(b)
RETURN a, r, b;
Result:

Neo4j browser result for a query finding all Binding relationships
  1. Count relationships of a given type(e.g. Binding in the following query)

MATCH (a)-[r:Binding]->(b)
RETURN COUNT(r) AS totalBindings;
Result:

Neo4j browser result for a query counting Binding relationships

Next Steps

  • Explore more advanced queries and graph analytics in Neo4j.
  • Try integrating additional datasets or expanding your graph model. For guidance, see the Basics tutorial.
  • Review the BioCypher documentation for deeper insights.

Feedback & Contributions

If you found this tutorial helpful or have suggestions for improvement, please open an issue or submit a pull request in the BioCypher repository. Specific feedback on examples, clarity of instructions, or missing details is especially appreciated.


Last Update Developed by Affiliation
2026.09.17 Inga Ulusoy (GH @iulusoy) Scientific Software Center