Class ChartAIController
- All Implemented Interfaces:
AIController
This controller provides tools that allow the LLM to query database schemas
and create or update chart visualizations based on natural language requests.
Attach it to an AIOrchestrator via
AIOrchestrator.Builder.withController(AIController) to expose its
tools to the LLM. Workflow instructions are delivered through the description
of the get_chart_instructions tool, which the LLM reads as part of
the tool manifest. The controller's workflow tells the model that where an
application's system prompt conflicts with it, the system prompt wins, so a
step can be adjusted without subclassing.
var controller = new ChartAIController(chart, databaseProvider);
AIOrchestrator orchestrator = AIOrchestrator
.builder(llmProvider, systemPrompt).withController(controller)
.withMessageList(messageList).build();
State changes requested by the LLM are deferred and applied in
onResponse(ResponseListener.ResponseEvent) on the success path,
avoiding partial state and multiple redraws during a multi-tool LLM turn. The
chart state is stored directly on the Chart component, so it survives
serialization.
If the LLM turn fails, onResponse(ResponseListener.ResponseEvent)
fires with the cause ? pending changes are discarded and the chart keeps its
last successfully-rendered state.
The controller sends the LLM nothing from the query results: when it reads
the chart state, it gets the SQL queries and the configuration it has set
itself, not the series, axis categories or other values the chart builds from
the rows. Only the schema description your DatabaseProvider returns
from DatabaseProvider.getSchema() and the message of a
ToolException it throws reach the LLM as is, so keep row values out
of both.
Data conversion from SQL query results to chart series is handled by a
DataConverter. A default implementation is used unless overridden via
setDataConverter(DataConverter).
Serialization: This controller is not serialized with the
orchestrator. After deserialization, create a new controller and restore
transient dependencies via reconnect(provider) .withController(controller).apply(). The chart
data can be captured via getState() and re-applied via
restoreState(ChartState):
var controller = new ChartAIController(chart, databaseProvider);
orchestrator.reconnect(llmProvider).withController(controller).apply();
if (savedState != null) {
controller.restoreState(savedState);
}
Register a listener via addStateChangeListener(SerializableConsumer)
to be notified when the chart state changes, for example to persist
getState() after each successful AI request.
Provider compatibility: The chart tools use optional properties in
their parameter schemas, which are incompatible with OpenAI's strict
tool-calling mode (strict mode requires every property listed under
properties to also appear in required). Strict tool calling
is off by default in both LangChain4J and Spring AI; only users who
explicitly opt in (e.g. strictTools(true) on LangChain4J's
OpenAiStreamingChatModel builder) are affected.
- Since:
- 25.3
- Author:
- Vaadin Ltd
- See Also:
-
Constructor Summary
ConstructorsConstructorDescriptionChartAIController(Chart chart, DatabaseProvider databaseProvider) Creates a new AI chart controller. -
Method Summary
Modifier and TypeMethodDescriptionaddStateChangeListener(SerializableConsumer<ChartState> listener) Adds a listener that is notified when the chart state changes after an AI request completes successfully.getState()Returns the current chart state, including the SQL queries, the chart configuration and the part of it the LLM has set.getTools()Returns the tools this controller exposes to the LLM.voidCalled when the turn ends: normally when the LLM stream has completed, successfully or with an error, but also when the turn fails before a stream ever opens.voidrestoreState(ChartState state) Restores a previously saved chart state.voidsetDataConverter(DataConverter dataConverter) Sets a custom data converter for transforming query results into chart series data.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface com.vaadin.flow.component.ai.orchestrator.AIController
onRequest
-
Constructor Details
-
ChartAIController
Creates a new AI chart controller.- Parameters:
chart- the chart component to update, notnulldatabaseProvider- the database provider for schema and query execution, notnull
-
-
Method Details
-
setDataConverter
Sets a custom data converter for transforming query results into chart series data. Replaces the default converter used to produce series points from the rows returned by the configured SQL queries.- Parameters:
dataConverter- the data converter to use, notnull
-
getTools
Description copied from interface:AIControllerReturns the tools this controller exposes to the LLM.- Specified by:
getToolsin interfaceAIController- Returns:
- list of tools, or empty list if controller provides no tools
-
getState
Returns the current chart state, including the SQL queries, the chart configuration and the part of it the LLM has set. Returnsnullif the chart has no data queries.- Returns:
- the current state, or
null
-
restoreState
Restores a previously saved chart state. Applies the configuration, sets the queries, and re-renders the chart.Does not fire state change listeners.
- Parameters:
state- the state to restore, notnull
-
addStateChangeListener
Adds a listener that is notified when the chart state changes after an AI request completes successfully. This is typically used to persist the chart state ? for example by callinggetState()and saving the result so that it can be reapplied withrestoreState(ChartState)after deserialization.The listener is not fired by
restoreState(ChartState).- Parameters:
listener- the listener, notnull- Returns:
- a registration for removing the listener
-
onResponse
Description copied from interface:AIControllerCalled when the turn ends: normally when the LLM stream has completed, successfully or with an error, but also when the turn fails before a stream ever opens. The call runs throughui.access(), so the session lock is held and Vaadin thread locals are bound ? though not necessarily on a request thread.Fires at most once per prompt. A prompt rejected by the
RequestInterceptorends without firing it, as does a postponed prompt abandoned because its UI was detached. A turn whose UI is detached when it ends also skips the hook, which requiresui.access().On success
ResponseListener.ResponseEvent.getError()is empty; use the call to commit staged state or run deferred UI updates. On failure it carries the cause (stream error, timeout, or any throw on the prompt path before the stream opens); release per-turn state captured inonRequest(locks, pending writes, snapshots) and discard the staged work. Note that a failure beforeAIController.onRequest(RequestListener.RequestEvent)? for example a throwingRequestInterceptor? also fires this method, so it can run without a precedingonRequestcall.An error is not the only abnormal ending. A turn cut off at the model's output limit ends with no error at all, and
ResponseListener.ResponseEvent.getMetadata()carries the finish reason that tells the two apart ? committing staged state on such a turn applies work the model never finished describing. The finish reason is the underlying framework's own word, so a controller that acts on it decides which values matter to it; seeResponseMetadata.The default does nothing. Exceptions thrown from the hook are caught and logged; Errors propagate.
- Specified by:
onResponsein interfaceAIController- Parameters:
event- the outcome of the turn ? response text, error, and provider metadata ? nevernull
-