diff --git a/README.md b/README.md index 0151231fb..f4089cc82 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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(); ``` diff --git a/__tests__/suggest.test.ts b/__tests__/suggest.test.ts new file mode 100644 index 000000000..e139600e1 --- /dev/null +++ b/__tests__/suggest.test.ts @@ -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 + }); +}); diff --git a/examples/suggest-example.ts b/examples/suggest-example.ts new file mode 100644 index 000000000..ec7c2f2de --- /dev/null +++ b/examples/suggest-example.ts @@ -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); diff --git a/llms.txt b/llms.txt index d66aa8ce5..fec35b604 100644 --- a/llms.txt +++ b/llms.txt @@ -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 @@ -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(); ``` @@ -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 }` diff --git a/site/components/Documentation.tsx b/site/components/Documentation.tsx index 284c5ce0c..adbcf9ef6 100644 --- a/site/components/Documentation.tsx +++ b/site/components/Documentation.tsx @@ -235,6 +235,21 @@ ava.dispose();`} + + {/* Suggest */} +
suggest(count?: number)
+ Get AI-recommended analysis queries based on dataset characteristics (default: 3)
+query — Suggested analysis questionscore — Meaningfulness score (0-1)reason — Explanation for the suggestion{`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);`}
+