Skip to content

Chart integration #8634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

rohitpathak21
Copy link

Summary

This PR adds a new Chart Toggle option to the agent configuration UI, enabling inline rendering of charts in chat conversations. When this toggle is enabled, the LLM responds with a strict Echarts options tructure for chart data, which the frontend parses and renders using ECharts.

The main motivation is to allow users to visualize analytical or structured data directly within conversations—similar to the artifact feature but specifically for charts. This unlocks real-time visualization use cases such as AI usage metrics, cost breakdowns, or monitoring reports.

An example application of this feature is an internal MCP tool we've developed, which generates AWS billing data formatted as ECharts JSON. When combined with this chart toggle, the data is rendered inline as a cost dashboard, effectively acting as a conversational mini-Cost Explorer.

There are opportunities for design improvement and extended parsing flexibility in future iterations.

Change Type

New feature (non-breaking change which adds functionality)

Testing

Manual testing was performed to validate the following:
Toggling the chart feature on/off in agent settings correctly alters rendering behavior.
Responses from the LLM in the :::chart{} format are parsed and rendered using ECharts in the chat window.
Existing functionality like artifacts, markdown, and code rendering remain unaffected.

Checklist

  • [ ✅] My code adheres to this project's style guidelines
  • [ ✅] I have performed a self-review of my own code
  • [✅ ] I have commented in any complex areas of my code
  • I have made pertinent documentation changes
  • [ ] My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes
  • Any changes dependent on mine have been merged and published in downstream modules.
  • A pull request for updating the documentation has been submitted.

I have added two video files showing the working of this functionality

Screencast.from.2025-07-24.04-48-28.mp4
Screencast.from.2025-07-24.04-50-42.mp4

@owengo
Copy link
Contributor

owengo commented Jul 27, 2025

It would be great to have support for graphics. It seems that your solutions works like this:

  1. The LLM calls an MCP tool ( or whatever ) to gather some external data
  2. Then it instantiates the chart copying all the data returned by the tool

I works probably very well for a small amount of data but it is not scalable.

With another LLM frontend 2 years ago I have tried cubejs, and it was working like this:

  1. The prompt explained to the llm the structure of the data in the backend ( the cubejs spec )
  2. The LLM instantiates the chart with a backend query as the datasource ( the language was some kind of pseudo sql specialized in aggregations )
  3. The charts rendering was directly connected to the backend

So the llm did not "see" the data, but it was an advantage because:

  • it was very fast, the inference time was only related the time to generate the query
  • it was "accurate": no copy-paste errors from the llm
  • no problem llm context saturated by the volume of data, and paying twice for it as input and output

Would it be possible the same kind of solution with echarts? ie: have a query in the datasource ( which could be generated by an MCP by the way )

@fstadt
Copy link

fstadt commented Jul 27, 2025

Possible solution: Provide a way to specify a url or mcp resource instead of the echarts json. The url could then be fetched to get the echarts json itself. Then the LLM would just need to correctly copy-paste the url / resource identifier.

Could possible be used for other artifacts as well. If the llm wants to "see" the data behind the graph itself, it could still do so by fetching the resource or url.

I still like the ability to provide the echarts json directly though since it might be useful even without integrating a specialized mcp server (just by using server instructions for echarts).

@rohitpathak21
Copy link
Author

It would be great to have support for graphics. It seems that your solutions works like this:

1. The LLM calls an MCP tool ( or whatever ) to gather some external data

2. Then it instantiates the chart copying all the data returned by the tool

I works probably very well for a small amount of data but it is not scalable.

With another LLM frontend 2 years ago I have tried cubejs, and it was working like this:

1. The prompt explained to the llm the structure of the data in the backend ( the cubejs spec )

2. The LLM instantiates the chart with a backend query as the datasource ( the language was some kind of pseudo sql specialized in aggregations )

3. The charts rendering was directly connected to the backend

So the llm did not "see" the data, but it was an advantage because:

* it was very fast, the inference time was only related the time to generate the query

* it was "accurate": no copy-paste errors from the llm

* no problem llm context saturated by the volume of data, and paying twice for it as input and output

Would it be possible the same kind of solution with echarts? ie: have a query in the datasource ( which could be generated by an MCP by the way )

@owengo Thank you for taking the time to thoroughly review my PR and providing such detailed feedback. I really appreciate you sharing your experience with CubeJS and the query-based approach - it's clear you've thought deeply about the scalability challenges of chart rendering systems.

You raise valid points about performance and scalability. However, I'd like to clarify a few aspects of our current implementation and the challenges we're trying to solve:

Our system needs to handle truly arbitrary data sources and query types that can't be predefined. While CubeJS works excellently for known schemas with predefined cubes, our use case involves dynamic query generation (Snowflake, MongoDB, custom APIs, GraphQL endpoints, etc.), unknown data structures that change per request and can't be defined upfront, and varied data formats (nested objects, flat arrays, mixed types) that need intelligent interpretation.

The approach we've implemented actually addresses many of your performance concerns. Our MCP tool already optimizes data flow by converting NLP → SQL → aggregated data, returning chart-ready datasets (not raw records). We return summarized data like ["Q1", "Q2", "Q3"] and , not thousands of individual records. The LLM handles the "last mile" of intelligent formatting of any data structure into proper ECharts options.

