Skip to content

Commit d15af18

Browse files
committed
feat(component): add tool for getting visualization config structure
1 parent e16bc76 commit d15af18

11 files changed

Lines changed: 101 additions & 9 deletions

File tree

src/components/db-query/tools/generate-query.tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class GenerateQueryTool implements IGraphTool {
4848
prompt: z
4949
.string()
5050
.describe(
51-
`Prompt from the user that will be used for generating the query. This should be exactly as the user asked without any assumptions.`,
51+
`Prompt from the user that will be used for generating the query.`,
5252
),
5353
}) as AnyObject[string];
5454
return graph.asTool({

src/components/visualization/nodes/select-visualization.node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ You need to suggest a visualisation from a list of visualisation that would best
4949
<instructions>
5050
The output should be a single string that has the name from the visualizations list and nothing else.
5151
If the required visualization would not be possible due to the structure of the data, return "none" followed by the changes required in the data to be able to render the visualization.
52+
Do not try to force fit the data to any visualization if it does not make sense. Prefer to returning none with appropriate reason instead.
5253
</instructions>
5354
<output-example-1>
5455
type-of-visualization

src/components/visualization/tools/generate-visualization.tool.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export class GenerateVisualizationTool implements IGraphTool {
2424
if (result.error) {
2525
return `Visualization could not be generated. Reason: ${result.error}`;
2626
}
27-
return `Visualization rendered for the user.`;
27+
return `Visualization rendered for the user with the following config: ${JSON.stringify(
28+
result.visualizerConfig,
29+
undefined,
30+
2,
31+
)}`;
2832
}
2933

3034
getMetadata(result: Record<string, string>): AnyObject {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {Context, inject} from '@loopback/core';
2+
import {graphTool} from '../../../decorators';
3+
import {IGraphTool} from '../../../graphs';
4+
import {VISUALIZATION_KEY} from '../keys';
5+
import {IVisualizer} from '../types';
6+
import {StructuredToolInterface, tool} from '@langchain/core/tools';
7+
import {RunnableToolLike} from '@langchain/core/runnables';
8+
import z from 'zod';
9+
import {AnyObject} from '@loopback/repository';
10+
11+
@graphTool()
12+
export class GetVisualizationContextTool implements IGraphTool {
13+
key = 'get-visualization-context';
14+
needsReview = false;
15+
16+
constructor(
17+
@inject.context()
18+
private readonly context: Context,
19+
) {}
20+
21+
async build(): Promise<StructuredToolInterface | RunnableToolLike> {
22+
const visualizations = await this._getVisualizations();
23+
return tool(
24+
({type}: {type: string}) => {
25+
const viz = visualizations.find(v => v.name === type);
26+
if (!viz) {
27+
throw new Error(`Visualization with type ${type} not found`);
28+
}
29+
return (
30+
viz.context ??
31+
'No additional context available for this visualization.'
32+
);
33+
},
34+
{
35+
name: this.key,
36+
description:
37+
'Tool to get the context information for a specific visualization type. ' +
38+
'Always call this before using generate/improve query tool if a visualization is needed or expected in the future. ' +
39+
'Use the response of this tool to inform the query tool about the structure expected for a visualization',
40+
schema: z.object({
41+
type: z
42+
.string()
43+
.optional()
44+
.describe(
45+
`Type of visualization to be generated. It can be one of the following: ${visualizations.map(v => v.name).join(', ')}. If not provided, the system will decide the best visualization based on the data and prompt.`,
46+
),
47+
}) as AnyObject[string],
48+
},
49+
);
50+
}
51+
52+
private async _getVisualizations() {
53+
const bindings = this.context.findByTag({
54+
[VISUALIZATION_KEY]: true,
55+
});
56+
if (bindings.length === 0) {
57+
throw new Error(`Node with key ${VISUALIZATION_KEY} not found`);
58+
}
59+
return Promise.all(
60+
bindings.map(binding => this.context.get<IVisualizer>(binding.key)),
61+
);
62+
}
63+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './generate-visualization.tool';
2+
export * from './get-visualization-context.tool';

src/components/visualization/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import {VisualizationGraphState} from './state';
44
export interface IVisualizer {
55
name: string;
66
description: string;
7+
context?: string;
78
getConfig(state: VisualizationGraphState): Promise<AnyObject> | AnyObject;
89
}

src/components/visualization/visualizer.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './nodes';
1717
import {GenerateVisualizationTool} from './tools/generate-visualization.tool';
1818
import {PieVisualizer, BarVisualizer, LineVisualizer} from './visualizers';
19+
import {GetVisualizationContextTool} from './tools';
1920

2021
export class VisualizerComponent implements Component {
2122
services: ServiceOrProviderClass[] | undefined;
@@ -35,6 +36,7 @@ export class VisualizerComponent implements Component {
3536
VisualizationGraph,
3637
// tools
3738
GenerateVisualizationTool,
39+
GetVisualizationContextTool,
3840
// nodes
3941
GetDatasetDataNode,
4042
SelectVisualizationNode,

src/components/visualization/visualizers/bar.visualizer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ You are an expert data visualization assistant. Your task is to create a bar cha
3333
</user-prompt>
3434
</inputs>`);
3535

36+
context?: string | undefined =
37+
`A bar chart requires data with at least two columns: one for the categories (x-axis) and one for the values (y-axis). Ensure that the category column contains discrete values representing different groups or categories, while the value column contains numerical data that can be compared across these categories. Bar charts can be oriented either vertically or horizontally depending on the data representation needs.`;
38+
3639
schema = z.object({
3740
categoryColumn: z
3841
.string()

src/components/visualization/visualizers/line.visualizer.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import {visualizer} from '../decorators/visualizer.decorator';
1212
@visualizer()
1313
export class LineVisualizer implements IVisualizer {
1414
name = 'line';
15+
16+
context?: string | undefined =
17+
`A line chart requires data with at least two columns: one for the x-axis (typically time or sequential data) and one for the y-axis (values). Optionally, a third list of columns can be used to differentiate multiple series (lines) in the chart, this is usually the field to group the data on. Ensure that the x-axis data is continuous and ordered to accurately represent trends over time.`;
18+
1519
description = `Renders the data in a line chart format. Best for showing trends and changes over time or continuous data.`;
1620
renderPrompt = PromptTemplate.fromTemplate(`
1721
<instructions>
1822
You are an expert data visualization assistant. Your task is to create a line chart config based on the provided SQL query, it's description and user prompt. Follow these steps:
1923
1. Analyze the SQL query results to understand the data structure.
2024
2. Identify the x-axis column (typically time or sequential data) and y-axis column (values) for the line chart.
21-
3. Determine if there are multiple series to be plotted (multiple lines).
25+
3. Determine if there are multiple series to be plotted (multiple lines) with combination of multiple columns, or single series based on single column.
2226
4. Create a configuration object for the line chart using the identified columns.
2327
5. Return the line chart configuration object.
2428
</instructions>
@@ -38,15 +42,17 @@ You are an expert data visualization assistant. Your task is to create a line ch
3842
xAxisColumn: z
3943
.string()
4044
.describe(
41-
'Column to be used for x-axis in the line chart (typically time or sequential data)',
45+
'Single column name to be used for x-axis in the line chart (typically time or sequential data)',
4246
),
4347
yAxisColumn: z
4448
.string()
45-
.describe('Column to be used for y-axis values in the line chart'),
46-
seriesColumn: z
49+
.describe(
50+
'Single column name to be used for y-axis values in the line chart',
51+
),
52+
seriesColumns: z
4753
.string()
4854
.describe(
49-
'Optional column to group data into multiple lines/series, leave it as empty string if not needed',
55+
'Optional column to group data into multiple lines/series, leave it as empty string if not needed. It can cover multiple columns separated by comma if the query needs to show multiple lines based on multiple columns. The UI supports multiple series in line chart by forming a combined key.',
5056
),
5157
}) as z.AnyZodObject;
5258

@@ -73,8 +79,15 @@ You are an expert data visualization assistant. Your task is to create a line ch
7379
description: state.queryDescription!,
7480
userPrompt: state.prompt!,
7581
});
76-
if (settings.seriesColumn === '' || settings.seriesColumn === undefined) {
77-
settings.seriesColumn = null;
82+
if (
83+
settings.seriesColumns === '' ||
84+
settings.seriesColumns === undefined ||
85+
settings.seriesColumns === null
86+
) {
87+
settings.seriesColumns = null;
88+
} else {
89+
settings.seriesColumns =
90+
settings.seriesColumns?.split(',').map((s: string) => s.trim()) ?? [];
7891
}
7992
return settings;
8093
}

src/components/visualization/visualizers/pie.visualizer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ You are an expert data visualization assistant. Your task is to create a pie cha
3333
</user-prompt>
3434
</inputs>`);
3535

36+
context?: string | undefined =
37+
`A pie chart requires data with at least two columns: one for the labels (categories) and one for the values (numerical data). Ensure that the values are non-negative and represent parts of a whole, as pie charts are used to visualize proportions and percentages among different categories.`;
38+
3639
schema = z.object({
3740
labelColumn: z
3841
.string()

0 commit comments

Comments
 (0)