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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
AVA is a fundamental shift from rule-based analytics to AI-native capabilities:

- **Natural Language Queries**: Ask questions about your data in plain English
- **Query Suggestions**: Get AI-recommended analysis queries based on your data characteristics
- **LLM-Powered Analysis**: Leverages large language models for intelligent data analysis
- **Smart Data Handling**: Automatically chooses between in-memory processing and SQLite based on data size
- **Modular Architecture**: Clean separation of concerns with data, analysis, and visualization modules
Expand Down Expand Up @@ -78,10 +79,26 @@ await ava.loadURL('https://api.example.com/data', (response) => response.data);
// or extract from text
await ava.loadText('杭州 100,上海 200,北京 300');

// Get suggested analysis queries
const queries = await ava.suggest(5); // Get top 5 suggested queries (default: 3)
console.log(queries);
// [
// {
// query: 'What is the average revenue by region?',
// score: 0.95,
// reason: 'Understanding revenue distribution across regions helps identify high-performing areas'
// },
// ...
// ]

// Ask questions in natural language
const result = await ava.analysis('What is the average revenue by region?');
console.log(result);

// Or use a suggested query
const suggestedResult = await ava.analysis(queries[0].query);
console.log(suggestedResult);

// Clean up
ava.dispose();
```
Expand Down
156 changes: 156 additions & 0 deletions __tests__/suggest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Tests for suggest functionality
*/

import * as path from 'path';

import { describe, it, expect, beforeEach, afterEach } from 'vitest';

import { AVA } from '../src/ava';

describe('Suggest Tests', () => {
let ava: AVA;
const testDataPath = path.join(__dirname, '../data/companies.csv');

const apiKey = process.env.LING_1T_API_KEY;
const skipLLMTests = !apiKey;

const getLLMConfig = () => ({
model: 'ling-1t',
apiKey: apiKey || '',
baseURL: 'https://api.tbox.cn/api/llm/v1',
});

beforeEach(() => {
if (skipLLMTests) {
// eslint-disable-next-line no-console
console.log('Skipping LLM integration test: LING_1T_API_KEY not set');
return;
}

ava = new AVA({
llm: getLLMConfig(),
});
});

afterEach(() => {
if (ava) {
ava.dispose();
}
});

describe('suggest method', () => {
it('should throw error when suggesting without loading data', async () => {
if (skipLLMTests) return;

await expect(ava.suggest()).rejects.toThrow('No data loaded');
});

it('should return 3 suggestions by default', async () => {
if (skipLLMTests) return;

try {
await ava.loadCSV(testDataPath);
const suggestions = await ava.suggest();

expect(suggestions).toBeDefined();
expect(Array.isArray(suggestions)).toBe(true);
expect(suggestions.length).toBe(3);

// Verify each suggestion has required fields
for (const suggestion of suggestions) {
expect(suggestion).toHaveProperty('query');
expect(suggestion).toHaveProperty('score');
expect(suggestion).toHaveProperty('reason');
expect(typeof suggestion.query).toBe('string');
expect(typeof suggestion.score).toBe('number');
expect(typeof suggestion.reason).toBe('string');
expect(suggestion.query.length).toBeGreaterThan(0);
expect(suggestion.reason.length).toBeGreaterThan(0);
// Score should be between 0 and 1
expect(suggestion.score).toBeGreaterThanOrEqual(0);
expect(suggestion.score).toBeLessThanOrEqual(1);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 60000);

it('should return specified number of suggestions', async () => {
if (skipLLMTests) return;

try {
await ava.loadCSV(testDataPath);
const suggestions = await ava.suggest(5);

expect(suggestions).toBeDefined();
expect(Array.isArray(suggestions)).toBe(true);
expect(suggestions.length).toBe(5);
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 60000);

it('should return suggestions sorted by score in descending order', async () => {
if (skipLLMTests) return;

try {
await ava.loadCSV(testDataPath);
const suggestions = await ava.suggest();

// Verify suggestions are sorted by score descending
for (let i = 0; i < suggestions.length - 1; i++) {
expect(suggestions[i].score).toBeGreaterThanOrEqual(suggestions[i + 1].score);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 60000);

it('should work with loadObject', async () => {
if (skipLLMTests) return;

try {
await ava.loadObject([
{ city: '杭州', gdp: 18753 },
{ city: '上海', gdp: 43214 },
{ city: '北京', gdp: 35371 },
]);

const suggestions = await ava.suggest();

expect(suggestions).toBeDefined();
expect(Array.isArray(suggestions)).toBe(true);
expect(suggestions.length).toBe(3);
} catch (error) {
// eslint-disable-next-line no-console
console.log('Skipping test due to API error:', error instanceof Error ? error.message : String(error));
}
}, 60000);

it('should generate queries that can be used with analysis', async () => {
if (skipLLMTests) return;

try {
await ava.loadCSV(testDataPath);
const suggestions = await ava.suggest(1);

expect(suggestions.length).toBeGreaterThan(0);

// Try to analyze using the first suggested query
const result = await ava.analysis(suggestions[0].query);

expect(result).toBeDefined();
expect(result.text).toBeDefined();
expect(typeof result.text).toBe('string');
expect(result.text.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));
}
}, 120000); // Longer timeout for this test
});
});
53 changes: 53 additions & 0 deletions examples/suggest-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Example: Using the suggest() method
*
* This example demonstrates how to use the suggest() method
* to get AI-recommended analysis queries based on your data.
*/

import { AVA } from '@antv/ava';

async function main() {
// Initialize AVA with LLM config
const ava = new AVA({
llm: {
model: 'ling-1t',
apiKey: process.env.LING_1T_API_KEY || 'YOUR_API_KEY',
baseURL: 'https://api.tbox.cn/api/llm/v1',
},
});

// Load sample data
await ava.loadObject([
{ city: '杭州', gdp: 18753, population: 1220 },
{ city: '上海', gdp: 43214, population: 2489 },
{ city: '北京', gdp: 35371, population: 2188 },
{ city: '深圳', gdp: 30664, population: 1768 },
{ city: '广州', gdp: 28839, population: 1868 },
]);

console.log('Getting query suggestions...\n');

// Get 5 suggested queries
const queries = await ava.suggest(5);

console.log('Suggested Analysis Queries:\n');
queries.forEach((suggestion, index) => {
console.log(`${index + 1}. Query: ${suggestion.query}`);
console.log(` Score: ${(suggestion.score * 100).toFixed(1)}%`);
console.log(` Reason: ${suggestion.reason}\n`);
});

// Use the top suggested query for analysis
if (queries.length > 0) {
console.log('\nAnalyzing with the top suggestion...\n');
const result = await ava.analysis(queries[0].query);
console.log('Analysis Result:');
console.log(result.text);
}

// Clean up
ava.dispose();
}

main().catch(console.error);
16 changes: 16 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ AVA, a complete rewrite focused on **AI-native Visual Analytics**. The first **A
## Key Features

- **Natural Language Queries**: Ask questions about your data in plain English
- **Query Suggestions**: Get AI-recommended analysis queries based on your data characteristics
- **LLM-Powered Analysis**: Leverages large language models for intelligent data analysis
- **Smart Data Handling**: Automatically chooses between in-memory processing and SQLite based on data size
- **Modular Architecture**: Clean separation of concerns with data, analysis, and visualization modules
Expand All @@ -35,10 +36,19 @@ await ava.loadObject([{ city: '杭州', gdp: 18753 }, { city: '上海', gdp: 432
await ava.loadURL('https://api.example.com/data', (response) => response.data);
await ava.loadText('杭州 100,上海 200,北京 300');

// Get AI-suggested queries
const suggestions = await ava.suggest(5); // Get top 5 suggestions (default: 3)
console.log(suggestions[0]);
// { query: "What is the average GDP by city?", score: 0.95, reason: "Reveals economic patterns across cities" }

// Ask questions in natural language
const result = await ava.analysis('What is the average revenue by region?');
console.log(result);

// Or use a suggested query
const suggestedResult = await ava.analysis(suggestions[0].query);
console.log(suggestedResult);

// Clean up
ava.dispose();
```
Expand All @@ -65,6 +75,12 @@ AVA uses a modular pipeline architecture that processes user queries through dis
- `loadURL(url: string, transform?: Function)` - Load data from URL
- `loadText(text: string)` - Extract data from unstructured text using LLM

