fix: metadata batch reading from correct directory
Fixed bug where getMetadataBatch() was reading from wrong directory: - FileSystemStorage: Changed to use getNounMetadata() instead of getMetadata() - OPFSStorage: Changed to use getNounMetadata() instead of getMetadata() - MetadataIndex fallback: Fixed to use getNounMetadata() - Added getNounMetadata() to StorageAdapter interface This resolves 0% success rate during metadata index rebuild. Also added comprehensive API documentation for return values and data field behavior.
This commit is contained in:
parent
b066fbd333
commit
32f5ac6fee
6 changed files with 179 additions and 18 deletions
152
docs/api-returns.md
Normal file
152
docs/api-returns.md
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
# Brainy API Return Values
|
||||||
|
|
||||||
|
## Core Operations
|
||||||
|
|
||||||
|
### `brain.add()` - Adding Entities
|
||||||
|
|
||||||
|
**Returns:** `Promise<string>` - The ID of the created entity
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct usage - add() returns the ID string directly
|
||||||
|
const id = await brain.add({
|
||||||
|
type: 'document',
|
||||||
|
data: 'My document content'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Created entity with ID:', id)
|
||||||
|
|
||||||
|
// Use the ID to create relationships
|
||||||
|
await brain.relate({
|
||||||
|
from: id,
|
||||||
|
to: anotherId,
|
||||||
|
type: 'references'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Incorrect - trying to access .id property
|
||||||
|
const result = await brain.add({
|
||||||
|
type: 'document',
|
||||||
|
data: 'My content'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(result.id) // ❌ undefined - result IS the ID, not an object!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting the Full Entity
|
||||||
|
|
||||||
|
If you need the full entity object after creation, use `brain.get()`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add entity and get its ID
|
||||||
|
const id = await brain.add({
|
||||||
|
type: 'document',
|
||||||
|
data: 'My content',
|
||||||
|
metadata: { label: 'Important Doc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get the full entity
|
||||||
|
const entity = await brain.get(id)
|
||||||
|
console.log(entity.id) // The ID
|
||||||
|
console.log(entity.type) // 'document'
|
||||||
|
console.log(entity.data) // 'My content'
|
||||||
|
console.log(entity.metadata) // { label: 'Important Doc', ... }
|
||||||
|
console.log(entity.vector) // The embedding vector
|
||||||
|
console.log(entity.createdAt) // Timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
### `brain.find()` - Finding Entities
|
||||||
|
|
||||||
|
**Returns:** `Promise<Entity[]>` - Array of full entity objects
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const entities = await brain.find({
|
||||||
|
query: 'machine learning',
|
||||||
|
limit: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
// Each entity has full information
|
||||||
|
for (const entity of entities) {
|
||||||
|
console.log(entity.id)
|
||||||
|
console.log(entity.type)
|
||||||
|
console.log(entity.data)
|
||||||
|
console.log(entity.metadata)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `brain.relate()` - Creating Relationships
|
||||||
|
|
||||||
|
**Returns:** `Promise<string>` - The ID of the created relationship
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const relationId = await brain.relate({
|
||||||
|
from: entityId1,
|
||||||
|
to: entityId2,
|
||||||
|
type: 'references'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Created relationship with ID:', relationId)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Field Behavior
|
||||||
|
|
||||||
|
### String Data - Used for Embeddings
|
||||||
|
|
||||||
|
When `data` is a string, it's used to generate embeddings for semantic search:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await brain.add({
|
||||||
|
type: 'document',
|
||||||
|
data: 'This text will be converted to an embedding vector',
|
||||||
|
metadata: {
|
||||||
|
title: 'My Document',
|
||||||
|
year: 2024
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Object Data - Structured Information
|
||||||
|
|
||||||
|
When `data` is an object, it's treated as structured data:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await brain.add({
|
||||||
|
type: 'product',
|
||||||
|
data: {
|
||||||
|
name: 'Widget',
|
||||||
|
price: 29.99,
|
||||||
|
category: 'Tools'
|
||||||
|
},
|
||||||
|
vector: precomputedVector // Must provide vector when using object data
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** If you provide object data without a `vector`, you must include a string somewhere for embedding generation, or the operation will fail.
|
||||||
|
|
||||||
|
### Metadata vs Data
|
||||||
|
|
||||||
|
- **`data`**: Primary content - used for embeddings (if string) or stored as structured data (if object)
|
||||||
|
- **`metadata`**: Auxiliary information - always stored as structured data, used for filtering
|
||||||
|
|
||||||
|
**Best practice for labels:**
|
||||||
|
```typescript
|
||||||
|
await brain.add({
|
||||||
|
type: 'document',
|
||||||
|
data: 'The full text content of the document...', // For semantic search
|
||||||
|
metadata: {
|
||||||
|
label: 'Quick Reference Label', // For display
|
||||||
|
author: 'John Doe',
|
||||||
|
category: 'Technical'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Method | Returns | Contains |
|
||||||
|
|--------|---------|----------|
|
||||||
|
| `brain.add()` | `string` | The ID of the created entity |
|
||||||
|
| `brain.get()` | `Entity \| null` | Full entity object with all fields |
|
||||||
|
| `brain.find()` | `Entity[]` | Array of full entity objects |
|
||||||
|
| `brain.relate()` | `string` | The ID of the created relationship |
|
||||||
|
| `brain.getRelations()` | `Relation[]` | Array of relationship objects |
|
||||||
|
|
@ -476,6 +476,13 @@ export interface StorageAdapter {
|
||||||
*/
|
*/
|
||||||
getMetadataBatch?(ids: string[]): Promise<Map<string, any>>
|
getMetadataBatch?(ids: string[]): Promise<Map<string, any>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get noun metadata from storage
|
||||||
|
* @param id The ID of the noun
|
||||||
|
* @returns Promise that resolves to the metadata or null if not found
|
||||||
|
*/
|
||||||
|
getNounMetadata(id: string): Promise<any | null>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save verb metadata to storage
|
* Save verb metadata to storage
|
||||||
* @param id The ID of the verb
|
* @param id The ID of the verb
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ export abstract class BaseStorageAdapter implements StorageAdapter {
|
||||||
|
|
||||||
abstract getMetadata(id: string): Promise<any | null>
|
abstract getMetadata(id: string): Promise<any | null>
|
||||||
|
|
||||||
|
abstract getNounMetadata(id: string): Promise<any | null>
|
||||||
|
|
||||||
abstract saveVerbMetadata(id: string, metadata: any): Promise<void>
|
abstract saveVerbMetadata(id: string, metadata: any): Promise<void>
|
||||||
|
|
||||||
abstract getVerbMetadata(id: string): Promise<any | null>
|
abstract getVerbMetadata(id: string): Promise<any | null>
|
||||||
|
|
|
||||||
|
|
@ -568,33 +568,33 @@ export class FileSystemStorage extends BaseStorage {
|
||||||
|
|
||||||
const results = new Map<string, any>()
|
const results = new Map<string, any>()
|
||||||
const batchSize = 10 // Process 10 files at a time
|
const batchSize = 10 // Process 10 files at a time
|
||||||
|
|
||||||
// Process in batches to avoid overwhelming the filesystem
|
// Process in batches to avoid overwhelming the filesystem
|
||||||
for (let i = 0; i < ids.length; i += batchSize) {
|
for (let i = 0; i < ids.length; i += batchSize) {
|
||||||
const batch = ids.slice(i, i + batchSize)
|
const batch = ids.slice(i, i + batchSize)
|
||||||
|
|
||||||
const batchPromises = batch.map(async (id) => {
|
const batchPromises = batch.map(async (id) => {
|
||||||
try {
|
try {
|
||||||
const metadata = await this.getMetadata(id)
|
const metadata = await this.getNounMetadata(id)
|
||||||
return { id, metadata }
|
return { id, metadata }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug(`Failed to read metadata for ${id}:`, error)
|
console.debug(`Failed to read metadata for ${id}:`, error)
|
||||||
return { id, metadata: null }
|
return { id, metadata: null }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises)
|
const batchResults = await Promise.all(batchPromises)
|
||||||
|
|
||||||
for (const { id, metadata } of batchResults) {
|
for (const { id, metadata } of batchResults) {
|
||||||
if (metadata !== null) {
|
if (metadata !== null) {
|
||||||
results.set(id, metadata)
|
results.set(id, metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small yield between batches
|
// Small yield between batches
|
||||||
await new Promise(resolve => setImmediate(resolve))
|
await new Promise(resolve => setImmediate(resolve))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -633,33 +633,33 @@ export class OPFSStorage extends BaseStorage {
|
||||||
|
|
||||||
const results = new Map<string, any>()
|
const results = new Map<string, any>()
|
||||||
const batchSize = 10 // Process 10 files at a time
|
const batchSize = 10 // Process 10 files at a time
|
||||||
|
|
||||||
// Process in batches to avoid overwhelming OPFS
|
// Process in batches to avoid overwhelming OPFS
|
||||||
for (let i = 0; i < ids.length; i += batchSize) {
|
for (let i = 0; i < ids.length; i += batchSize) {
|
||||||
const batch = ids.slice(i, i + batchSize)
|
const batch = ids.slice(i, i + batchSize)
|
||||||
|
|
||||||
const batchPromises = batch.map(async (id) => {
|
const batchPromises = batch.map(async (id) => {
|
||||||
try {
|
try {
|
||||||
const metadata = await this.getMetadata(id)
|
const metadata = await this.getNounMetadata(id)
|
||||||
return { id, metadata }
|
return { id, metadata }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug(`Failed to read metadata for ${id}:`, error)
|
console.debug(`Failed to read metadata for ${id}:`, error)
|
||||||
return { id, metadata: null }
|
return { id, metadata: null }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises)
|
const batchResults = await Promise.all(batchPromises)
|
||||||
|
|
||||||
for (const { id, metadata } of batchResults) {
|
for (const { id, metadata } of batchResults) {
|
||||||
if (metadata !== null) {
|
if (metadata !== null) {
|
||||||
results.set(id, metadata)
|
results.set(id, metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small yield between batches
|
// Small yield between batches
|
||||||
await new Promise(resolve => setImmediate(resolve))
|
await new Promise(resolve => setImmediate(resolve))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1618,26 +1618,26 @@ export class MetadataIndexManager {
|
||||||
prodLog.warn(`⚠️ FALLBACK: Storage adapter missing getMetadataBatch - using individual calls with concurrency limit`)
|
prodLog.warn(`⚠️ FALLBACK: Storage adapter missing getMetadataBatch - using individual calls with concurrency limit`)
|
||||||
metadataBatch = new Map()
|
metadataBatch = new Map()
|
||||||
const CONCURRENCY_LIMIT = 3 // Very conservative limit
|
const CONCURRENCY_LIMIT = 3 // Very conservative limit
|
||||||
|
|
||||||
for (let i = 0; i < nounIds.length; i += CONCURRENCY_LIMIT) {
|
for (let i = 0; i < nounIds.length; i += CONCURRENCY_LIMIT) {
|
||||||
const batch = nounIds.slice(i, i + CONCURRENCY_LIMIT)
|
const batch = nounIds.slice(i, i + CONCURRENCY_LIMIT)
|
||||||
const batchPromises = batch.map(async (id) => {
|
const batchPromises = batch.map(async (id) => {
|
||||||
try {
|
try {
|
||||||
const metadata = await this.storage.getMetadata(id)
|
const metadata = await this.storage.getNounMetadata(id)
|
||||||
return { id, metadata }
|
return { id, metadata }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
prodLog.debug(`Failed to read metadata for ${id}:`, error)
|
prodLog.debug(`Failed to read metadata for ${id}:`, error)
|
||||||
return { id, metadata: null }
|
return { id, metadata: null }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises)
|
const batchResults = await Promise.all(batchPromises)
|
||||||
for (const { id, metadata } of batchResults) {
|
for (const { id, metadata } of batchResults) {
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
metadataBatch.set(id, metadata)
|
metadataBatch.set(id, metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield between batches to prevent socket exhaustion
|
// Yield between batches to prevent socket exhaustion
|
||||||
await this.yieldToEventLoop()
|
await this.yieldToEventLoop()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue