How the MiroFish Ontology Generator Defines and Manages Entity and Edge Types in the Graph
The MiroFish ontology generator converts raw simulation documents into a structured knowledge-graph schema by prompting an LLM to produce JSON definitions for entity and edge types, validating those definitions against strict capacity limits, injecting mandatory fallback types, and finally emitting Zep-compatible Python classes.
The ontology generation service sits at the heart of the MiroFish platform, transforming unstructured text into a formal graph schema that drives simulations. This article examines how the OntologyGenerator class in backend/app/services/ontology_generator.py defines entity and edge types, enforces structural constraints, and manages type hierarchies through a three-stage pipeline.
Ontology Generation Pipeline
The generation workflow follows a deterministic sequence from prompt construction to executable code output.
Stage 1: Prompt Construction
The process begins with the ONTOLOGY_SYSTEM_PROMPT constant defined at lines 11-55 of backend/app/services/ontology_generator.py. This deterministic prompt instructs the LLM to return a JSON object containing three top-level keys: entity_types, edge_types, and analysis_summary.
The prompt enforces strict schema rules:
- Exactly 10 entity types must be defined
- Two fallback entity types (
PersonandOrganization) must be included - Descriptions are capped at 100 characters
- Each entity requires
name,description, optionalattributes, and optionalexamples
Stage 2: LLM Validation and Post-Processing
The generate() method (lines 57-85) orchestrates the LLM call via LLMClient.chat_json, passing the constructed prompt along with the user-provided document_texts, simulation_requirement, and additional_context.
Raw LLM output flows into _validate_and_process() (lines 87-104), which performs critical normalization:
- Guarantees required top-level keys exist in the JSON
- Initializes missing
attributesandexamplesfields with empty lists - Truncates descriptions exceeding length limits
- Enforces hard limits of 10 entity types and 10 edge types via truncation
Fallback Injection: If the mandatory Person or Organization types are missing, the generator injects them with predefined attributes—full_name and role for Person, org_name and org_type for Organization. When capacity is reached, the system evicts the least-important custom types to accommodate these fallbacks.
Stage 3: Python Code Generation
The generate_python_code() method (lines 106-149) transforms the validated ontology JSON into executable Python modules. This stage creates:
- Entity Classes: Zep-compatible
EntityModelsubclasses for each entity type (lines 71-89) - Edge Classes:
EdgeModelsubclasses defining relationships withsource_targetsconstraints (lines 94-117) - Lookup Dictionaries:
ENTITY_TYPES,EDGE_TYPES, andEDGE_SOURCE_TARGETSmappings for runtime type resolution (lines 123-149)
The generated code imports base classes from zep_cloud.external_clients.ontology, ensuring compatibility with the Zep graph database API.
Entity Type Management Strategy
The ontology generator implements a rigorous entity type governance model to ensure graph consistency.
Structural Requirements
Each entity type definition must conform to a strict schema:
- Name: Unique identifier string
- Description: Maximum 100-character explanation of the entity's semantic meaning
- Attributes: Optional list of objects containing
name,type, anddescription - Examples: Optional list of string instances illustrating the entity type
Mandatory Fallback Types
The system guarantees two universal entity types exist in every ontology:
- Person: Automatically injected with attributes
full_name(string) androle(string) if missing from LLM output - Organization: Automatically injected with attributes
org_name(string) andorg_type(string) if missing
These fallbacks ensure that simulations always have basic actor types available for relationship modeling.
Capacity Enforcement
The MAX_ENTITY_TYPES = 10 constant creates a hard ceiling on ontology complexity. When fallback injection would exceed this limit, the _validate_and_process() method truncates the existing entity list, removing the least-important custom types (those at the end of the array) before appending the mandatory fallbacks.
Edge Type Management Strategy
Edge types follow parallel governance rules with relationship-specific constraints.
Relationship Structure
Each edge type requires:
- Name: Unique relationship identifier
- Description: Semantic explanation of the relationship (max 100 characters)
- Source Targets: List of valid source-target entity pairs (
[{source: "EntityA", target: "EntityB"}]) defining which entity types may connect via this edge - Attributes: Optional relationship properties (name, type, description)
Validation and Limits
The _validate_and_process() method applies identical normalization to edge types as it does to entities: missing fields receive default empty values, descriptions undergo truncation, and the MAX_EDGE_TYPES = 10 limit is strictly enforced through list truncation.
Schema Constraints
The source_targets field provides the critical constraint that prevents invalid graph connections. During code generation, these constraints translate into the EDGE_SOURCE_TARGETS dictionary, which the simulation engine consults before instantiating relationships.
Integration with Zep Graph Database
The generated ontology achieves runtime utility through tight integration with the Zep cloud platform. The generate_python_code() method imports EntityModel, EntityText, and EdgeModel from zep_cloud.external_clients.ontology, creating subclasses that Zep's graph API recognizes natively.
This integration allows the graph_builder.py service (located at backend/app/services/graph_builder.py) to import the generated ontology module and instantiate nodes and edges that comply with the defined schema, ensuring type safety throughout the simulation lifecycle.
Practical Implementation Examples
Generating an Ontology from Simulation Documents
from backend.app.services.ontology_generator import OntologyGenerator
# Sample simulation inputs
documents = [
"The protest was led by student groups at University X...",
"CEO of TechCorp announced a new product..."
]
simulation_req = "Model the spread of information about the new product and the protest."
additional = "Focus on interactions between media outlets and public figures."
# Initialize generator and produce ontology JSON
generator = OntologyGenerator()
ontology_json = generator.generate(
document_texts=documents,
simulation_requirement=simulation_req,
additional_context=additional
)
# Access defined types
print(ontology_json["entity_types"])
print(ontology_json["edge_types"])
The generate() method constructs the user message (lines 28-53 in the source), invokes LLMClient.chat_json, and normalizes output through _validate_and_process().
Converting Ontology to Executable Python Code
# Generate Zep-compatible Python module
python_code = generator.generate_python_code(ontology_json)
# Persist for simulation engine consumption
with open("custom_ontology.py", "w", encoding="utf-8") as f:
f.write(python_code)
# Runtime usage in graph builder
# from custom_ontology import Person, Organization, ENTITY_TYPES, EDGE_TYPES
This code generation loop produces entity class definitions (lines 71-89), edge class definitions (lines 94-117), and lookup dictionaries (ENTITY_TYPES, EDGE_TYPES, EDGE_SOURCE_TARGETS at lines 123-149).
Key Source Files and Architecture
| File | Role | Location |
|---|---|---|
backend/app/services/ontology_generator.py |
Core service implementing prompt construction, LLM orchestration, validation with fallback injection, and Python code generation. | ontology_generator.py |
backend/app/utils/llm_client.py |
Utility wrapper handling LLM provider communication; provides chat_json method used by the ontology generator. |
llm_client.py |
backend/app/services/graph_builder.py |
Consumer of generated ontology code; instantiates Zep-compatible nodes and edges during simulation execution. | graph_builder.py |
These components form the end-to-end pipeline that transforms unstructured simulation requirements into type-safe graph schemas for the MiroFish platform.
Summary
- The OntologyGenerator service converts raw documents into structured graph schemas through a three-stage pipeline: prompt construction, LLM validation with post-processing, and Python code generation.
- Entity types are strictly governed by a maximum limit of 10 types, with mandatory fallback injection for
PersonandOrganizationentities that include predefined attributes likefull_nameandorg_name. - Edge types support relationship constraints through
source_targetsdefinitions that specify valid source-target entity pairs, enforcing schema integrity at the type level. - The system generates Zep-compatible Python classes (
EntityModelandEdgeModelsubclasses) along with lookup dictionaries (ENTITY_TYPES,EDGE_TYPES,EDGE_SOURCE_TARGETS) for runtime graph construction. - All validation logic resides in
backend/app/services/ontology_generator.py, specifically within the_validate_and_process()method that enforces capacity limits and injects fallback types.
Frequently Asked Questions
How does the ontology generator ensure that essential entity types are always present?
The generator enforces the presence of Person and Organization entity types through mandatory fallback injection. If the LLM output omits these types, the _validate_and_process() method automatically appends them with predefined attributes—full_name and role for Person, org_name and org_type for Organization. If the entity list has reached the MAX_ENTITY_TYPES = 10 limit, the generator evicts the least-important custom types (those at the end of the list) to make room for these mandatory fallbacks.
What constraints exist on the number of entity and edge types in the ontology?
The system imposes strict capacity limits to maintain graph performance and schema clarity. The MAX_ENTITY_TYPES constant is set to 10, and MAX_EDGE_TYPES is similarly capped at 10. During the _validate_and_process() phase, any entity or edge types exceeding these limits are truncated from the end of their respective lists. This ensures that the final ontology JSON and generated Python code never contain more than 10 entity types and 10 edge types, regardless of the LLM's initial output.
How are relationship constraints between entity types enforced in the generated code?
Relationship constraints are enforced through the source_targets field in edge type definitions. Each edge type specifies a list of valid source-target pairs (e.g., [{"source": "Person", "target": "Organization"}]) that define which entity types may connect via that relationship. During code generation, these constraints populate the EDGE_SOURCE_TARGETS dictionary, which the simulation engine consults at runtime. The generated edge classes inherit from Zep's EdgeModel and maintain these constraints, ensuring that the graph builder can only create valid connections between compatible entity types.
What is the role of the generate_python_code() method in the ontology pipeline?
The generate_python_code() method serves as the final transformation stage, converting validated ontology JSON into executable Python modules that the simulation engine can import directly. This method generates three critical components: (1) EntityModel subclasses for each entity type with their defined attributes, (2) EdgeModel subclasses for each relationship type with source-target constraints, and (3) lookup dictionaries (ENTITY_TYPES, EDGE_TYPES, EDGE_SOURCE_TARGETS) that map type names to their corresponding classes. By importing EntityModel, EntityText, and EdgeModel from zep_cloud.external_clients.ontology, the generated code ensures full compatibility with the Zep graph database API, enabling the graph_builder.py service to instantiate type-safe nodes and edges during simulation execution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →