Create a database
Create a table
tenant_id
column? By specifying this column, You are making the table tenant aware. The rows in it will belong to specific tenants. If you leave it out, the table is considered shared, more on this later.
Get credentials
Get third party credentials
Set the environment
.env.example
to .env
, and update it with the connection string you just
copied from Nile Console and the configuration of your AI vendor and model. Make sure you don’t include
the word “psql”. It should look something like this:yarn install
or npm install
.Run the application
npm start
or yarn start
.
Now you can use curl
to explore the APIs. Here are a few examples:Check the data in Nile
What's next?
src/app.ts
.
It creates an Express app, and sets up some of the usual middleware:
tenantDB
which wraps Drizzle and passes the tenant context to Nile.
And of course, the handler that uses AI models to automatically estimate the time to complete tasks.
Lets look at these one by one.
AsyncLocalStorage
- which provides a way to store data that is global and unique per execution context.
The data we are storing is the tenant ID, and by passing next
as the second argument to tenantContext.run
we are making sure that all the code that runs after this middleware will have access to the tenant ID.
db/db.ts
- this is where we set up Drizzle and connect it to Nile. We use pg
client as the driver for Drizzle, and we pass it the connection string from the environment.
tenantDB
function. This is where we wrap Drizzle with Nile’s tenant virtualization features.
We need Drizzle to set Nile context on the connection before each query we execute. We do this by accepting each query as a callback and wrapping it in a transaction that starts with set nile.tenant_id
.
We get the tenant ID from the tenant context, which we set earlier in the middleware.
tenantDB
work, lets take a look at how we use them to handle a request for all todos for a tenant.
We are using the tenantDB
function to execute a query that returns all the todos for a tenant.
The query doesn’t need to include the tenant ID in the where
clause, because tenantDB
will set it in the context.
app.post("/api/tenants/:tenantId/todos"
. This handler executes when users add new tasks.
This is what the handler code looks like:
findSimilarTasks
, aiEstimate
and embedTask
are all defined in AiUtils.ts
.
They are wrappers around standard AI model calls and database queries, and they handle the specifics of the AI model we are using.
This will make it easy to switch models in the future.
Getting similar tasks is done by querying the database for tasks with similar embeddings.
SEARCH_QUERY
task type. This is because we are looking for
similar tasks to the new task. We use an embedding model from the nomic
family, which is trained to perform specific types of embedding tasks. Telling it that we are generating the embedding for a lookup vs
generating an embedding that we will store with the document (as we’ll do in a bit), should help the model produce more relevant results.
DrizzleORM provides the magical sql
method, as well as the cosineDistance
function, which we use to work
with pg_vector
and calculate the similarity between the embeddings of the new task and the existing tasks.
As you can see, we filter out results where the cosine distance is higher than 1.
The lower the cosine distance is, the more similar the tasks are (0 indicate that they are identical).
A cosine distance of 1 means that the vectors are essentially unrelated, and when cosine distance is closer to 2, it indicates that the vectors are semantically opposites.
The embedTask
function uses the embedding model to generate the embedding and is a very simple wrapper on the model:
aiEstimate
to generate the time estimate.
This function also wraps a model, this time a conversation model rather than an embedding model. And it icludes the similar tasks in the promopt, so the model will
generate similar estimates:
todoSchema
and tenantSchema
.
We defined these in src/db/schema.ts
, and these are the objects that Drizzle uses to interact with the database. It is an object that maps to the tables in our database.
Drizzle can also generate migration files from these objects, and we can use them to create the tables in the database.