2025-10-09 16:33:08 -07:00
|
|
|
/**
|
|
|
|
|
* Natural Language Processor Tests
|
|
|
|
|
* Tests NLP features including query parsing, entity extraction, and sentiment analysis
|
|
|
|
|
*/
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
|
|
|
import { Brainy } from '../../../src/brainy'
|
|
|
|
|
import { NaturalLanguageProcessor } from '../../../src/neural/naturalLanguageProcessor'
|
|
|
|
|
import { NounType } from '../../../src/types/graphTypes'
|
|
|
|
|
|
2025-10-09 16:33:08 -07:00
|
|
|
describe('NaturalLanguageProcessor', () => {
|
2025-09-11 16:23:32 -07:00
|
|
|
let brain: Brainy<any>
|
|
|
|
|
let nlp: NaturalLanguageProcessor
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
nlp = new NaturalLanguageProcessor(brain)
|
|
|
|
|
|
|
|
|
|
// Create a rich test dataset for NLP to work with
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'John Smith is a senior software engineer at Google working on machine learning',
|
|
|
|
|
type: NounType.Person,
|
|
|
|
|
metadata: {
|
|
|
|
|
name: 'John Smith',
|
|
|
|
|
role: 'engineer',
|
|
|
|
|
level: 'senior',
|
|
|
|
|
company: 'Google',
|
|
|
|
|
skills: ['JavaScript', 'Python', 'Machine Learning']
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'Machine learning research paper on neural networks published in 2024',
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: {
|
|
|
|
|
title: 'Advances in Neural Networks',
|
|
|
|
|
category: 'research',
|
|
|
|
|
year: 2024,
|
|
|
|
|
topics: ['AI', 'neural networks', 'deep learning']
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'TechCorp headquarters located in San Francisco California',
|
|
|
|
|
type: NounType.Location,
|
|
|
|
|
metadata: {
|
|
|
|
|
company: 'TechCorp',
|
|
|
|
|
city: 'San Francisco',
|
|
|
|
|
state: 'CA',
|
|
|
|
|
type: 'headquarters'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'Product launch event scheduled for December 2024',
|
|
|
|
|
type: NounType.Event,
|
|
|
|
|
metadata: {
|
|
|
|
|
eventType: 'launch',
|
|
|
|
|
date: '2024-12-01',
|
|
|
|
|
status: 'scheduled'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'Python programming language used for data science and machine learning',
|
|
|
|
|
type: NounType.Concept,
|
|
|
|
|
metadata: {
|
|
|
|
|
category: 'programming',
|
|
|
|
|
uses: ['data science', 'machine learning', 'web development']
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('processNaturalQuery - Core Functionality', () => {
|
|
|
|
|
it('should handle questions about entities', async () => {
|
|
|
|
|
const query = 'What is John Smith working on?'
|
|
|
|
|
const result = await nlp.processNaturalQuery(query)
|
2025-10-09 16:58:01 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
// Should search for John Smith
|
|
|
|
|
const hasSearch = result.similar || result.like || result.where
|
|
|
|
|
expect(hasSearch).toBeDefined()
|
|
|
|
|
})
|
2025-10-09 16:58:01 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
it('should extract location-based queries', async () => {
|
|
|
|
|
const query = 'Find companies in San Francisco'
|
|
|
|
|
const result = await nlp.processNaturalQuery(query)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(result).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
// Should have search criteria (location might be in where clause)
|
|
|
|
|
expect(result.like || result.where).toBeDefined()
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
2025-10-09 16:58:01 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
it('should extract limit from queries', async () => {
|
|
|
|
|
const query = 'Show me the top 5 machine learning papers'
|
|
|
|
|
const result = await nlp.processNaturalQuery(query)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(result).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
if (result.limit) {
|
|
|
|
|
const limit = typeof result.limit === 'string' ? parseInt(result.limit) : result.limit
|
|
|
|
|
expect(limit).toBeGreaterThan(0)
|
|
|
|
|
expect(limit).toBeLessThanOrEqual(10) // Should extract a reasonable limit
|
|
|
|
|
}
|
|
|
|
|
// Limit extraction is optional feature
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('extract - Entity and Information Extraction', () => {
|
|
|
|
|
it('should extract entities from text', async () => {
|
|
|
|
|
const text = 'John Smith works at Google on machine learning projects'
|
|
|
|
|
const extraction = await nlp.extract(text)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(extraction).toBeDefined()
|
|
|
|
|
expect(Array.isArray(extraction)).toBe(true)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2026-05-26 11:32:46 -07:00
|
|
|
// This unit suite runs with mock (zero-vector) embeddings, so the neural embedding
|
|
|
|
|
// signal can't fire here — only the array shape is asserted. Real, non-empty extraction
|
|
|
|
|
// is verified against actual embeddings in
|
|
|
|
|
// tests/integration/advanced-apis-regression.test.ts (BR-ADV-FEATURES-BUN). An empty
|
|
|
|
|
// result here reflects the mock embeddings, not expected production behaviour.
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should extract topics and concepts', async () => {
|
|
|
|
|
const text = 'This paper discusses neural networks, deep learning, and artificial intelligence'
|
|
|
|
|
const extraction = await nlp.extract(text, { types: ['concept', 'topic'] })
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(extraction).toBeDefined()
|
|
|
|
|
expect(Array.isArray(extraction)).toBe(true)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
|
|
|
|
// May or may not find specific concepts depending on neural matcher
|
|
|
|
|
// Just verify extraction works
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should extract dates and times', async () => {
|
|
|
|
|
const text = 'The meeting is scheduled for December 15, 2024 at 3:00 PM'
|
|
|
|
|
const extraction = await nlp.extract(text, { types: ['date', 'time', 'event'] })
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(extraction).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
expect(Array.isArray(extraction)).toBe(true)
|
|
|
|
|
// Neural extraction may or may not find specific dates
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should extract locations', async () => {
|
|
|
|
|
const text = 'Our offices are in San Francisco, New York, and London'
|
|
|
|
|
const extraction = await nlp.extract(text, { types: ['location', 'place'] })
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(extraction).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
expect(Array.isArray(extraction)).toBe(true)
|
|
|
|
|
// Neural extraction may or may not find specific locations
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('sentiment - Sentiment Analysis', () => {
|
|
|
|
|
it('should analyze positive sentiment', async () => {
|
|
|
|
|
const text = 'This is an excellent machine learning framework! Really impressive results.'
|
|
|
|
|
const sentiment = await nlp.sentiment(text)
|
|
|
|
|
|
|
|
|
|
expect(sentiment).toBeDefined()
|
|
|
|
|
expect(sentiment.overall.score).toBeGreaterThan(0) // Positive score
|
|
|
|
|
expect(sentiment.overall.label).toBe('positive')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should analyze negative sentiment', async () => {
|
|
|
|
|
const text = 'This approach is terrible and the results are disappointing.'
|
|
|
|
|
const sentiment = await nlp.sentiment(text)
|
|
|
|
|
|
|
|
|
|
expect(sentiment).toBeDefined()
|
|
|
|
|
expect(sentiment.overall.score).toBeLessThan(0) // Negative score
|
|
|
|
|
expect(sentiment.overall.label).toBe('negative')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should analyze neutral sentiment', async () => {
|
|
|
|
|
const text = 'The document contains information about machine learning.'
|
|
|
|
|
const sentiment = await nlp.sentiment(text)
|
|
|
|
|
|
|
|
|
|
expect(sentiment).toBeDefined()
|
|
|
|
|
expect(Math.abs(sentiment.overall.score)).toBeLessThan(0.3) // Close to neutral
|
|
|
|
|
expect(sentiment.overall.label).toBe('neutral')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should provide magnitude scores', async () => {
|
|
|
|
|
const text = 'Machine learning is transforming technology'
|
|
|
|
|
const sentiment = await nlp.sentiment(text)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(sentiment).toBeDefined()
|
|
|
|
|
expect(sentiment.overall.magnitude).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
// Magnitude can be 0 for neutral text
|
|
|
|
|
expect(sentiment.overall.magnitude).toBeGreaterThanOrEqual(0)
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(sentiment.overall.magnitude).toBeLessThanOrEqual(10)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Query Pattern Recognition', () => {
|
|
|
|
|
it('should recognize question patterns', async () => {
|
|
|
|
|
const questions = [
|
|
|
|
|
'What is machine learning?',
|
|
|
|
|
'Who is John Smith?',
|
|
|
|
|
'Where is Google located?',
|
|
|
|
|
'When is the product launch?',
|
|
|
|
|
'How does Python work?'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const q of questions) {
|
|
|
|
|
const result = await nlp.processNaturalQuery(q)
|
|
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
// Should produce a search query
|
|
|
|
|
expect(result.similar || result.like || result.where).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should recognize command patterns', async () => {
|
|
|
|
|
const commands = [
|
|
|
|
|
'Find all engineers',
|
|
|
|
|
'Show me recent papers',
|
|
|
|
|
'List upcoming events',
|
|
|
|
|
'Get information about Google',
|
|
|
|
|
'Search for machine learning'
|
|
|
|
|
]
|
2025-10-09 16:58:01 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
for (const cmd of commands) {
|
|
|
|
|
const result = await nlp.processNaturalQuery(cmd)
|
|
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
// Should have search criteria
|
|
|
|
|
expect(result.similar || result.like || result.where).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Advanced Features', () => {
|
|
|
|
|
it('should handle ambiguous queries gracefully', async () => {
|
|
|
|
|
const query = 'stuff about that thing'
|
|
|
|
|
const result = await nlp.processNaturalQuery(query)
|
|
|
|
|
|
|
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
// Should still attempt to create a query
|
|
|
|
|
expect(result).not.toBeNull()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle very long queries', async () => {
|
|
|
|
|
const longQuery = 'Find ' + 'machine learning '.repeat(50) + 'papers'
|
|
|
|
|
const result = await nlp.processNaturalQuery(longQuery)
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(result).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
// Should still produce a valid query
|
|
|
|
|
expect(result.like || result.where).toBeDefined()
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle empty queries', async () => {
|
|
|
|
|
const result = await nlp.processNaturalQuery('')
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(result).toBeDefined()
|
2025-10-09 16:33:08 -07:00
|
|
|
// Empty query returns minimal query structure
|
|
|
|
|
expect(result).toHaveProperty('like')
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
2025-10-09 16:58:01 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
it('should extract modifiers and preferences', async () => {
|
|
|
|
|
const queries = [
|
|
|
|
|
'Find the most recent papers',
|
|
|
|
|
'Show the best engineers',
|
|
|
|
|
'Get the latest news',
|
|
|
|
|
'Find popular frameworks'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const q of queries) {
|
|
|
|
|
const result = await nlp.processNaturalQuery(q)
|
|
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
|
|
|
|
|
// Might have boost or ordering
|
|
|
|
|
if (result.boost) {
|
|
|
|
|
expect(['recent', 'popular', 'verified']).toContain(result.boost)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Integration with Brainy', () => {
|
|
|
|
|
it('should produce queries that work with brain.find()', async () => {
|
|
|
|
|
const naturalQueries = [
|
|
|
|
|
'Find machine learning papers',
|
|
|
|
|
'Search for John Smith',
|
|
|
|
|
'Get information about Python'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const nq of naturalQueries) {
|
|
|
|
|
const tripleQuery = await nlp.processNaturalQuery(nq)
|
|
|
|
|
|
|
|
|
|
// Should be able to use with brain.find()
|
|
|
|
|
const results = await brain.find(tripleQuery as any)
|
|
|
|
|
|
|
|
|
|
expect(results).toBeDefined()
|
|
|
|
|
expect(Array.isArray(results)).toBe(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should work with complex real-world queries', async () => {
|
|
|
|
|
const complexQuery = 'Find senior engineers at tech companies working on AI'
|
|
|
|
|
const tripleQuery = await nlp.processNaturalQuery(complexQuery)
|
|
|
|
|
|
|
|
|
|
expect(tripleQuery).toBeDefined()
|
|
|
|
|
|
|
|
|
|
// Use with brain
|
|
|
|
|
const results = await brain.find(tripleQuery as any)
|
|
|
|
|
expect(results).toBeDefined()
|
|
|
|
|
expect(Array.isArray(results)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle entity extraction for brain operations', async () => {
|
|
|
|
|
const text = 'John Smith is a talented engineer at Google'
|
|
|
|
|
const extraction = await nlp.extract(text)
|
|
|
|
|
|
|
|
|
|
// Could use extracted entities to create new entries
|
|
|
|
|
if (extraction && extraction.length > 0) {
|
|
|
|
|
for (const entity of extraction) {
|
|
|
|
|
if (entity.type === 'person' && entity.text) {
|
|
|
|
|
// Could add to brain
|
|
|
|
|
const id = await brain.add({
|
|
|
|
|
data: entity.text,
|
|
|
|
|
type: NounType.Person,
|
|
|
|
|
metadata: { extracted: true }
|
|
|
|
|
})
|
|
|
|
|
expect(id).toBeDefined()
|
|
|
|
|
|
|
|
|
|
// Verify it was added
|
|
|
|
|
const retrieved = await brain.get(id)
|
|
|
|
|
expect(retrieved).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Performance', () => {
|
|
|
|
|
it('should process queries quickly', async () => {
|
|
|
|
|
const query = 'Find machine learning papers from 2024'
|
|
|
|
|
|
|
|
|
|
const startTime = Date.now()
|
|
|
|
|
const result = await nlp.processNaturalQuery(query)
|
|
|
|
|
const duration = Date.now() - startTime
|
|
|
|
|
|
|
|
|
|
expect(result).toBeDefined()
|
|
|
|
|
expect(duration).toBeLessThan(200) // Should be fast
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle multiple queries efficiently', async () => {
|
|
|
|
|
const queries = Array(10).fill('Find AI research')
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
const startTime = Date.now()
|
|
|
|
|
const results = await Promise.all(
|
|
|
|
|
queries.map(q => nlp.processNaturalQuery(q))
|
|
|
|
|
)
|
|
|
|
|
const duration = Date.now() - startTime
|
2025-10-09 16:33:08 -07:00
|
|
|
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(results).toHaveLength(10)
|
2025-10-09 16:33:08 -07:00
|
|
|
expect(duration).toBeLessThan(2000) // Should handle batch in reasonable time
|
2025-09-11 16:23:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should cache pattern matching for performance', async () => {
|
|
|
|
|
const query = 'Find machine learning papers'
|
|
|
|
|
|
|
|
|
|
// First call - might be slower
|
|
|
|
|
const start1 = Date.now()
|
|
|
|
|
await nlp.processNaturalQuery(query)
|
|
|
|
|
const time1 = Date.now() - start1
|
|
|
|
|
|
|
|
|
|
// Second call - should be faster due to caching
|
|
|
|
|
const start2 = Date.now()
|
|
|
|
|
await nlp.processNaturalQuery(query)
|
|
|
|
|
const time2 = Date.now() - start2
|
|
|
|
|
|
|
|
|
|
// Second should be similar or faster
|
|
|
|
|
expect(time2).toBeLessThanOrEqual(time1 + 10)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|