### Query Suggestions
- `suggest(count?: number)` - Get AI-recommended analysis queries based on dataset characteristics (default: 3)
- Returns: Array of `{ query: string, score: number, reason: string }`
- Queries are sorted by score in descending order
- Score ranges from 0 to 1, indicating meaningfulness

### Analysis
- `analysis(query: string)` - Analyze data with natural language query
- Returns: `{ text, code, sql, visualizationHTML }`
Expand Down
34 changes: 34 additions & 0 deletions site/components/Documentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ ava.dispose();`}</pre>
</div>
</div>
</div>

{/* Suggest */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-3">Query Suggestions</h3>
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
<code className="text-sm text-[#78d3f8] block mb-2">suggest(count?: number)</code>
<p className="text-sm text-gray-600 mb-3">Get AI-recommended analysis queries based on dataset characteristics (default: 3)</p>
<div className="text-xs text-gray-500 space-y-1">
<div className="font-semibold mb-2">Returns array of objects with:</div>
<div>• <code className="bg-white px-1 rounded">query</code> — Suggested analysis question</div>
<div>• <code className="bg-white px-1 rounded">score</code> — Meaningfulness score (0-1)</div>
<div>• <code className="bg-white px-1 rounded">reason</code> — Explanation for the suggestion</div>
</div>
</div>
</div>
</div>
</section>

Expand All @@ -245,6 +260,25 @@ ava.dispose();`}</pre>
Usage Examples
</h2>
<div className="space-y-6">
<div className="border-l-4 border-[#78d3f8] pl-4">
<h4 className="font-semibold text-gray-800 mb-2">Query Suggestions</h4>
<div className="bg-gray-900 text-gray-100 rounded-lg p-4 font-mono text-xs overflow-x-auto">
<pre className="whitespace-pre">{`await ava.loadObject([
{ city: 'Hangzhou', gdp: 18753 },
{ city: 'Shanghai', gdp: 43214 }
]);

// Get 5 suggested queries
const suggestions = await ava.suggest(5);
console.log(suggestions[0]);
// { query: "What is the average GDP?",
// score: 0.95,
// reason: "Reveals economic patterns" }

// Use suggested query for analysis
const result = await ava.analysis(suggestions[0].query);`}</pre>
</div>
</div>
<div className="border-l-4 border-[#78d3f8] pl-4">
<h4 className="font-semibold text-gray-800 mb-2">Browser File Upload</h4>
<div className="bg-gray-900 text-gray-100 rounded-lg p-4 font-mono text-xs overflow-x-auto">
Expand Down
Loading