Say hello to the Graph Model Domain Specific Language (GMDSL), created with the help of Github’s Copilot.
I was ready to iterate on the implementation of BrainExpanded, especially the backend graph data model. I wanted to explore the use of Neo4j as my graph store and also start getting ready for a live deployment to real alpha-testing users. As I was learning Cypher (Neo4j’s graph langauge), it occured to me that I had to iterate on many aspects of the end-to-end, before I could finalize the graph data model for the graph store.
- Graph database data model. I wanted something simpler than Cypher when it comes to describing the graph nodes and edges.
- OpenAPI for the Web Service. This is the part of the BrainExpanded API that allows client applications to access the graph and ensures the appropriate authorization.
- C# classes to represent the data model. This is the implementation of the above contract but also additional, application-specific logic that requires queries against the graph store.
- Python representation of the data model. To make it easier to implement the AI agents that operate over the BrainExpanded memory, I implemented a data model in Python to represent the entries in the graph store. However, I later decided that perhaps the AI agents should not access the database directly and, instead, also go through the Web API layer. So the OpenAPI contract might be enough.
- As the OpenAPI changes, the Swift classes representing the data model will also change.
I also realized that the node types and relationships in the graph store will evolve differently for different users so the OpenAPI contract will have to evolve as well or be appropriately generic to support such an evolution. Stay tuned on this topic.
So what do software engineers do in such a case? They build a tool to automate things. Well, at least this is what I decided to do. I have been using Github’s Copilot a lot for BrainExpanded but for this little project, Copilot wrote 90-95% of the code with me supervising the iteration process as we were adding more features.
The result is a very simple DSL that helps me describe a simple graph data model. The tool then generates the Cypher, OpenAPI, and C# classes. The Python and Swift classes will be generated from the OpenAPI using existing tools.
Given that this is a generic tool and not tightly coupled with BrainExpanded, I am opening up the repository to everyone in case there is interest in adding more plugins or evolving its functionality. You can find it at https://github.com/savasp/gmdsl.
Here’s a simple example of a graph data model description:
namespace Example
// Import some core types for properties
import GM.Core
// New types can be declared for properties
type Address {
street: String
city: String
state: String
zipcode: Integer
country: String
}
// Declare some graph nodes
node Person {
firstName: String
lastName: String
dateOfBirth: DateTime
placeOfBirth: Location
}
node Company {
name: String
address: Address
}
// Department graph node
node Department {
name: String
}
// Declare some edges. As per Neo4j's data model, edges may have
// properties
edge PartOf(Department -> Company)
edge Friend(Person <-> Person) {
metOn: DateTime
}
edge Works(Person -> Company) {
role: String
}
I can now generate Cypher schema declarations, C# classes, and the OpenAPI contract.
Here’s the generated Cypher schema (with the generated comments removed). Over time, I will add the necessary logic to the cypher plugin to generate additional statements.
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Person) REQUIRE n.firstName IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Person) REQUIRE n.lastName IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Person) REQUIRE n.dateOfBirth IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Person) REQUIRE n.placeOfBirth IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Company) REQUIRE n.name IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Company) REQUIRE n.address IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Department) REQUIRE n.name IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR ()-[r:Friend]-() REQUIRE r.metOn IS NOT NULL;
CREATE CONSTRAINT IF NOT EXISTS FOR ()-[r:Works]-() REQUIRE r.role IS NOT NULL;
Here are two of the classes generated by the C# plugin. Relationships are represented as objects. This plugin has a bug. It assumes that nodes have IDs. I am working on a fix which will indeed require all nodes and relationships to have IDs since that’s necessary for my use case.
namespace Example
{
public class Example.Person
{
public string firstName { get; set; }
public string lastName { get; set; }
public System.DateTime dateOfBirth { get; set; }
public None placeOfBirth { get; set; }
}
public class Example.Friend
{
public string SourceId { get; set; }
public string TargetId { get; set; }
public System.DateTime metOn { get; set; }
}
}
And here’s an example of a generated OpenAPI contract (only parts of it are shown). Both graph nodes and relationships are represented as HTTP resources with the usual HTTP verbs.
paths:
/people:
post:
summary: Create Person
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/person'
responses:
'201':
description: Created
/people/{node-id}:
get:
summary: Get Person by ID
responses:
'200':
description: OK
put:
summary: Update Person by ID
...
delete:
summary: Delete Person by ID
...
/companies:
post:
...
/companies/{node-id}:
get:
...
put:
...
...
/people/{node-id}/friends:
post:
summary: Create Friend from Person
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/friend'
...
/people/{node-id}/friends/{relationship-id}:
get:
...
put:
...
...
...
components:
schemas:
person:
type: object
properties:
firstName:
type: string
lastName:
type: string
dateOfBirth:
type: string
placeOfBirth:
type: string
...
friend:
type: object
properties:
metOn:
type: string
This was a fun few hours-long distraction from the main work on BrainExpanded. I enjoyed it and learned even more about how to use Github’s Copilot as a coding companion. How do the kids call it these days? “Vibe coding”? I don’t like the term but here we are
Leave a Reply