5.8 KiB
AI Select-With Companions
Declaring on a component which other component types must reach the AI together with it, so that the AI server stops hard-coding component knowledge.
1. Goal
When the AI's component selector picks a type that is never used alone, the types it needs have to
be in the filtered catalog too, or the edit model cannot name them: given an Enclosure and told to
drill holes, but without Drill Hole in its catalog, it invented chassis.DrillHole instead of
electromechanical.DrillHole. A SELECTION @AiNote asks the selector to pick the companions, but
a note is advice to a cheap, low-effort call and can be missed. The guarantee has to be
deterministic, and it belongs with the component, not in the server.
2. Current state
@AiNotes onChassisPanel(Enclosure),BlankBoardandDrillHoletell the selector and the edit model when to use Drill Hole and the cutouts and how to place them (see ai-component-notes.md for notes in general).- The guarantee lives in
diylc-ai-server/v1/ai/classes/CircuitContextBuilder.phpas constants and twoifblocks after the selection is merged:ENCLOSURE_CLASS(chassis.ChassisPanel) addsENCLOSURE_HOLE_CLASSES:DrillHole,RectangularCutout,EllipticalCutout,PolygonalCutout;BLANK_BOARD_CLASS(boards.BlankBoard) addsDRILL_HOLE_CLASS.
WIRE_CLASSESsits beside them but is a different kind of rule: wires follow from the construction type, not from a component, so it stays in PHP.- The server reads
catalog_full.jsononly after the selection, to filter it.
3. Design decisions
| # | Decision | Rationale |
|---|---|---|
| D1 | A new single (not repeatable) annotation in diylc-core/.../core/annotations, @AiSelectWith(Class<? extends IDIYComponent<?>>[] value), beside @AiNote |
Class literals keep a rename or move compile-checked; core already knows components only through IDIYComponent, so the boundary holds. |
| D2 | Collected from the class and every superclass, merged without duplicates, like collectNotes |
A base class can declare companions for all its subclasses, the same way it carries notes. |
| D3 | Published only in catalog_full.json, as "selectWith": ["electromechanical.DrillHole", ...] in catalogClassName form, and left out when empty |
The server expands from the full catalog; the selector does not need it, and the index stays small. |
| D4 | One level: the server adds the companions of the selected types, not the companions of those | Nothing needs more today, and a single pass cannot loop. Revisit only if a chain appears. |
| D5 | The @AiNotes stay |
selectWith only guarantees a type is present; the notes say why and how to use it. |
| D6 | Blank Board lists Drill Hole even though mounting holes are optional | One ~1.1K catalog entry the model may ignore is cheaper than a missing class name. |
| D7 | The server skips a companion that is not in its catalog | A catalog and server out of step must not break a request. |
4. Steps
- Annotation.
AiSelectWith.javainorg.diylc.core.annotations, GPL header,@Retention(RUNTIME),@Target(TYPE), Javadoc with@authoras onAiNote. - Components (
diylc-library):ChassisPanel:@AiSelectWith({DrillHole.class, RectangularCutout.class, EllipticalCutout.class, PolygonalCutout.class});BlankBoard:@AiSelectWith(DrillHole.class).
- Generator. In
ComponentCatalogGenerator.buildFullEntry, after the notes, putselectWithfrom acollectSelectWith(Class<?>)that walks the hierarchy likecollectNotesand maps each class through the same conversion ascatalogClassName. - Tests in
diylc-library/.../plugins/chatbot/service/ComponentCatalogGeneratorTest:- the generated Enclosure entry lists the four hole types and Blank Board lists Drill Hole;
- every
selectWithentry of every component is itself aclassNamein the catalog; collectSelectWithmerges a superclass's companions, as the existingcollectNotestests do.
- Regenerate
catalog_full.json/catalog_index.json(runComponentCatalogGeneratorfromdiylc-swing,.run/ComponentCatalogGenerator.run.xml), checkversionmatches the latestupdate.xmlrelease, and copy both intodiylc-ai-server/v1/ai/. - Server. In
CircuitContextBuilder::buildForEditIntent:- load
catalog_full.jsonbefore the selection is merged instead of after; - after
$selectedComponentsis assembled (selection, existing classes, wires, Blank Board for PCB), add theselectWithof every selected type that is in the catalog, thenarray_unique; - delete
ENCLOSURE_CLASS,BLANK_BOARD_CLASS,DRILL_HOLE_CLASS,ENCLOSURE_HOLE_CLASSESand the twoifblocks; keepWIRE_CLASSES. - Order matters: the Blank Board added for PCB must be in the list before the expansion runs.
- load
- Docs.
diylc-ai-server/docs/promptChatbot_flow.md(filtered catalog paragraph) says the companions come fromselectWith; ai-component-notes.md §1 mentions@AiSelectWithnext toScope.SELECTION.
Do step 6 only after the regenerated catalog is on the server: a catalog without selectWith and a
server without the constants would lose the guarantee.
5. Verify
mvn -q -pl diylc-library -am testandmvn -q -pl diylc-swing -am compile.php -l v1/ai/classes/CircuitContextBuilder.php.- A request that selects only the Enclosure ("put an LM386 amp in an enclosure") reaches the edit model with Drill Hole and the three cutouts in its catalog; a PCB request carries Drill Hole.
6. Open
- Whether other types deserve companions. Candidates worth checking against real requests:
a board-mounted pot or jack with its hookup wire is already covered by
WIRE_CLASSES, so look for pairs the selector actually drops before adding any.