Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,17 @@ console.log(queries);

// Ask questions in natural language
const result = await ava.analysis('What is the average revenue by region?');
console.log(result);
console.log(result.text); // Natural language summary
// result.data → structured analysis result
// result.code → JavaScript code (small datasets)
// result.sql → SQL query (large datasets with SQLite)

// Generate chart visualization from analysis result
const viz = await ava.visualize(result);
console.log(viz.chartType); // e.g. 'column'
console.log(viz.syntax); // GPT-Vis chart syntax
// viz.html → standalone HTML that renders the chart


// Or use a suggested query
const suggestedResult = await ava.analysis(queries[0].query);
Expand All @@ -103,6 +113,42 @@ console.log(suggestedResult);
ava.dispose();
```

## 📘 Documentation

Create an AVA instance:

- `new AVA(options)`: initialize runtime and LLM configuration.
- `llm`: required model config, e.g. `{ model, apiKey, baseURL }`
- `sqlThreshold?`: optional size threshold (bytes) to switch from in-memory analysis to SQLite/IndexedDB (default: 10KB)

Core APIs in AVA:

- `loadCSV(filePathOrContent)`: load CSV (Node.js: file path; Browser: CSV content string).
- `loadObject(data)` / `loadURL(url, transform?)` / `loadText(text)`: load data into AVA.
- `suggest(count?)`: generate recommended analysis questions.
- `analysis(query)`: run data analysis and return `{ query, text, data, code?, sql? }` (`code` for in-memory JS analysis, `sql` for SQLite analysis).
- `visualize(analysisResult)`: generate chart output from analysis result, returns `{ chartType, syntax, html } | null` (`null` when no visualization intent or no usable data).
- `dispose()`: release SQLite / IndexedDB and in-memory resources.

Minimal usage:

```typescript
const ava = new AVA({ llm: { model, apiKey, baseURL } });

await ava.loadObject([{ city: 'Hangzhou', gdp: 18753 }]);

const analysis = await ava.analysis('Show GDP by city');
console.log(analysis.text);

const viz = await ava.visualize(analysis);
if (viz) {
console.log(viz.chartType);
console.log(viz.html);
}

ava.dispose();
```

## 🏗️ Architecture

AVA uses a modular pipeline architecture that processes user queries through distinct stages. Data is loaded from multiple sources (CSV, JSON, URL, or text), analyzed intelligently based on size (JavaScript for small datasets, SQLite for large ones), results are summarized using LLM into natural language responses, and optionally visualized with chart recommendations.
Expand Down
90 changes: 83 additions & 7 deletions __tests__/ava.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ describe('AVA Integration Tests', () => {
expect(result.text).toBeDefined();
expect(typeof result.text).toBe('string');
expect(result.text.length).toBeGreaterThan(0);
// analysis() should return query field
expect(result.query).toBe('What is the total revenue?');
// analysis() should not return visualization fields
expect(result).not.toHaveProperty('visualizationSyntax');
expect(result).not.toHaveProperty('visualizationHTML');
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
Expand All @@ -92,8 +96,10 @@ describe('AVA Integration Tests', () => {
expect(result.text.length).toBeGreaterThan(0);
// Result should mention regions
expect(result.text.toLowerCase()).toMatch(/california|texas|new york/);
// analysis() should return data and query for downstream visualize()
expect(result.data).toBeDefined();
expect(result.query).toBe('What is the average revenue by region?');
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
Expand All @@ -112,7 +118,6 @@ describe('AVA Integration Tests', () => {
// Maximum revenue in test data is 32400 (may be formatted as 32,400)
expect(result.text.toLowerCase()).toMatch(/32[,\s]?400/);
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
Expand All @@ -130,7 +135,6 @@ describe('AVA Integration Tests', () => {
expect(typeof result.text).toBe('string');
expect(result.text.toLowerCase()).toContain('california');
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
Expand All @@ -148,13 +152,87 @@ describe('AVA Integration Tests', () => {
expect(typeof result.text).toBe('string');
expect(result.text.length).toBeGreaterThan(0);
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 60000);
});

describe('Visualize', () => {
beforeEach(async () => {
if (skipLLMTests) return;
await ava.loadCSV(testDataPath);
});

it('should visualize analysis data with a chart query', async () => {
if (skipLLMTests) return;

try {
const analysisResult = await ava.analysis('Visualize the average revenue by region as a bar chart');

expect(analysisResult.data).toBeDefined();

const vizResult = await ava.visualize(analysisResult);

// vizResult may be null if LLM doesn't detect visualization intent
// but with an explicit "bar chart" query it should return a result
if (vizResult) {
expect(vizResult.chartType).toBeDefined();
expect(typeof vizResult.chartType).toBe('string');
expect(vizResult.html).toBeDefined();
expect(typeof vizResult.html).toBe('string');
expect(vizResult.syntax).toBeDefined();
expect(typeof vizResult.syntax).toBe('string');
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 120000);

it('should return null for non-visualization data', async () => {
if (skipLLMTests) return;

try {
const analysisResult = await ava.analysis('What is the total revenue?');
const vizResult = await ava.visualize(analysisResult);

// Non-visualization queries may return null
// This is acceptable behavior — the advisor decides
if (vizResult === null) {
expect(vizResult).toBeNull();
} else {
expect(vizResult.chartType).toBeDefined();
expect(vizResult.html).toBeDefined();
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 120000);

it('should emit step events during visualize', async () => {
if (skipLLMTests) return;

const steps: any[] = [];
const handler = (event: any) => { steps.push({ ...event }); };
ava.on('step', handler);

try {
const analysisResult = await ava.analysis('Show revenue by region as a chart');
await ava.visualize(analysisResult);

// Step events should have been emitted
expect(steps.length).toBeGreaterThan(0);
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
} finally {
ava.off('step', handler);
}
}, 120000);
});

describe('Analysis with Large Dataset (SQLite)', () => {
it('should use SQLite for large datasets', async () => {
if (skipLLMTests) return;
Expand All @@ -176,7 +254,6 @@ describe('AVA Integration Tests', () => {
// Verify result mentions the count (12 companies in test data)
expect(result.text).toContain('12');
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
} finally {
Expand All @@ -202,7 +279,6 @@ describe('AVA Integration Tests', () => {
expect(typeof result.text).toBe('string');
expect(result.text.length).toBeGreaterThan(0);
} catch (error) {
// If the API fails, skip the test rather than failing
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
} finally {
Expand Down
Loading
Loading