I'm definitely open to exploring a hybrid solution that combines the best of both approaches. We could implement a mode-based system where known, high-volume data sources use query mode with direct backend connections, while arbitrary/unknown data uses our current embedded approach. We could also add an optional backend integration layer that detects if incoming data matches known schemas and routes accordingly, with fallback to our current approach for arbitrary data.

I'll admit I'm not deeply familiar with CubeJS, but from what I've researched, it seems to require predefined data models that need to be configured upfront, known database schemas with established relationships, and standardized query patterns within the OLAP paradigm. While this works great for traditional BI scenarios, it can't handle the truly dynamic, arbitrary data visualization that our system aims to provide. Our current approach can create charts for literally any data structure that an MCP tool can return.

Would you be interested in collaborating on defining what a hybrid approach might look like? I think there's definitely value in keeping the current flexible approach as the default for arbitrary data, adding query-based optimization for specific high-volume use cases, and creating a configuration layer that lets users choose the appropriate mode.

I'd love to hear your thoughts on this direction and whether you have specific backend integration patterns in mind that would work well with ECharts. Thanks again for the thoughtful feedback - it's exactly this kind of architectural discussion that makes features better!

@rohitpathak21
Copy link
Author

Possible solution: Provide a way to specify a url or mcp resource instead of the echarts json. The url could then be fetched to get the echarts json itself. Then the LLM would just need to correctly copy-paste the url / resource identifier.

Could possible be used for other artifacts as well. If the llm wants to "see" the data behind the graph itself, it could still do so by fetching the resource or url.

I still like the ability to provide the echarts json directly though since it might be useful even without integrating a specialized mcp server (just by using server instructions for echarts).

@fstadt @owengo
You both raise good points, but I think there's a key question: where should the intelligence live?

The URL/resource approach shifts "arbitrary data handling" from the LLM (where it excels) to my rendering logic (where it becomes much more complex). When fetching from a URL, the data format can still be anything - nested objects, flat arrays, unknown schemas, etc.

The core value of my approach is using the LLM as an intelligent data transformation layer. It handles pattern recognition and makes smart visualization decisions that would be exponentially harder to code programmatically.

The performance concerns assume large raw datasets, but our MCP tools return pre-aggregated, chart-ready data. The LLM does "last mile" intelligent formatting, not heavy data processing.

I'm open to a hybrid approach for high-volume known schemas, but for "create charts from any data source," the LLM-based approach provides flexibility that's hard to replicate otherwise.

Thoughts on preserving this intelligent data handling while addressing scalability concerns?

@@ -0,0 +1,182 @@
const dedent = require('dedent');
const { ChartModes, EModelEndpoint } = require('librechat-data-provider');

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'EModelEndpoint' is assigned a value but never used. Allowed unused vars must match /^_/u.
Current date: ${new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' })}
`;

const generateChartsPrompt = ({ charts, endpoint }) => {

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'endpoint' is defined but never used. Allowed unused args must match /^_/u.
Comment on lines 35 to 37
<BarChartIcon className="h-4 w-4" />
Bar Chart
</button>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string: <button
onClick={() => onToggle('bar')}
aria-label="Switch to bar chart view"
className={cn(
'flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-all duration-200',
activeChart === 'bar'
? 'bg-white text-gray-900 shadow-sm dark:bg-indigo-600 dark:text-white dark:shadow-indigo-500/25'
: 'text-gray-600 hover:text-gray-900 dark:text-slate-300 dark:hover:bg-slate-700/50 dark:hover:text-white',
)}
>

Bar Chart
Comment on lines 48 to 50
<ActivityLogIcon className="h-4 w-4" />
Line Chart
</button>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string: <button
onClick={() => onToggle('line')}
aria-label="Switch to line chart view"
className={cn(
'flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-all duration-200',
activeChart === 'line'
? 'bg-white text-gray-900 shadow-sm dark:bg-indigo-600 dark:text-white dark:shadow-indigo-500/25'
: 'text-gray-600 hover:text-gray-900 dark:text-slate-300 dark:hover:bg-slate-700/50 dark:hover:text-white',
)}
>

Line Chart

const ChartWithCustomLegend = ({
data,
complexity,

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'complexity' is defined but never used. Allowed unused args must match /^_/u.
return (
<div className="flex items-center justify-center rounded-lg border border-red-200 bg-red-50 p-8 dark:border-red-800 dark:bg-red-900/20">
<div className="text-center">
<p className="font-medium text-red-600 dark:text-red-400">Chart rendering failed</p>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string: Chart rendering failed
Comment on lines 299 to 301
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
Unable to display this chart
</p>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string:
Unable to display this chart
clipRule="evenodd"
/>
</svg>
<span className="font-medium">Failed to render charts for the requested data</span>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string: Failed to render charts for the requested data
Comment on lines 23 to 26
<p className="mt-2 text-sm text-red-600 dark:text-red-400">
The chart data could not be processed. Please try rephrasing your request or check the data
format.
</p>

Check failure

Code scanning / ESLint

disallow literal string Error

disallow literal string:
The chart data could not be processed. Please try rephrasing your request or check the data
format.
@ronak21691
Copy link

@danny-avila is there plan to get this into main? We were thinking to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants