“Software and cathedrals are much the same; first we build them, then we pray.” --Sam Redwine
Made with ❤️ by Jay Mathis
Intellibrix is a software component framework that organizes functionality into Structures
and Bricks
.
-
A
Brick
is a software component that provides a specific functionality. -
A
Structure
is a collection ofBricks
that can coordinate between them. -
A
Brick
can interact with its parentStructure
viathis.structure
-
A
Structure
can enumerate and interact with its childBricks
viathis.bricks
-
A
Brick
can contain manyPrograms
that provide different related functionality. -
A
Program
containsSteps
that are executed in sequence to perform a specific task. -
A
Step
containsActions
that are executed in sequence to act upon, process, and transform the data. -
A
Brick
can also containTasks
that are scheduled to run at a specific time or interval. -
A
Database
can be attached to aBrick
to provide database capabilities. -
An
Intelligence
can be attached to aBrick
to provide AI or other custom processing capabilities. -
Both
Bricks
andStructures
have an EventEmitter that can be used to emit and listen for events. -
You aren't limited to using
Programs
,Databases
, andIntelligence
- extend a brick and make it work however you want!
npm install --save intellibrix # or your package manager's equivalent
import { Brick, Intelligence, Structure } from 'intellibrix'
const structure = new Structure() // We add our bricks to this (optional)
structure.events.on('add', ({ brick, structure }) => { // Listen for bricks being added to the structure (optional)
console.log(`Added brick ${brick.id} to ${structure.id}`)
})
const intelligence = new Intelligence({ // Define our intelligence
service: 'openai', // default
model: 'gpt-3.5-turbo', // default
system: 'Respond to every question in the style of Shakespeare', // optional
key: 'OPENAI_API_KEY' // replace with your API key
})
const brick = new Brick({ intelligence }) // Create the brick, passing our intelligence
structure.add(brick) // Add the brick to the structure (optional)
// This is the core feature of Bricks, they're programmable
brick.program({ // Define a program on our brick
name: 'poem', // Program name
description: 'Generate a poem', // Program description
steps: [ // One or many steps
{
name: 'step_1', // Step name
description: 'Fetch a response from the OpenAI API', // Step description
actions: [ // One or many actions
{
name: 'fetch_poem', // Action name
description: 'Fetch a response from the OpenAI API', // Action description
method: async function ({ topic }) { // This is the function that will be executed for this action
return await this.ai.ask(`Write a poem about ${topic}`) // Ask the AI to answer our question
}
}
]
}
]
})
const { text } = await brick.run('poem', { topic: 'TypeScript' })
console.log(text)
// You can also access brick.ai.ask directly
// const { text } = await brick.ai.ask('Write a poem about TypeScript')
// console.log(text)
import { Brick, Structure } from 'intellibrix'
const structure = new Structure()
const brick = new Brick()
structure.add(brick)
// This program will return the result of a multi-step calculation with multiple actions
// This is intentionally overcomplicated to show how to chain actions and steps
// name and description are optional, but can be used for easier analysis and debugging later
brick.program({
name: 'calculate',
description: 'Multi-step calculation',
steps: [
{
name: 'Step 1',
description: 'Add the numbers, then multiply by a random number between 0 and 1',
actions: [
{
name: 'sum',
description: 'Add two numbers',
method: async function ({ x, y }) {
return x + y
}
},
{
name: 'multiply',
description: 'Multiply a number by a random number between 0 and 1',
method: async function (payload) {
return payload * Math.random()
}
}
]
},
{
name: 'Step 2',
description: 'Add a random integer between 10 and 50 to the result and return the floor',
actions: [
{
name: 'add_random',
description: 'Add a random integer between 10 and 50 to the result',
method: async function (payload) {
return payload + Math.floor(Math.random() * (50 - 10 + 1) + 10)
}
},
{
name: 'floor',
description: 'Round the result down to the nearest integer',
method: async function (payload) {
return Math.floor(payload)
}
}
]
}
]
})
const result = await brick.run('calculate', { x: 1, y: 2 })
console.log(result)
import { Brick, Database, Structure } from 'intellibrix'
const structure = new Structure()
const keyValueDatabase = new Database()
const sqlDatabase = new Database({ service: 'sql', uri: 'sqlite::memory:' })
const keyValueBrick = new Brick({ database: keyValueDatabase })
const sqlBrick = new Brick({ database: sqlDatabase })
structure.add(keyValueBrick)
structure.add(sqlBrick)
keyValueBrick.db.interface.set('foo', 'bar')
const foo = await keyValueBrick.db.interface.get('foo')
console.log(foo)
// Also access all of Sequelize's power directly with sqlBrick.db.sequelize
await sqlBrick.db.interface.query('CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `rank` TEXT NOT NULL)')
await sqlBrick.db.interface.query('INSERT INTO `users` (`name`, `rank`) VALUES (?, ?)', ['Belisarius Cawl', 'Archmagos'])
const results = await sqlBrick.db.interface.query('SELECT * FROM `users`')
console.log(results)
import { Brick, Intelligence } from 'intellibrix'
const intelligence = new Intelligence({
service: 'custom',
method: async function ({ name }) {
// You would likely want to do something more interesting here
return `Hello ${name}`
}
})
const brick = new Brick({ intelligence })
const response = await brick.ai.ask({ name: 'Belisarius Cawl' })
console.log(response)
import { Brick } from 'intellibrix'
class MyBrick extends Brick {
constructor (options) {
super(options)
}
myMethod ({ topic }) {
return `${topic} has ${topic.length} characters`
}
}
const brick = new MyBrick({ name: 'My Brick' })
const lengthString = brick.myMethod({ topic: 'TypeScript' })
console.log(lengthString)
Intellibrix uses Pino for logging in both Bricks and Structures:
import { Brick, Structure } from 'intellibrix'
// To use raw JSON logs, pass { logJSON: true } to the Brick or Structure constructor
// Pass in pinoOptions to override the default transport and logging options
const structure = new Structure({ name: 'My Structure', logLevel: 'debug' })
const brick = new Brick({ name: 'My Brick', logLevel: 'silent' })
structure.add(brick) // Outputs: DEBUG (STRUCTURE:My Structure): Added Brick: My Brick
structure.log.info('This is an info message') // Outputs: INFO (STRUCTURE:My Structure): This is an info message
brick.log.fatal('This is a fatal message') // Outputs: Nothing because logLevel is set to silent
Intelligence is a class that provides AI or other custom processing capabilities to a Brick
. It can currently utilize the OpenAI API or a custom function to manually process data.
An Intelligence
has these methods:
ask('Tell me about the biggest event from 1938', context?)
- Ask the AI a question and return the response (prompt, context?)image('A shiny red apple', '1024x1024')
- Generate an image from a text prompt (prompt, size? ='256x256'
)
You may pass a context
array of the format [{ role: 'user' | 'assistant', content: string }]
to ask()
to provide context to the AI.
You may also access the openai
library directly with brick.ai.instance
.
An OpenAI Intelligence
supports the function-calling API (see test/function.test.ts
):
const intelligence = new Intelligence({
service: 'openai',
model: 'gpt-3.5-turbo-0613',
key: 'OPENAI_API_KEY',
functions: {
schema: [
{
name: 'return_answer',
description: 'Test function',
parameters: {
type: 'object',
required: ['answer', 'explanation'],
properties: {
answer: {
type: 'string',
description: 'The answer to the question'
},
explanation: {
type: 'string',
description: 'The explanation of the answer'
}
}
}
}
],
methods: {
return_answer: async (payload) => {
payload.processed = true // This just shows that you can modify the payload
return payload
}
}
}
})
const brick = new Brick({ intelligence })
const response = await brick.ai.ask('What is 42 + 42?')
console.log(response.functionResult)
Database is a class that can be attached to a Brick
to provide database capabilities. It currently supports in-memory key-value storage as well as various SQL servers via Sequelize.
A Database
has these methods, depending on the type of storage:
In-Memory Key-Value Storage:
set('foo', 'bar')
- Set a key to a valueget('foo')
- Get a value from a keydelete('foo')
- Delete a keydump()
- Dump the entire database to an objectload(data)
- Overwrite the entire database with an object
SQL Storage:
query('SELECT * FROM users WHERE name="?"', ['Fabius Bile'])
- Run a SQL query
Both Structures
and Bricks
have EventEmitters that can be used to emit and listen for events.
const structure = new Structure()
const brick = new Brick()
structure.events.on('foo', () => console.log('foo'))
brick.events.on('bar', () => console.log('bar'))
structure.events.emit('foo')
brick.events.emit('bar')
Bricks can be assigned scheduled tasks to perform at a certain time or interval. Both cron syntax and Date objects are supported.
Scheduled tasks are powered by node-cron.
const brick = new Brick()
const cronTask = brick.schedule({
name: 'New Year Cron',
description: 'This uses cron syntax and will run on January 1st at 12:00 AM',
schedule: '0 0 1 1 *',
start: true, // Start the task immediately, otherwise you must call task.cronjob.start() manually - this does not execute the task
method: () => console.log('Happy New Year!')
})
const dateTask = brick.schedule({
name: 'New Year Date',
description: 'This uses a Date object and will run on January 1st at 12:00 AM',
schedule: new Date('January 1, 2022 00:00:00'),
start: false,
method: () => console.log('Happy New Year!')
})
brick.unschedule('New Year Cron') // Remove a task from the brick's schedule
dateTask.cronjob.start() // Since we didn't use the start option, we must start the task manually or it won't run when the date is reached
The core intellibrix
package comes with some bricks that provide basic functionality and serve as examples.
You can also find more complex Bricks under the @intellibrix
scope on NPM.
If you would like to contribute a Brick, you may self-publish or if you'd like your Brick to be under the @intellibrix
scope, contact dev@intellibrix.dev
.
You can instantiate these like any other Brick, passing in the appropriate Intelligence, Database, and options.
- Express Web Server
- A brick that provides a basic Express web server
import ExpressBrick from 'intellibrix/bricks/express'
const routes = [{ method: 'get', path: '/', handler: (req, res) => res.send('Hello World!') }]
const brick = new ExpressBrick({ port: 3000, routes })
- Internationalization
- A brick that provides internationalization support powered by i18next
import I18nBrick from 'intellibrix/bricks/i18n'
const brick = new I18nBrick({ defaultLanguage: 'en', resources: { en: { translation: { hello: 'Hello World!' } } } })
console.log(brick.t('hello'))
- Terminal
- A brick that provides a terminal interface powered by TerminalKit
- TerminalKit allows for rich UI elements such as progress bars, tables, and more
import TerminalBrick from 'intellibrix/bricks/terminal'
const brick = new TerminalBrick()
brick.print('What is your name? ')
const name = await brick.input()
- Question and Answer
- Simple question and answer functionality powered by OpenAI
import QABrick from 'intellibrix/bricks/qa'
const qa = new QABrick({ intelligence })
const { text } = await qa.run('qa', { question: 'What is the meaning of life?' })
- You may pass a
context
array to the payload of this format to provide conversation history to the AI
git clone https://github.com/intellibrix/intellibrix.git # or your fork
cd intellibrix
npm install # or your package manager's equivalent
Create an env.js
file in the .jest
folder containing the following:
process.env.USE_OPENAI=true // Set to false to disable OpenAI tests
process.env.OPENAI_API_KEY="sk-YOUROPENAIAPIKEY" // Your OpenAI API key
process.env.DATABASE_URI="postgresql://user:pass@host:port/dbname" // Your database URI
Then run:
npm run test:watch