feat(core): enhance addVerb functionality with auto-creation of missing nouns

- Added `autoCreateMissingNouns` and `missingNounMetadata` options to the `addVerb` method in `brainyData.ts`, enabling automatic creation of missing source or target nouns.
- Improved error handling and logging for auto-creation failures, ensuring better feedback during runtime.
- Reformatted existing code for storage options validation to improve readability and maintain consistency.

**Purpose**: Simplify the addition of relationships by automating the process for non-existing nouns and enhance developer experience with better error handling and logging.
This commit is contained in:
David Snelling 2025-07-22 16:01:48 -07:00
parent 4abf8070f2
commit c666c6780b
2 changed files with 388 additions and 301 deletions

View file

@ -62,14 +62,15 @@ GitHub Pages that showcases Brainy's main features.
npm install @soulcraft/brainy
```
TensorFlow.js packages are included as bundled dependencies and will be automatically installed without any additional configuration.
TensorFlow.js packages are included as bundled dependencies and will be automatically installed without any additional
configuration.
## 🏁 Quick Start
Brainy uses a unified build that automatically adapts to your environment (Node.js, browser, or serverless):
```typescript
import { BrainyData, NounType, VerbType } from '@soulcraft/brainy'
import {BrainyData, NounType, VerbType} from '@soulcraft/brainy'
// Create and initialize the database
const db = new BrainyData()
@ -101,13 +102,15 @@ await db.addVerb(catId, dogId, {
```typescript
// Standard import - automatically adapts to any environment
import { BrainyData } from '@soulcraft/brainy'
import {BrainyData} from '@soulcraft/brainy'
// Minified version for production
import { BrainyData } from '@soulcraft/brainy/min'
import {BrainyData} from '@soulcraft/brainy/min'
```
> **Note**: The CLI functionality is available as a separate package `@soulcraft/brainy-cli` to reduce the bundle size of the main package. Install it globally with `npm install -g @soulcraft/brainy-cli` to use the command-line interface.
> **Note**: The CLI functionality is available as a separate package `@soulcraft/brainy-cli` to reduce the bundle size
> of the main package. Install it globally with `npm install -g @soulcraft/brainy-cli` to use the command-line
> interface.
### Browser Usage
@ -115,7 +118,7 @@ import { BrainyData } from '@soulcraft/brainy/min'
<script type="module">
// Use local files instead of CDN
import { BrainyData } from './dist/unified.js'
import {BrainyData} from './dist/unified.js'
// Or minified version
// import { BrainyData } from './dist/unified.min.js'
@ -277,13 +280,13 @@ The pipeline runs automatically when you:
```typescript
// Add data (runs embedding → indexing → storage)
const id = await db.add("Your text data here", { metadata })
const id = await db.add("Your text data here", {metadata})
// Search (runs embedding → similarity search)
const results = await db.searchText("Your query here", 5)
// Connect entities (runs graph construction → storage)
await db.addVerb(sourceId, targetId, { verb: VerbType.RelatedTo })
await db.addVerb(sourceId, targetId, {verb: VerbType.RelatedTo})
```
Using the CLI:
@ -408,7 +411,8 @@ brainy visualize --root <id> --depth 3
### Using the CLI in Your Code
The CLI functionality is available as a separate package `@soulcraft/brainy-cli`. If you need CLI functionality in your application, install the CLI package:
The CLI functionality is available as a separate package `@soulcraft/brainy-cli`. If you need CLI functionality in your
application, install the CLI package:
```bash
npm install @soulcraft/brainy-cli
@ -466,7 +470,7 @@ const status = await db.status()
const backupData = await db.backup()
// Restore data into the database
const restoreResult = await db.restore(backupData, { clearExisting: true })
const restoreResult = await db.restore(backupData, {clearExisting: true})
```
### Working with Nouns (Entities)
@ -482,11 +486,11 @@ const id = await db.add(textOrVector, {
const ids = await db.addBatch([
{
vectorOrData: "First item to add",
metadata: { noun: NounType.Thing, category: 'example' }
metadata: {noun: NounType.Thing, category: 'example'}
},
{
vectorOrData: "Second item to add",
metadata: { noun: NounType.Thing, category: 'example' }
metadata: {noun: NounType.Thing, category: 'example'}
},
// More items...
], {
@ -524,6 +528,19 @@ await db.addVerb(sourceId, targetId, {
// other metadata...
})
// Add a relationship with auto-creation of missing nouns
// This is useful when the target noun might not exist yet
await db.addVerb(sourceId, targetId, {
verb: VerbType.RelatedTo,
// Enable auto-creation of missing nouns
autoCreateMissingNouns: true,
// Optional metadata for auto-created nouns
missingNounMetadata: {
noun: NounType.Concept,
description: 'Auto-created noun'
}
})
// Get all relationships
const verbs = await db.getAllVerbs()
@ -603,7 +620,7 @@ Brainy includes comprehensive multithreading support to improve performance acro
7. **Automatic Environment Detection**: Adapts to browser (Web Workers) and Node.js (Worker Threads) environments
```typescript
import { BrainyData, euclideanDistance } from '@soulcraft/brainy'
import {BrainyData, euclideanDistance} from '@soulcraft/brainy'
// Configure with custom options
const db = new BrainyData({
@ -652,7 +669,7 @@ hybrid approach:
3. **Memory-Efficient Indexing** - Optimizes memory usage for large-scale vector collections
```typescript
import { BrainyData } from '@soulcraft/brainy'
import {BrainyData} from '@soulcraft/brainy'
// Configure with optimized HNSW index for large datasets
const db = new BrainyData({
@ -838,7 +855,7 @@ const memoryAug = createMemoryAugmentation({
// Your implementation here
return {
success: true,
data: { example: 'data', key }
data: {example: 'data', key}
}
}
})
@ -909,7 +926,8 @@ The simplified augmentation system provides:
#### WebSocket Augmentation Types
Brainy exports several WebSocket augmentation types that can be used by augmentation creators to add WebSocket capabilities to their augmentations:
Brainy exports several WebSocket augmentation types that can be used by augmentation creators to add WebSocket
capabilities to their augmentations:
```typescript
import {
@ -936,7 +954,7 @@ const mySenseAug = createSenseAugmentation({
// Implementation
return {
success: true,
data: { nouns: [], verbs: [] }
data: {nouns: [], verbs: []}
}
}
}) as IWebSocketSenseAugmentation
@ -970,7 +988,8 @@ await mySenseAug.processRawData('data', 'text')
await mySenseAug.connectWebSocket('wss://example.com')
```
These WebSocket augmentation types combine the base augmentation interfaces with the `IWebSocketSupport` interface, providing type safety and autocompletion for augmentations with WebSocket capabilities.
These WebSocket augmentation types combine the base augmentation interfaces with the `IWebSocketSupport` interface,
providing type safety and autocompletion for augmentations with WebSocket capabilities.
### Model Control Protocol (MCP)
@ -999,7 +1018,7 @@ everywhere.
Brainy automatically detects the environment it's running in:
```typescript
import { environment } from '@soulcraft/brainy'
import {environment} from '@soulcraft/brainy'
// Check which environment we're running in
console.log(`Running in ${
@ -1034,7 +1053,6 @@ Works in all modern browsers:
For browsers without OPFS support, falls back to in-memory storage.
## Related Projects
- **[Cartographer](https://github.com/sodal-project/cartographer)** - A companion project that provides standardized
@ -1098,7 +1116,7 @@ pipeline.register(wsConduit)
// Replace the example URL below with your actual WebSocket server URL
const connectionResult = await pipeline.executeConduitPipeline(
'establishConnection',
['wss://example-websocket-server.com/brainy-sync', { protocols: 'brainy-sync' }]
['wss://example-websocket-server.com/brainy-sync', {protocols: 'brainy-sync'}]
)
if (connectionResult[0] && (await connectionResult[0]).success) {
@ -1107,7 +1125,7 @@ if (connectionResult[0] && (await connectionResult[0]).success) {
// Read data from the remote instance
const readResult = await pipeline.executeConduitPipeline(
'readData',
[{ connectionId: connection.connectionId, query: { type: 'getAllNouns' } }]
[{connectionId: connection.connectionId, query: {type: 'getAllNouns'}}]
)
// Process and add the received data to the local instance
@ -1158,7 +1176,7 @@ const connectionResult = await pipeline.executeConduitPipeline(
{
signalServerUrl: 'wss://example-signal-server.com', // Replace with your signal server
localPeerId: 'my-local-peer-id', // Replace with your local peer ID
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // Public STUN server
iceServers: [{urls: 'stun:stun.l.google.com:19302'}] // Public STUN server
}
]
)
@ -1177,7 +1195,7 @@ if (connectionResult[0] && (await connectionResult[0]).success) {
})
// When adding new data locally, also send to the peer
const nounId = await db.add("New data to sync", { noun: "Thing" })
const nounId = await db.add("New data to sync", {noun: "Thing"})
// Send the new noun to the peer
await pipeline.executeConduitPipeline(
@ -1203,7 +1221,7 @@ Brainy supports searching a server-hosted instance from a browser, storing resul
searches against the local instance:
```typescript
import { BrainyData } from '@soulcraft/brainy'
import {BrainyData} from '@soulcraft/brainy'
// Create and initialize the database with remote server configuration
// Replace the example URL below with your actual Brainy server URL
@ -1223,13 +1241,13 @@ if (!db.isConnectedToRemoteServer()) {
}
// Search the remote server (results are stored locally)
const remoteResults = await db.searchText('machine learning', 5, { searchMode: 'remote' })
const remoteResults = await db.searchText('machine learning', 5, {searchMode: 'remote'})
// Search the local database (includes previously stored results)
const localResults = await db.searchText('machine learning', 5, { searchMode: 'local' })
const localResults = await db.searchText('machine learning', 5, {searchMode: 'local'})
// Perform a combined search (local first, then remote if needed)
const combinedResults = await db.searchText('neural networks', 5, { searchMode: 'combined' })
const combinedResults = await db.searchText('neural networks', 5, {searchMode: 'combined'})
// Add data to both local and remote instances
const id = await db.addToBoth('Deep learning is a subset of machine learning', {

View file

@ -341,7 +341,7 @@ export class BrainyData<T = any> implements BrainyDataInterface<T> {
// All required fields are present, keep s3Storage as is
} else {
// Missing required fields, remove s3Storage to avoid type errors
const { s3Storage, ...rest } = storageOptions
const {s3Storage, ...rest} = storageOptions
storageOptions = rest
console.warn('Ignoring s3Storage configuration due to missing required fields')
}
@ -1253,6 +1253,22 @@ export class BrainyData<T = any> implements BrainyDataInterface<T> {
/**
* Add a verb between two nouns
* If metadata is provided and vector is not, the metadata will be vectorized using the embedding function
*
* @param sourceId ID of the source noun
* @param targetId ID of the target noun
* @param vector Optional vector for the verb
* @param options Additional options:
* - type: Type of the verb
* - weight: Weight of the verb
* - metadata: Metadata for the verb
* - forceEmbed: Force using the embedding function for metadata even if vector is provided
* - id: Optional ID to use instead of generating a new one
* - autoCreateMissingNouns: Automatically create missing nouns if they don't exist
* - missingNounMetadata: Metadata to use when auto-creating missing nouns
*
* @returns The ID of the added verb
*
* @throws Error if source or target nouns don't exist and autoCreateMissingNouns is false or auto-creation fails
*/
public async addVerb(
sourceId: string,
@ -1264,6 +1280,8 @@ export class BrainyData<T = any> implements BrainyDataInterface<T> {
metadata?: any
forceEmbed?: boolean // Force using the embedding function for metadata even if vector is provided
id?: string // Optional ID to use instead of generating a new one
autoCreateMissingNouns?: boolean // Automatically create missing nouns
missingNounMetadata?: any // Metadata to use when auto-creating missing nouns
} = {}
): Promise<string> {
await this.ensureInitialized()
@ -1273,8 +1291,59 @@ export class BrainyData<T = any> implements BrainyDataInterface<T> {
try {
// Check if source and target nouns exist
const sourceNoun = this.index.getNouns().get(sourceId)
const targetNoun = this.index.getNouns().get(targetId)
let sourceNoun = this.index.getNouns().get(sourceId)
let targetNoun = this.index.getNouns().get(targetId)
// Auto-create missing nouns if option is enabled
if (!sourceNoun && options.autoCreateMissingNouns) {
try {
// Create a placeholder vector for the missing noun
const placeholderVector = new Array(this._dimensions).fill(0)
// Add metadata if provided
const metadata = options.missingNounMetadata || {
autoCreated: true,
createdAt: new Date().toISOString(),
noun: NounType.Concept
}
// Add the missing noun
await this.add(placeholderVector, metadata, {id: sourceId})
// Get the newly created noun
sourceNoun = this.index.getNouns().get(sourceId)
console.warn(`Auto-created missing source noun with ID ${sourceId}`)
} catch (createError) {
console.error(`Failed to auto-create source noun with ID ${sourceId}:`, createError)
throw new Error(`Failed to auto-create source noun with ID ${sourceId}: ${createError}`)
}
}
if (!targetNoun && options.autoCreateMissingNouns) {
try {
// Create a placeholder vector for the missing noun
const placeholderVector = new Array(this._dimensions).fill(0)
// Add metadata if provided
const metadata = options.missingNounMetadata || {
autoCreated: true,
createdAt: new Date().toISOString(),
noun: NounType.Concept
}
// Add the missing noun
await this.add(placeholderVector, metadata, {id: targetId})
// Get the newly created noun
targetNoun = this.index.getNouns().get(targetId)
console.warn(`Auto-created missing target noun with ID ${targetId}`)
} catch (createError) {
console.error(`Failed to auto-create target noun with ID ${targetId}:`, createError)
throw new Error(`Failed to auto-create target noun with ID ${targetId}: ${createError}`)
}
}
if (!sourceNoun) {
throw new Error(`Source noun with ID ${sourceId} not found`)