AI advisor plugins
The AI Assistant workbench is a host. It does not know about Data Vault models, lineage views, or your metadata editor. You add those by shipping two things in your plugin:
-
An
@AiAdvisorPlugin/IAiAdvisor— prompt, inclusions, optional graph edits. -
One or more
@GuiToolbarElement/@GuiContextActionmethods on the editor that already owns the subject — toolbar, canvas, table, note, …
Hop never enumerates locations. A location is a string you invent.
The user-facing workbench is documented in AI Assistant. This page is the plugin contract.
Mental model
Your file type / metadata editor
└── @GuiToolbarElement / @GuiContextAction
└── builds AiAdvisorOpenRequest
location, areaLabel, artifact, preferFloatingWindow
└── HopGui.openAiAdvisorSession(request)
└── extension point HopGuiAiAdvisorOpenSession
└── hop-tech-ai workbench (no-op if the plugin is absent)
Your IAiAdvisor (@AiAdvisorPlugin) // omit classLoaderGroup
└── locations = { "data-vault-graph" } // same string
└── buildPrompt(AiAdvisorRequest) // cast artifact, honour inclusions
└── optional parseResponse / previewProposal / validate / apply / afterApply / summarizeApplied
Three JARs stay decoupled:
| Module | What you may depend on |
|---|---|
|
|
|
|
|
The workbench, Language Model Chat, langchain4j.
Third-party advisor JARs must not set |
If plugins/tech/ai is missing, openAiAdvisorSession is a silent no-op.
That is how privacy-sensitive installs remove AI.
Location ids
AiAdvisorLocations in hop-core only lists ids Hop itself uses:
| Constant | When Hop uses it |
|---|---|
|
Pipeline canvas and pipeline toolbar AI Help |
|
Workflow canvas and workflow toolbar AI Help |
|
|
Everything else is yours. Typical ids for hopper-edw-style plugins:
-
data-vault-graph -
business-vault-graph -
dimensional-graph -
source-model-graph -
lineage-view -
execution-map -
a metadata type key (
rdbms,neo4j-connection, …) when Help lives on that editor
Put the same string on @AiAdvisorPlugin(locations = {…}) and on AiAdvisorOpenRequest.setLocation(…).
The workbench does not validate the id against a registry.
Empty locations() means the workbench may offer the advisor on any session.
Open a session from your GUI
Build the request in hop-core types only, then hand it to HopGui.
The class names in the example are illustrative (a Data Vault graph).
Replace them with your file type’s graph, context id and toolbar root.
@GuiPlugin
public class VaultAiGuiPlugin {
public static final String LOCATION = "data-vault-graph";
@GuiToolbarElement(
root = HopGuiVaultGraph.GUI_PLUGIN_TOOLBAR_PARENT_ID,
id = "HopGuiVaultGraph-ToolBar-10048-ai-help",
toolTip = "i18n::HopGuiVaultGraph.Toolbar.AiHelp.Tooltip",
image = "ai-provider.svg")
public void openFromToolbar() {
HopGuiVaultGraph graph = /* active vault graph */;
if (graph == null || graph.getModel() == null) {
return;
}
open(graph, graph.getModel(), null);
}
@GuiContextAction(
id = "vault-graph-ai-help",
parentId = HopGuiVaultContext.CONTEXT_ID,
type = GuiActionType.Modify,
name = "i18n::HopGuiVaultGraph.AiHelp.Name",
tooltip = "i18n::HopGuiVaultGraph.AiHelp.Tooltip",
image = "ai-provider.svg",
category = "Help",
categoryOrder = "1")
public void openFromCanvas(HopGuiVaultContext context) {
if (context.getVaultGraph() != null) {
open(context.getVaultGraph(), context.getVaultGraph().getModel(), null);
}
}
private static void open(HopGuiVaultGraph graph, Object model, String focusName) {
AiAdvisorOpenRequest request = new AiAdvisorOpenRequest();
request.setAdvisorPluginId("data-vault-advisor");
request.setLocation(LOCATION);
request.setAreaLabel("Data Vault");
request.setPreferFloatingWindow(true);
request.setArtifact(model);
request.setArtifactName(model.toString()); // a stable display name
request.setArtifactKind("data-vault");
request.setTitle(model.toString());
request.setFocusNodeName(focusName);
try {
graph.getHopGui().openAiAdvisorSession(request);
} catch (HopException e) {
// log; the extension point is absent when hop-tech-ai is not installed
}
}
}
PipelineAiGuiPlugin / WorkflowAiGuiPlugin in plugins/tech/ai are the same pattern for Hop’s own graphs.
AiAdvisorOpenRequest fields
| Field | Role |
|---|---|
|
|
|
Free-form id. Session reuse keys on this plus advisor id and artifact name. |
|
Session-tree group ("Pipelines", "Data Vault", "Lineage"). Falls back to "General" when empty. |
|
|
|
The open object ( |
|
Reuse and display.
|
|
Optional transform, action, table, … the user right-clicked. |
|
Optional |
|
Default |
|
A |
If a dialog or dock is already open, that layout is reused even when preferFloatingWindow is true.
Implement IAiAdvisor
Keep langchain4j and SWT off this class.
buildPrompt must not call the model; the workbench does that through Language Model Chat.
@AiAdvisorPlugin(
id = "data-vault-advisor",
name = "i18n::DataVaultAiAdvisor.Name",
description = "i18n::DataVaultAiAdvisor.Description",
image = "ai-provider.svg",
locations = {"data-vault-graph"})
public class DataVaultAiAdvisor implements IAiAdvisor {
public static final String ID = "data-vault-advisor";
@Override
public String getId() {
return ID;
}
@Override
public String getName() {
return BaseMessages.getString(PKG, "DataVaultAiAdvisor.Name");
}
@Override
public String[] getLocations() {
return new String[] {"data-vault-graph"};
}
@Override
public List<AiAdvisorScenario> listScenarios() {
return List.of(
new AiAdvisorScenario("dv-general", "General", "Questions about this vault model"),
new AiAdvisorScenario("dv-hub", "Hub design", "Business keys and hub grain"));
}
@Override
public List<String> listBaselineSharing() {
return List.of("model outline");
}
@Override
public List<AiAdvisorInclusion> listInclusions() {
return List.of(
new AiAdvisorInclusion(
"checks",
"Include model check results",
false,
"Validation messages for this model",
"check results"),
catalogInclusion(),
new AiAdvisorInclusion(
"metadata",
"Include selected metadata",
false,
"JSON of metadata objects the user picks",
"metadata"));
}
private static AiAdvisorInclusion catalogInclusion() {
AiAdvisorInclusion inclusion =
new AiAdvisorInclusion(
"catalog",
"Include catalog sources",
false,
"Record definitions the user picks",
"catalog sources");
inclusion.setPicker(true);
inclusion.setMultiSelect(true);
return inclusion;
}
@Override
public List<AiAdvisorInclusionChoice> listInclusionChoices(
String inclusionId, AiAdvisorRequest request) {
if (!"catalog".equals(inclusionId)) {
return List.of();
}
return List.of(
new AiAdvisorInclusionChoice("SRC_ORDERS", "Orders"),
new AiAdvisorInclusionChoice("SRC_CUSTOMER", "Customer"));
}
@Override
public AiAdvisorPrompt buildPrompt(AiAdvisorRequest request) throws HopException {
if (!(request.getArtifact() instanceof MyVaultModel model)) {
throw new HopException("No Data Vault model is bound to this session.");
}
String system = loadPreambleAndScenario(request.getScenarioId());
StringBuilder user = new StringBuilder();
user.append("User question:\n").append(request.getUserPrompt()).append("\n\n");
user.append("Model structure JSON:\n").append(serializeStructure(model)).append("\n\n");
if (request.inclusionEnabled("checks")) {
user.append("Check results JSON:\n").append(serializeChecks(model, request)).append("\n\n");
}
if (request.inclusionEnabled("catalog")) {
user.append("Catalog sources: ")
.append(request.selectedInclusionIds("catalog"))
.append("\n\n");
}
// Workbench appends selected metadata when inclusion id is "metadata".
return new AiAdvisorPrompt(system, user.toString());
}
}
Inclusions
The workbench shows a collapsible Sharing line: a short summary of what this send includes (question, any baseline the advisor always sends, then each opted-in inclusion). Expand it to see the checkboxes.
Each AiAdvisorInclusion is one optional extra.
Best practice: defaultSelected = false for sensitive extras (logs, full XML, selected metadata).
A plugin-id catalog may default on so hop_proposals can use real plugin ids.
listBaselineSharing() supplies the short phrases for context you always put in the prompt (for example graph structure).
Leave it empty if the question is the only baseline.
| Id | Workbench behaviour |
|---|---|
any string you choose |
Checkbox only.
You honour |
any string with |
Checkbox and a Select… button.
The workbench calls |
|
Checkbox and a Select metadata… picker of Hop |
Ids checks, catalog, xml and logs are what the bundled pipeline/workflow advisors use.
They are not reserved; using the same ids on another advisor is a convention, not a requirement.
Default-on inclusions cost tokens on every send. Default-off inclusions (full XML, logs, metadata) are the ones that can leak secrets or personal data.
AiAdvisorRequest on each turn
| Field | Set by |
|---|---|
|
The open request. |
|
The session (log excerpt is captured on the UI thread). |
|
The session pane checkboxes, Hop metadata picker, and generic Select… pickers. |
|
The combo and the question field. |
|
|
|
Copied from the session (which came from |
|
The Hop GUI session. |
|
What the user applied from earlier proposal blocks ( |
After buildPrompt returns, the workbench appends standing notes to the system prompt, in this order:
-
Plugin files (no Hop-tech-ai dependency):
-
{plugin-folder}/ai-context.md— every advisor in that plugin -
{plugin-folder}/ai-context/<advisor-id>.md— one advisor (for exampleai-context/data-vault-advisor.md) -
The same paths on the plugin classpath (JAR root) if the folder file is missing
-
The folder file wins when both exist, so an install can edit notes without rebuilding
-
-
IAiAdvisor.getStandingContext()when non-empty (computed notes; prefer files) -
Configuration → Extra context and Context files (default
${PROJECT_HOME}/AGENTS.md)
Missing files are skipped. Your buildPrompt does not load these.
Proposals (optional)
The default parseResponse extracts ` `dv_proposals `).
The workbench never parses proposals itself.hop_proposals ` JSON blocks (`AiProposalParser in hop-core).
Override parseResponse only when you use a different fence (for example `
Proposal type is a free string (ADD_TRANSFORM, CREATE_HUB, CLIPBOARD_TRANSFORMS, SAVE_METADATA, …).
Unknown types must not be dropped — validate them as blocked with a reason, or apply them in your own applier.
The workbench itself copies CLIPBOARD_TRANSFORMS, CLIPBOARD_ACTIONS, and CLIPBOARD_METADATA to the system clipboard after applyProposals, and saves SAVE_METADATA through the metadata serializer.
Pipeline and workflow appliers skip those types so a mixed selection still applies graph edits (REPLACE_TRANSFORM / REPLACE_ACTION keep name and location).
Third-party advisors can emit the same type names without depending on hop-tech-ai appliers.
Override previewProposal to show domain text (DDL, diffs) in the review dialog.
Return null to keep the default parameter list.
Override summarizeApplied so follow-up turns get ADD_HUB: Create H_CUSTOMER instead of unknown change.
The default is type: description.
Pipeline/workflow advisors already produce the richer ADD_TRANSFORM: name (pluginId) wording.
After applyProposals succeeds the workbench calls afterApply on the UI thread, then refreshes any Explorer tab whose IHopFileTypeHandler.getSubject() is the session artifact (setChanged on Hop graphs, redraw / updateGui on other handlers).
Custom file types should still implement afterApply for undo using request.getAttributes().get(AiAdvisorRequest.ATTR_HOP_GUI) and request.getArtifact().
Chat-only advisors leave apply / afterApply / summarize as the defaults.
Classloading
IAiAdvisor lives in hop-core.
Two plugin classloaders both see the same interface.
PluginRegistry.loadClass(…, IAiAdvisor.class) does not require a shared group.
Set classLoaderGroup = "hop-ai" only on classes that live in plugins/tech/ai (or that must load langchain4j / org.apache.hop.ai.engine.*).
Third-party advisor plugins:
-
@AiAdvisorPlugin— omitclassLoaderGroup(empty default). -
@GuiPlugintoolbar/context that only buildsAiAdvisorOpenRequestand callsHopGui.openAiAdvisorSession— omitclassLoaderGroup. -
Compile against
hop-core+hop-uionly. Never compile againsthop-tech-ai. -
Do not bundle langchain4j.
If plugins/tech/ai is removed, openAiAdvisorSession is still a silent no-op; the third-party advisor plugin still loads.
The workbench combo lists advisors whose locations() match the session (plus advisors with empty locations).
A data-vault-graph session does not offer Pipeline AI Help.
The unbound perspective lists every advisor.
Do not put hopper-edw location ids in AiAdvisorLocations.
Privacy
The assistant sends whatever you put in the prompt to an external service.
-
Never hard-code secrets in the artifact. Use environment variables or a variable resolver.
-
Redact passwords, tokens and API keys in XML, logs and JSON you append. Named fields matching
password,secret,token,api-keyare redacted by the workbench for XML, logs and selected metadata; literals in SQL, notes and oddly named fields are not. -
Keep personal records out of sample rows, notes and logs you include.
-
Default new inclusions off when they send bulk XML, logs or metadata.
Checklist
-
Invent a location id. Do not add it to
AiAdvisorLocations. -
Implement
IAiAdvisorwith that id onlocations. OmitclassLoaderGroup. -
Add toolbar and/or context-menu plugins on the editor that owns the subject (also omit
classLoaderGroup). -
Build
AiAdvisorOpenRequest(location, area label, artifact, attributes,preferFloatingWindowwhen the subject should stay visible). -
Call
hopGui.openAiAdvisorSession(request)— notAiAdvisorViews. -
Honour inclusions in
buildPrompt; use idmetadatafor Hop metadata,picker = true+listInclusionChoicesfor anything else (catalog names, …). -
Keep
defaultSelected = falsefor sensitive extras. A plugin-id catalog may default on. SetsummaryandlistBaselineSharing()so the collapsed Sharing line is accurate. -
Keep SWT and langchain4j off
IAiAdvisor. -
Ship standing notes as
ai-context.mdand/orai-context/<id>.mdin the plugin folder (or JAR root). -
Emit
hop_proposalsunless you overrideparseResponse. OverridepreviewProposalfor custom review text. -
Implement
afterApplyandsummarizeAppliedif you apply custom proposal types. -
Verify AI still disappears when
plugins/tech/aiis removed, and that your advisor JAR still loads.