Skip to content

Commit 85dffca

Browse files
leondt1xueren.dt
andauthored
feat: refactor advise (#836)
* feat(ava): add new chart advise prompt * fix(ava): fix type * fix(ava): fix render bug * fix(ava): fix ci problem --------- Co-authored-by: xueren.dt <xueren.dt@antgroup.com>
1 parent d87a964 commit 85dffca

40 files changed

Lines changed: 2831 additions & 214 deletions

packages/ava/src/advisor/advise-chart-pipeline/plugins/advisePlugin.ts

Lines changed: 48 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
import { logError, logInDev, isOpenAi, isTbox, requestLLM, safeJsonParse } from '@ava/utils';
1+
import { logError, isOpenAi, isTbox, requestLLM, safeJsonParse } from '@ava/utils';
22
import {
33
AdviseChartParams,
44
AdviseChartPluginInput,
55
AdvisorPlugin,
6-
ChartConfig,
7-
AdviseChart,
86
IAdviseChartPipeline,
97
DataShard,
108
PlainLikeDataType,
119
} from '@ava/types';
12-
import {
13-
generateAllChartConfigs,
14-
optimizeChartConfig,
15-
sortChartConfigs,
16-
transformChartEncode,
17-
} from '@ava/advisor/chartAdvise';
18-
import { getPlainChartAdvisePrompt } from '@ava/advisor/chartAdvise/prompt';
1910
import { AdviseChartPluginEnum } from '@ava/constants/pipeline';
2011
import { DATA_SHAPE } from '@ava/extract/constants';
2112

13+
import { getChartAdvisePrompt, getSpecGeneratePrompt } from '../../chartAdvise/prompt';
14+
import { CHART_ID_MAP } from '../../../ckb';
15+
import { Spec } from '../../../bind';
16+
2217
export class AdvisePlugin implements AdvisorPlugin<AdviseChartParams> {
2318
name = AdviseChartPluginEnum.AdvisePlugin;
2419

@@ -41,86 +36,52 @@ export class AdvisePlugin implements AdvisorPlugin<AdviseChartParams> {
4136
};
4237

4338
advisePlain = async (dataShards: DataShard[], input: AdviseChartPluginInput) => {
44-
let allChartConfigs: Array<ChartConfig[]> = [];
45-
let _llmCompleted = false;
46-
let _llmCostTime = '';
4739
const { context, dataStore } = input;
48-
const { excludes, includes, disableModel, forceType, llm, uiConfig = {} } = context;
49-
const paramList = dataShards.map((shard) => {
50-
const { data, metas, purpose } = shard;
51-
// generate all valid chart configs using field data
52-
const curConfigs = generateAllChartConfigs(metas, excludes, includes);
53-
allChartConfigs.push(curConfigs);
54-
return {
55-
userInput: purpose?.purposeDesc ?? '',
56-
chartConfig: curConfigs,
57-
metas,
58-
data: data as PlainLikeDataType,
59-
};
60-
});
61-
if (forceType && dataShards.length === 1) {
62-
// user specified chart type
63-
const finalRes: AdviseChart[] = allChartConfigs[0]
64-
.filter((item) => item.type === forceType)
65-
.map((item) => ({
66-
type: item.type,
67-
encode: transformChartEncode(item.encode),
68-
}));
69-
70-
const result = [
71-
{
72-
adviseCharts: finalRes,
73-
metas: dataShards[0].metas,
74-
data: dataShards[0].data as PlainLikeDataType,
75-
},
76-
];
77-
dataStore.advise = result;
40+
const { llm } = context;
41+
if (!isOpenAi(llm) && !isTbox(llm)) {
42+
logError('LLM config is missing or invalid');
43+
dataStore.advise = [];
7844
} else {
79-
if (!disableModel) {
80-
try {
81-
logInDev.debug('All possible chart configs', JSON.stringify(allChartConfigs));
82-
// Use LLM to score all chart configs based on user purpose/data/metas
83-
const prompt = getPlainChartAdvisePrompt(paramList);
84-
const startTime = performance.now();
85-
let LLMRes = '';
86-
if (!isOpenAi(llm) && !isTbox(llm)) {
87-
logError('LLM config is missing or invalid');
88-
} else {
89-
LLMRes = await requestLLM({ config: llm, prompt });
90-
}
91-
if (LLMRes) {
92-
logInDev.debug('chart configs after LLM scoring', LLMRes);
93-
const LLMResArr = safeJsonParse(LLMRes, []);
94-
if (LLMResArr.length === allChartConfigs.length) {
95-
allChartConfigs = allChartConfigs.map((configs, index) => sortChartConfigs(configs, LLMResArr[index]));
96-
}
97-
const endTime = performance.now();
98-
_llmCostTime = ((endTime - startTime) / 1000).toFixed(2);
99-
_llmCompleted = true;
100-
} else {
101-
logError('LLM scoring failed');
102-
}
103-
} catch (error) {
104-
logError('LLM scoring failed', error);
105-
}
106-
}
107-
// Optimize chart configuration based on rules
108-
const finalRes = allChartConfigs.map((configs, index) => {
109-
const { data, metas } = dataShards[index];
110-
const adviseCharts = optimizeChartConfig({
111-
chartConfigs: configs,
112-
metas,
113-
data: data as PlainLikeDataType,
114-
uiConfig,
45+
try {
46+
const params = dataShards.map((shard) => {
47+
const { data, metas, purpose } = shard;
48+
return {
49+
metas,
50+
data: data as PlainLikeDataType,
51+
purpose: purpose?.purposeDesc ?? '',
52+
};
53+
});
54+
const adviseInputs = getChartAdvisePrompt(params);
55+
const recommendationStr = await requestLLM({ config: llm, prompt: adviseInputs });
56+
const recommendation = safeJsonParse(recommendationStr, []);
57+
const bestCharts = recommendation.map((item) => CHART_ID_MAP[item[0]]) as string[];
58+
const specGenerateInputs = dataShards.map((shard, index) => {
59+
const { data } = shard;
60+
return {
61+
data: data as PlainLikeDataType,
62+
chartId: bestCharts[index],
63+
};
11564
});
116-
return {
117-
adviseCharts,
118-
metas,
119-
data: data as PlainLikeDataType,
120-
};
121-
});
122-
logInDev.debug('chart configs after optimization', JSON.stringify(finalRes));
123-
dataStore.advise = finalRes;
65+
const chartSpecGeneratePrompt = getSpecGeneratePrompt(specGenerateInputs);
66+
const chartSpecsStr = await requestLLM({ config: llm, prompt: chartSpecGeneratePrompt });
67+
const chartSpecs = safeJsonParse(chartSpecsStr, []) as Spec[];
68+
const res = chartSpecs.map((item, index) => ({
69+
charts: [
70+
{
71+
spec: {
72+
...item,
73+
type: bestCharts[index],
74+
},
75+
},
76+
],
77+
data: dataShards[index].data as PlainLikeDataType,
78+
metas: dataShards[index].metas,
79+
}));
80+
dataStore.advise = res;
81+
} catch (error) {
82+
logError('LLM request failed');
83+
dataStore.advise = [];
84+
}
12485
}
12586
};
12687

packages/ava/src/advisor/advisor.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-dupe-class-members */
12
import {
23
AdviseChartParams,
34
AdviseStageOutput,
@@ -10,14 +11,14 @@ import { extractData } from '@ava/extract';
1011

1112
import { logError } from '../utils';
1213
import { getRenderer, type Spec } from '../bind';
14+
1315
import { AdviseChartPipeline } from './advise-chart-pipeline/pipeline';
1416

1517
/**
1618
* The 1st level advisor class.
1719
* Used to extract data, recommend charts, and render charts. All functionalities are AI-driven.
1820
*/
1921
export class Advisor {
20-
2122
/**
2223
* Configuration for the advisor. Includes LLM settings, chart inclusion/exclusion lists.
2324
*/
@@ -36,13 +37,13 @@ export class Advisor {
3637

3738
/**
3839
* Extract data shards from raw data or user query.
39-
*
40+
*
4041
* Case 1 - User provides a text query, extract relevant data shards:
41-
*
42+
*
4243
advisor.extract('What is the average age of people who work as engineers?');
43-
*
44+
*
4445
* Case 2 - User provides a query with raw data:
45-
*
46+
*
4647
advisor.extract(`帮我可视化以下数据:
4748
城市 类别 渠道 销售额 价格
4849
杭州 体育 A 100 80
@@ -51,9 +52,9 @@ export class Advisor {
5152
广州 体育 B 120 70
5253
深圳 体育 C 180 95
5354
`);
54-
*
55+
*
5556
* Case 3 - User provides raw data, extract data shards directly:
56-
*
57+
*
5758
advisor.extract({ type: 'A', value: 2 });
5859
*/
5960
async extract(params: AdviseChartParams) {
@@ -66,40 +67,41 @@ export class Advisor {
6667
/**
6768
* Advise charts based on the data shards, which are extracted from the `advisor.extract` API.
6869
* This is the core function of the Advisor class, which leverages LLMs to recommend suitable chart types and encodings based on the provided data and user purpose.
69-
*
70+
*
7071
* const advises = advisor.advise(dataShards);
7172
*/
7273
advise(params: AdviseChartParams): Promise<AdviseStageOutput>;
74+
7375
advise(params: AdviseTextParams): Promise<AdviseText>;
76+
7477
async advise(params: AdviseChartParams | AdviseTextParams): Promise<AdviseStageOutput | AdviseText> {
7578
const result = await this.adviseChartPipeline.execute(params as AdviseChartParams);
7679
return result;
7780
}
7881

7982
/**
8083
* Render the chart recommendation spec into a chart dom. We can use different renderers by customizing the renderer.
81-
*
84+
*
8285
* Case 1 - Using default renderer (AVA built-in):
83-
*
86+
*
8487
advisor.render({
8588
container: '#chart',
8689
spec: chartSpec,
8790
});
88-
*
91+
*
8992
* Case 2 - Using custom renderer:
90-
*
93+
*
9194
const customRenderer: Renderer = (container, spec) => { ... };
9295
bindRenderer(customRenderer);
9396
advisor.render({
9497
container: '#chart',
9598
spec: chartSpec,
9699
});
97-
*
100+
*
98101
*/
99-
render(params: { container: string; spec: Spec }) {
102+
render(container: string, spec: Spec) {
100103
const renderer = getRenderer();
101104
if (renderer) {
102-
const { container, spec } = params;
103105
return renderer(container, spec);
104106
}
105107
logError('Chart render not configured, please bind a renderer first, GPT-Vis is recommended.');

0 commit comments

Comments
 (0)