Class FormAIController
- All Implemented Interfaces:
AIController
AIOrchestrator via
withController(...).
The controller accepts any HasComponents container. It discovers
fields by walking the container's component tree and collecting every
component that implements HasValue. The walk recurses into nested
HasComponents children so layouts containing layouts are handled.
Field identifiers: the controller assigns an opaque UUID to each field
at discovery time and uses that UUID as the field's id in every tool call the
LLM makes. Developers never see UUIDs; the LLM never sees field labels in the
id slot. Semantic meaning travels through each field's description —
the field's label, helper text, and the describe(HasValue, String)
hint.
Binder integration: the two-argument constructor accepts a
Binder. For every named binding (bind("propertyName"),
bindInstanceFields(this), or @PropertyId) the property name
is used as a default field description so the LLM can refer to the field by
its bean-side name. The default only applies when no explicit
describe(HasValue, String) has been registered; calling
describe(...) always wins. Lambda-bound bindings carry no property
name and contribute no default.
Validation: each value the LLM writes is validated immediately after it is applied. A bound field is validated through its binding, so the converter and every registered validator run as one unit; an unbound field that exposes a default validator is validated through that validator. A value that fails validation stays in the field and the failure is reported back to the LLM as a rejection, so it can supply a corrected value within the same turn.
Field locking: while a fill is in progress, every non-ignored field that wasn't already read-only is set to read-only so the user cannot type into a field the AI is about to overwrite. Locks are released when the turn ends, successfully or otherwise. Application code that changes a field's read-only state mid-turn (e.g. from a value-change listener reacting to the LLM's writes) will be overridden when the controller releases its own locks at turn end — applications should avoid toggling read-only state during a fill turn, or reapply it after the turn completes.
Serialization: the controller is not serialized with the orchestrator.
After deserialization, create a new controller against the same form (and
binder, if any) and call
orchestrator.reconnect(provider).withController(controller).apply().
- Author:
- Vaadin Ltd
-
Constructor Summary
ConstructorsConstructorDescriptionFormAIController(T form) Creates a new form AI controller for the given container.FormAIController(T form, com.vaadin.flow.data.binder.Binder<?> binder) Creates a new form AI controller for the given container and binder. -
Method Summary
Modifier and TypeMethodDescriptionAdds a free-form description that the LLM sees alongside the field when deciding what to fill in.getTools()Returns the tools this controller exposes to the LLM.ignore(com.vaadin.flow.component.HasValue<?, ?> field) Hides the given field from the LLM.voidCalled synchronously on the UI thread just before the LLM stream opens.voidonResponse(Throwable error) Called on the UI thread under the session lock when the LLM stream has completed — either successfully or with an error.valueOptions(com.vaadin.flow.component.HasValue<?, String> field, Collection<String> options) Declares a fixed set of labels the LLM may pick for aString-typed field.valueOptions(com.vaadin.flow.component.HasValue<?, String> field, BiFunction<String, Integer, List<String>> query) Declares the set of values the LLM may pick for aString-typed field.<T> FormAIControllervalueOptions(com.vaadin.flow.component.HasValue<?, T> field, Collection<String> options, Function<String, T> toValue) Declares a fixed set of labels the LLM may pick for the field.<T> FormAIControllervalueOptions(com.vaadin.flow.component.HasValue<?, T> field, BiFunction<String, Integer, List<String>> query, Function<String, T> toValue) Declares the set of values the LLM may pick for the field.
-
Constructor Details
-
FormAIController
public FormAIController(T form) Creates a new form AI controller for the given container. Fields are discovered by walking the container's component tree each time the controller is asked for tools, so fields added or removed between turns are picked up automatically.- Type Parameters:
T- the container type- Parameters:
form- the container whose fields the LLM may populate, notnull
-
FormAIController
public FormAIController(T form, com.vaadin.flow.data.binder.Binder<?> binder) Creates a new form AI controller for the given container and binder. For every named binding on the binder, the bean property name is used as a defaultdescriptionwhen the developer has not registered one explicitly; the controller itself still uses an opaque UUID as the field's tool-call id. Lambda-bound bindings carry no property name and contribute no default. A field that is part of the layout but not bound to the supplied binder behaves the same as in the no-binder constructor.- Type Parameters:
T- the container type- Parameters:
form- the container whose fields the LLM may populate, notnullbinder- the binder whose property names default the field descriptions, notnull; use the single-argument constructor for the no-binder case- Throws:
NullPointerException- ifformorbinderisnull
-
-
Method Details
-
describe
Adds a free-form description that the LLM sees alongside the field when deciding what to fill in. Use it to add business semantics that are not implied by the field's label, helper text, or component type (for example clarifying that a numeric field expects a percentage rather than an absolute amount). Later calls for the same field overwrite earlier ones.- Parameters:
field- the field to describe, notnulldescription- the description text, notnull- Returns:
- this controller, for chaining
-
valueOptions
public <T> FormAIController valueOptions(com.vaadin.flow.component.HasValue<?, T> field, BiFunction<String, Integer, List<String>> query, Function<String, T> toValue) Declares the set of values the LLM may pick for the field. The query callback receives a filter string and a limit and returns matching options as the labels the LLM should see; thetoValuefunction converts a chosen label back to the field's value type. Later calls for the same field overwrite earlier ones.Multi-select fields: a multi-select's value type is
Set<Item>, so the typed signature forcestoValueto returnSet<Item>. WritetoValueto return a single-item set per label (e.g.label -> Set.of(itemByLabel.get(label))).- Type Parameters:
T- the field's value type- Parameters:
field- the field whose options the LLM may query, notnullquery- the filter callback returning labels for the LLM, notnulltoValue- converts a chosen label to the field's value type, notnull- Returns:
- this controller, for chaining
-
valueOptions
public FormAIController valueOptions(com.vaadin.flow.component.HasValue<?, String> field, BiFunction<String, Integer, List<String>> query) Declares the set of values the LLM may pick for aString-typed field. The query callback receives a filter string and a limit and returns matching options; each label is used as the field value as-is. Later calls for the same field overwrite earlier ones.- Parameters:
field- the field whose options the LLM may query, notnullquery- the filter callback returning labels for the LLM, notnull- Returns:
- this controller, for chaining
-
valueOptions
public <T> FormAIController valueOptions(com.vaadin.flow.component.HasValue<?, T> field, Collection<String> options, Function<String, T> toValue) Declares a fixed set of labels the LLM may pick for the field. ThetoValuefunction converts a chosen label back to the field's value type. Later calls for the same field overwrite earlier ones.Multi-select fields: a multi-select's value type is
Set<Item>, so the typed signature forcestoValueto returnSet<Item>. WritetoValueto return a single-item set per label (e.g.label -> Set.of(itemByLabel.get(label))).- Type Parameters:
T- the field's value type- Parameters:
field- the field whose options the LLM may pick from, notnulloptions- the labels the LLM may pick from, notnull; a defensive copy is takentoValue- converts a chosen label to the field's value type, notnull- Returns:
- this controller, for chaining
-
valueOptions
public FormAIController valueOptions(com.vaadin.flow.component.HasValue<?, String> field, Collection<String> options) Declares a fixed set of labels the LLM may pick for aString-typed field. Each chosen label is used as the field value as-is. Later calls for the same field overwrite earlier ones.- Parameters:
field- the field whose options the LLM may pick from, notnulloptions- the labels the LLM may pick from, notnull; a defensive copy is taken- Returns:
- this controller, for chaining
-
ignore
Hides the given field from the LLM. The field is excluded from the tool surface and is not locked during a fill. Use this for fields the AI must not read or write (password fields, internal IDs, PII).- Parameters:
field- the field to hide, notnull- Returns:
- this controller, for chaining
-
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
-
onRequest
public void onRequest()Description copied from interface:AIControllerCalled synchronously on the UI thread just before the LLM stream opens. By the time this method fires, the user message and an empty assistant placeholder are already in the message list; the turn is committed to the conversation history and theRequestListeneronly after this method returns successfully. Implementations can prepare for the turn — locking UI surfaces, snapshotting state the tool definitions depend on, and so on.The default does nothing. Throwing from this method aborts the turn before the commit step: the conversation history is unchanged, the request listener is not notified, the LLM stream is not opened, the assistant placeholder is updated to a generic error message,
AIController.onResponse(Throwable)fires with the thrown exception so per-turn state captured before the throw can still be released, and the exception propagates back to the caller of the prompt entry point.- Specified by:
onRequestin interfaceAIController
-
onResponse
Description copied from interface:AIControllerCalled on the UI thread under the session lock when the LLM stream has completed — either successfully or with an error. Every turn fires this exactly once.On success
errorisnull; use the call to commit staged state or run deferred UI updates. On failureerrorcarries the cause (stream error, timeout, or any throw betweenAIController.onRequest()and the start of the stream); release per-turn state captured inonRequest(locks, pending writes, snapshots) and discard the staged work.The default does nothing. Exceptions thrown from the hook are caught and logged; Errors propagate.
- Specified by:
onResponsein interfaceAIController- Parameters:
error- the cause of failure, ornullon success
-