Overview
RAG Chatbot — Tool catalog
Catalogo dei "tool" che l'LLM Anthropic puo' invocare via tool_use quando l'utente
fa una richiesta nel chatbot <wuic-rag-chatbot>. Per ogni tool: il kind che
viene emesso in proposed_action_json, un prompt utente canonico che lo triggera,
i campi minimi della proposta e i metadati WUIC modificati al click di "Applica".
Ogni tool ha una corrispondente regola di test end-to-end in
playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs (single source of
truth: se la prompt cambia qui ma non li', il test fallisce). I prompt canonici
sotto sono ESATTAMENTE quelli del test.
Indice
| Kind | Cosa fa | Metadata target |
|---|---|---|
toolbar_action | Aggiunge un button alla toolbar della list-grid | _mtdt__cstom__actions__tabelle |
row_action | Aggiunge un button-action sulla singola riga | _metadati__colonne (mc_voa_class=6) |
table_style | Stile condizionale (classe CSS o inline) sull'intera <tr> | _metadati__u_i__stili__tabelle |
column_style | Stile condizionale sulla singola cella | _metadati__u_i__stili__colonne |
display_formula | Template Angular per la cella (formato compatto, badge, link) | _metadati__colonne.mc_ui_grid_column_data_template |
form_title_formula | Titolo dinamico del form di edit | _metadati__tabelle.md_display_formula |
default_value_callback | Default JS per un campo in INSERT | _metadati__colonne.mc_default_value_callback |
custom_validation | Validazione blocking pre-save | _metadati__colonne.mc_validation_custom_callback |
selection_changed | Trigger al change di un lookup | _metadati__colonne.mc_selection_changed_custom_function |
lifecycle_callback | Hook before_save / after_save / after_load | _metadati__tabelle.md_before_save (et al) |
simple_metadata_update | Update generic di un field semplice (label, pagesize, flag) | _metadati__tabelle / _metadati__colonne |
metadata_column_create | Crea nuova colonna metadata (anche calcolata) | _metadati__colonne (INSERT) |
sql_metadata_field | Scrive uno snippet SQL su un field gated della tabella metadata (super-admin) | _metadati__tabelle.md_join_override et al |
designer_inject | Inietta tool/widget nel canvas del dashboard designer (client-only, no backend) | dashboard designer state |
menu_entry | Aggiunge una voce di menu che apre una route (INSERT idempotente) | _metadati__menu |
suggest_callback | Bottone suggest/autocomplete (sparkles) sul FieldEditor di una colonna | _metadati__colonne.mcsuggestvaluecallback |
workflow_inject | Inietta/modifica nodi e archi nel canvas del workflow designer (client-only) | workflow designer state |
scene3d_inject | Inietta/modifica oggetti nella scena 3D three.js (client-only) | scene3d designer state |
pivot_inject | Costruisce la vista (tabelle/join) e la configurazione pivot (client-only) | pivot-builder state |
report_inject | Genera un report da template descrivendolo a parole | file .mrt nuovo (server) |
appsettings_inject | Prepara modifiche alle impostazioni applicative (client-only) | appsettings editor state |
db_table_create | Crea una tabella nuova sul DB Dati e la registra nei metadata | tabella fisica + _metadati__tabelle |
metadata_scaffold_table | Importa nei metadata una tabella fisica esistente (idempotente) | _metadati__tabelle / __colonne |
metadata_scaffold_view | Importa nei metadata una vista SQL esistente | _metadati__tabelle / __colonne |
metadata_scaffold_column | Mappa nei metadata una colonna fisica non ancora mappata | _metadati__colonne |
menu_entry_move | Sposta/riordina una voce di menu esistente | _metadati__menu |
metadata_column_delete | Rimuove una colonna: DROP fisico + DELETE metadato | _metadati__colonne (DELETE) |
menu_entry_delete | Rimuove una voce di menu | _metadati__menu (DELETE) |
Esempi canonici di prompt
toolbar_action — Button bulk sulla toolbar
Prompt utente:
> Su cities aggiungi un'azione di toolbar 'Archivia selezionate' (icona pi pi-archive) che archivia in bulk i record selezionati via POST /api/cities/bulk-archive con i loro id. Richiedi conferma prima.
Campi richiesti nella proposta: route, label, callback_js. Opzionale
requires_multi_selection:true se il callback opera su datasource.getSelectedRows()
(il backend abilita md_multiple_selection sulla route in tal caso).
row_action — Button sulla singola riga
Prompt utente:
> Su cities aggiungi un button di riga 'Genera PDF' (icona pi pi-file-pdf) che apre /api/cities/{id}/pdf in nuova tab con l'id della riga.
Campi richiesti: route, label, callback_js. Lo scope del callback ha record
(BehaviorSubject map: record.id?.value).
table_style — Stile condizionale riga
Prompt utente:
> Su cities colora in rosso (classe row-danger) le righe con LatestRecordedPopulation inferiore a 1000.
Scope del condition_js: la variabile record e' la riga di griglia, un oggetto
piatto — record.LatestRecordedPopulation vale 192, record.deleted vale
true/false. Vale anche per column_style. E' diverso dai callback di form
(default value, validazione, selection changed), dove i campi sono BehaviorSubject
e si legge con .value / si scrive con .next(...).
Campi richiesti: route, css_class, condition_js. Per i colori della palette
predefinita usa una classe (row-danger / row-warning / row-info / row-success).
Per colori arbitrari emetti css_class con prefisso style: (es.
style:background-color:#9b59b6;color:white) — il framework iniettera' !important
automaticamente e neutralizzera' il bg dei <td> cosi' il colore arriva all'occhio.
column_style — Stile condizionale cella
Prompt utente:
> Su cities colora di rosso SOLO la cella LatestRecordedPopulation (classe cell-danger) quando il valore e' inferiore a 1000, NON l'intera riga.
Campi richiesti: route, column_name, css_class, condition_js.
display_formula — Template cella
Prompt utente:
> Su cities, formatta la colonna LatestRecordedPopulation in formato compatto k/M (es. 12500 -> 12.5k, 1500000 -> 1.5M).
Campi richiesti: route, column_name, template_html. Scope: rowData
(oggetto piatto, NO BehaviorSubject). Supporta Angular pipe standard
(number:'1.1-1', date:, currency:, ecc.), interpolation {{ rowData.x }},
ternario inline, *ngIf blocchi.
form_title_formula — Titolo dinamico edit form
Prompt utente:
> Su cities imposta il titolo dinamico del form di edit a 'Modifica <CityName>' dove <CityName> e' il nome della citta', oppure 'Nuova citta' su INSERT.
Campi richiesti: route, formula_js. Scope: metaInfo, record, datasource,
wtoolbox. Deve ritornare string.
default_value_callback — Default in INSERT
Prompt utente:
> Su cities, la colonna CityName deve avere come valore di default in inserimento la stringa 'Nuova citta'.
Campi richiesti: route, column_name, callback_js. Lo scope del callback ha
record (NUOVO record, plain object), field (MetadatiColonna target), metaInfo,
wtoolbox. Il return e' ignorato — il callback DEVE scrivere il valore in
record[field.mc_nome_colonna].
custom_validation — Validazione blocking
Prompt utente:
> Su cities valida che LatestRecordedPopulation non sia negativo. Blocca il save con messaggio 'La popolazione non puo essere negativa'.
Campi richiesti: route, column_name, callback_js. Scope: record
(BehaviorSubject map), field, vr (ValidationRule: setta vr.message='...'),
wtoolbox. Deve ritornare boolean: true se valido, false per bloccare.
selection_changed — Trigger al change di un lookup
Prompt utente:
> Su cities, al cambio del campo StateProvinceID (selection_changed), normalizza il valore tutto in maiuscolo via record.StateProvinceID.next(...).
Campi richiesti: route, column_name, callback_js. Scope: record, value,
datasource, wtoolbox. L'oggetto lookup risolto e' in record.<col>__lookup_obj.value.
lifecycle_callback — Hook before/after save/load
Prompt utente:
> Su cities, prima del save (event=before_save) metti il campo CityName in maiuscolo via record.CityName.next(record.CityName.value.toUpperCase()).
Campi richiesti: route, event (before_save / after_save / after_load),
callback_js. Scope: record (BehaviorSubject map), datasource, wtoolbox.
simple_metadata_update — Update di field semplici
Prompt utente (esempio A — label colonna):
> Modifica il titolo della colonna LatestRecordedPopulation in 'Official Population' su cities.
Prompt utente (esempio B — pagesize tabella):
> Imposta il pagesize della tabella cities a 50.
Campi richiesti: route, target (column / table), field_label (label friendly),
value. Il backend mappa il field_label al campo SQL fisico via mappa server-side
(circa 29 field coperti: header label, page size, hide-in-list, ecc.).
metadata_column_create — Crea colonna metadata
Prompt utente:
> Su cities crea una colonna calcolata e2e_total_chars di tipo number che calcola LEN sul CityName. La formula a livello metadato deve essere LEN([Application].[Cities].[CityName]) e la colonna deve essere is_computed=true (non fisica nel DB).
Campi richiesti: route, column_name, ui_column_type, is_computed. Quando
is_computed=true deve essere presente anche computed_formula.
sql_metadata_field — Snippet SQL su field gated (super-admin)
Prompt utente:
> Su cities applica al campo md_join_override il join SQL: LEFT JOIN [Application].[People] AS [e2e_test_people] ON 1=0. E' un test e2e no-op, scopo di verificare che il tool propose_sql_metadata_field scriva correttamente in _metadati__tabelle.mdjoinoverride. Dbms target: mssql.
Campi richiesti: target_table, target_row_key, field_name, sql_snippet,
dbms_target. Auth: super-admin (D3 gate). Il prompt utente DEVE specificare il
dbms target — il backend non lo desume.
designer_inject — Iniezione tool nel dashboard designer
Client-only kind: applicato non dal backend ma da un handler registrato dal
designer.component via ChatbotHostRegistryService. Vedi
docs/pages/_internal/designer-tool-catalog.md per il catalogo dei tool palette.
menu_entry — Voce di menu
Prompt utente:
> Aggiungi una voce di menu 'Scadenzario' che apre la route cities.
Campi richiesti: route, label (opzionali icon, tooltip, parent_id). Il
framework persiste in _metadati__menu con INSERT idempotente (non duplica se
esiste gia' una voce con stessa uri+label); md_id (dalla route), mm_id e
mmordine (in coda) li calcola il backend — il modello passa solo route + label.
suggest_callback — Suggest/autocomplete su un campo
Prompt utente:
> Sul campo CityName di cities aggiungi un suggest che propone il nome in maiuscolo.
Campi richiesti: route, column_name, callback_js. Aggiunge il bottone
"suggest" (icona sparkles) accanto al FieldEditor della colonna: al click esegue
il callback per proporre/compilare un valore. La colonna DEVE esistere: se il campo
citato non e' tra le colonne reali il modello fa una clarification (mai sostituire
con un'altra colonna). Campo SQL: _metadati__colonne.mcsuggestvaluecallback.
workflow_inject — Iniezione nel workflow designer
Prompt utente:
> Aggiungi un nodo condizione al workflow.
Client-only kind (come designer_inject): sulla pagina /workflow-designer
l'apply agisce sullo state del canvas (handler
workflow-designer.component.onChatbotProposedWorkflowAction), la persistenza
avviene solo al click "Salva grafo". Sei action_type: inject (default —
aggiunge nodes[] e connections[], puo' costruire un intero scheletro in un
colpo), piu' le azioni di modifica (connect/disconnect, insert-between, set-prop,
remove). Vedi workflow-designer per i tipi di nodo.
scene3d_inject — Iniezione nella scena 3D
Prompt utente:
> Aggiungi un cubo rosso e una sfera blu alla scena.
Client-only kind: sulla pagina /scene3d_designer (editor three.js) supporta 3
action_type: inject (aggiunge oggetti — anche molti in un colpo: "crea una
scena con piano, luce direzionale e 3 sfere", mesh-repeater su route dati come
grafico a barre 3D o kanban 3D), piu' set-prop e remove su oggetti esistenti
(target_name, prop_name, value). Compila objects[] per l'inject.
Quiet tools — nessuna card "Applica"
Tre tool non producono una proposta ma modellano la conversazione:
remember_fact/forget_fact— pinnano/rimuovono fatti ad alta priorita'
("questo progetto usa colonne snake_case") che sopravvivono alla summarization
della history.
suggest_followups— i chip cliccabili sotto ogni risposta che propongono la
domanda successiva.
Robustezza del routing — varianti non-banali
I prompt canonici sopra sono un solo esempio per kind. Il routing del tool è
però robusto a formulazioni diverse: la suite
playwright/docs/rag-chatbot/rag-chatbot--tools-variations.mjs prova **5+ varianti
per kind** (riformulazioni semantiche, vincoli realistici, coppie disambiguanti),
molte derivate da prompt reali, e misura il routing-rate su più ripetizioni.
Il routing è risultato language-agnostic (italiano ed inglese instradano allo
stesso modo — WUIC_RAG_VAR_LANG=en).
Esempi alternativi che instradano correttamente (oltre ai canonici):
toolbar_action→ «metti in cima alla griglia un pulsante che esporta in CSV le righe spuntate»row_action→ «su ogni riga un'icona occhio che apre la scheda di quella riga in una nuova tab»table_style→ «evidenzia di verde le righe modificate di recente»column_style→ «metti uno sfondo giallo soltanto sulla cella CityName quando è vuota»display_formula→ «visualizza la popolazione con il separatore delle migliaia»custom_validation→ «impedisci di salvare se ValidTo è precedente a ValidFrom»simple_metadata_update→ «nascondi la colonna LastEditedBy dalla lista» (nota: «filtro di default» èsimple_metadata_updateconfield_label=default_filter, NONsql_metadata_field)metadata_column_create→ «aggiungi una colonna calcolata che divide la popolazione per 1000»designer_inject→ «crea un master-detail con 2 grid provincie e città», «componi un layout a 3 colonne con tre grid», «metti due grid affiancate»
Veri negativi (NON devono emettere un tool): richieste di esempio di codice
(«mi dai un esempio di toolbar action senza applicarlo?») o domande concettuali
restano in risposta testuale senza proporre alcuna azione.
Designer — verifica del canvas e limiti della palette
rag-chatbot--designer-canvas-verify.mjs verifica che ogni prompt designer produca
davvero i componenti attesi nel canvas (istogramma per tipo, non solo presenza
nel DOM). I grafici si ottengono con un DATAREPEATER in modalità
action: "chart" (non esiste un tool CHART separato — il chart è un datarepeater
in modalità chart, configurabile bar/line/pie via «Configura chart»). Altre action
del datarepeater: list (grid, default), edit/dialog/detail (scheda/form),
map, scheduler, calendar, kanban, tree, carousel, pivot, spreadsheet.
I titoli testuali sono emessi come LABEL/H1.
#### Config archetipo inline (archetype_config)
Un nodo DATAREPEATER del designer_inject può portare un campo archetype_config
= il contenuto di md_props_bag.archetypes.<action>. Viene applicato in-memory al
metaInfo.tableMetadata del datasource bindato (nessuna chiamata server) tramite lo
stesso path di "Configura chart" (customProps_<action> + ds.fetchData() +
propertyTreeBuilder), e persiste alla serializzazione del datasource al save della
dashboard. L'applicazione è posticipata finché il datasource bindato è materializzato.
- chart:
dataOptionsDEVE includeredataProperty:"dato"(chiave dell'array dati
che parseData legge — senza, il grafico resta vuoto) +
datasets:[{label, labelField:'<col etichette>', dataField:'<col valori>'}].
- map:
markerColorField,titleField,zoom,center:{lat,lng}(i marker
prendono le coordinate dalla colonna point auto-rilevata della route).
- Altri archetipi (
scheduler,kanban,tree, …) seguono lo stesso meccanismo con i
rispettivi campi (vedi mapOptions/kanbanOptions/schedulerOptions).
Verificato: «grafico a torta della popolazione per provincia» → pie con dati; «città su
una mappa colorate per provincia» → Google Map con un marker per città.
> ⚠️ Test full-UI del designer: ogni prompt va eseguito su conversazione pulita
> (il chatbot persiste l'id sessione in localStorage['wuic_rag_last_session_id']
> e lo ripristina al boot — senza reset la history di un prompt contamina il
> successivo). Inoltre richiede backend in ASPNETCORE_ENVIRONMENT=Development
> (CORS per il frontend dev) + frontend attivo.
Colonne lookup — confronto sul valore reale (ID)
Per una richiesta del tipo «colora la riga se <colonna lookup> = *<valore
visualizzato>*» (es. su cities, «provincia = Virginia») il valore VERO nel record
dei callback JS è l'ID in record.<column> (es. record.StateProvinceID), non la
stringa visualizzata nella cella né l'alias di join SQL. Il modello quindi:
1. risolve il valore visualizzato nel suo ID interrogando la tabella di lookup (non le
righe caricate in grid) via
request_metadata_detail{detail:'lookup_value', route:'<route>', column:'<col>', value:'<value>'};
2. genera il condition_js confrontando l'ID:
return Number(record.StateProvinceID?.value ?? record.StateProvinceID) === 49;request_metadata_detail non è un tool registrato in rag_tools.json: è guidato dal system
prompt e ri-letto dal testo della risposta. Il detail:'lookup_value' è risolto server-side
da RagController.ResolveLookupValueDetail (connessione DATI, non metadata) e ritorna
matched_ids. Se il valore non esiste, il modello fa una clarification senza inventare un
ID (mai confrontare la stringa visualizzata né l'alias di join SQL, che nel record JS non
esiste).
Regression test: rag-chatbot--lookup-value.mjs (subtest resolve + no-match).
Pattern operativo
1. L'utente apre il chatbot (FAB in basso a destra) e scrive il prompt.
2. Il LLM analizza la richiesta + il routeContext iniettato (route corrente, tabella,
colonne) e emette il tool_use con il kind appropriato.
3. La proposta viene mostrata in card con campi editabili:
- I campi NON-body (classe, label, target, ecc.) come <input> di testo.
- Il body del callback come <textarea> o Monaco editor avanzato.
4. L'utente puo' editare entrambi prima del click "Applica".
5. Al click "Applica":
- Se la proposta e' stata editata: POST /api/Rag/UpdateProposedAction ri-scrive
il proposed_action_json nel DB del messaggio.
- POST /api/Rag/ApplyAction rilegge dal DB la proposta e applica i side-effect
sui metadati WUIC + chiama InvalidateMetadataCachesAndSetVersion cosi' il
refresh successivo della route target vede la modifica.
Test docs-driven
Il file playwright/docs/rag-chatbot/rag-chatbot--tools-end-to-end.mjs definisce
l'array TOOLS con una entry per ogni kind. Ogni entry ha:
kind— valore atteso inproposed_action_json;prompt— testo esatto inviato al LLM;assertFields— campi minimi che la proposta deve contenere;verify(api, mdId)— controllo DB post-apply (la riga metadata esiste);verifyDom(page)— controllo runtime (effetto visibile in/cities/list);cleanup(api, mdId)— rollback chirurgico (DELETE / UPDATE NULL).
Per rilanciare solo alcuni kind: WUIC_RAG_TOOLS_ONLY=row_action,table_style npm run test:docs.
Pagine contestuali oltre ai designer
Oltre a /designer, /workflow-designer e /scene3d_designer, l'assistente e'
contestuale anche su queste pagine. Ognuna registra un handler e uno state
provider in ChatbotHostRegistryService: il contesto della pagina viene
iniettato nel prompt e l'apply agisce sullo stato del componente.
> NB: le sezioni sotto usano "Esempio di richiesta" e non il marker
> **Prompt utente:**. I prompt canonici estratti da quel marker devono avere
> una entry corrispondente in rag-chatbot--tools-end-to-end.mjs (suite
> backend-side): questi kind sono invece coperti da suite dedicate, elencate in
> ciascuna sezione.
pivot_inject — vista e configurazione pivot
Pagina: <route>/pivot-builder. Kind CLIENT-ONLY: l'apply muta lo stato del
componente, la persistenza resta al click su "Save Configuration" / "Create View".
Esempio di richiesta: *"unisci ordini e clienti su customer_id e somma il totale
per mese"* → una sola proposta action_type: 'inject' con tables[] + joins[].
Attenzione all'ambiguita': su questa pagina "aggiungi la colonna X" significa
metterla in un asse del pivot, non creare una colonna SQL
(metadata_column_create). La nota di disambiguazione iniettata nel prompt
copre esplicitamente questo caso.
Copertura e2e: playwright/docs/pivot-builder/pivot-builder--chatbot-*.mjs
(5 deterministici + 2 full-UI, incluso il test di disambiguazione).
report_inject — generazione di un report
Pagina: <route>/report-designer. A differenza degli altri *_inject l'apply
scrive subito un file .mrt lato server, riusando lo scaffolder del
framework; il nome e' timestampato e il report aperto non viene sovrascritto.
Esempio di richiesta: *"fai un report raggruppato per stato con il totale della
popolazione"* → template: 'crossgroup'.
Template: bands | crosstab | crossgroup. Aggregazioni: Sum, Avg,
Count, Min, Max. Le colonne per value/totals devono essere numeriche.
Copertura e2e: playwright/docs/report-designer/report-designer--chatbot-*.mjs.
appsettings_inject — impostazioni applicative
Pagina: /appsettings-editor. L'apply prepara le modifiche (compaiono come
"modifiche pendenti"); il salvataggio resta un'azione esplicita dell'utente.
Esempio di richiesta: "porta il timeout delle query a 120 secondi".
Due limiti applicati lato client:
- le chiavi riservate (connection string, credenziali, chiavi API, licenza) sono
mascherate nel dump inviato al modello — quel dump viaggia verso il
provider LLM configurato a ogni messaggio;
- le stesse chiavi, piu'
dbmsefirstRun, sono in denylist di scrittura:
un valore errato non sarebbe una preferenza da correggere ma un backend che
non riparte.
Copertura e2e: playwright/docs/appsettings/appsettings--chatbot-*.mjs.
Scaffolding dalla chat
La pagina di scaffolding e' una route generata a metadata e non ha un componente
Angular host: questi kind sono quindi applicati dal backend
(RagController.ApplyKindCore).
L'introspezione necessaria a comporre le proposte passa da
request_metadata_detail con i detail db_connections, db_databases,
db_tables, db_columns, menu_tree — non da tool propose_*, perche' una
lettura non deve produrre un box "Applica azione". L'elenco delle connessioni
espone solo i nomi logici: le stringhe di connessione sono filtrate.
| Kind | Esempio di richiesta |
|---|---|
db_table_create | "crea la tabella ordini_fornitori" |
metadata_scaffold_table | "aggiungi all'applicazione la tabella Sales.Invoices che esiste gia'" |
metadata_scaffold_view | "importa la vista vw_fatturato_mensile" |
metadata_scaffold_column | "mappa la colonna DueDate che ho aggiunto al database" |
menu_entry_move | "metti Fatture dentro Amministrazione" |
Limiti noti, riportati anche nelle description dei tool:
- la creazione di tabelle e' implementata solo su SQL Server e MySQL; sugli
altri provider la proposta viene rifiutata con un messaggio esplicito, invece
di riuscire a vuoto;
- l'import e' idempotente: rieseguirlo aggiunge solo le colonne nuove, ed e'
il modo corretto per riallineare una route dopo un ALTER TABLE esterno o per
recuperare una creazione andata a meta';
scaffoldDB(import di un intero database),scaffoldStoredefixFKeys
non sono esposti: un'unica conferma con un blast radius illimitato.
Operazioni che rimuovono dati
metadata_column_delete e menu_entry_delete sono irreversibili: la prima
esegue un DROP della colonna fisica.
La conferma e' verificata dal server, non dalla UI: ApplyAction e
ApplyActionDirect richiedono un confirmToken uguale al nome dell'oggetto
(colonna o etichetta di menu), altrimenti rispondono 400 confirmation-required.
Il controllo sta sul server perche' ApplyActionDirect e' il canale dei client
agentici e non ha UI. In chat l'utente deve ridigitare quel nome in un dialog
dedicato.
Guardie aggiuntive negli esecutori:
- una colonna chiave primaria non e' rimovibile;
- su SQL Server, se il nome metadata e quello fisico divergono la rimozione e'
rifiutata (il DROP userebbe il nome sbagliato);
- l'etichetta di menu della proposta viene confrontata con quella corrente: gli
identificativi possono essere riassegnati, quindi una proposta vecchia non deve
poter cancellare la voce sbagliata.
Copertura e2e: playwright/docs/rag-chatbot-tools/rag-chatbot-tools--destructive-confirm-gate.mjs,
--scaffold-not-found.mjs, --scaffold-idempotent.mjs, --menu-move-and-stale.mjs.
Riferimenti
- Componente:
wuic-framework-lib/src/lib/component/rag-chatbot/rag-chatbot.component.ts - Backend dispatcher:
KonvergenceCore/Controllers/RagController.cs(ApplyAction) - Repository:
KonvergenceCore/Services/RagChat/RagChatRepository.cs - Tool definition JSON:
codebase_embeddings/onnx_export/rag_tools.json(passato al LLM) - Cookbook callback: docs/pages/callback-cookbook.md
- Pagina principale: docs/pages/rag-chatbot.md
Screenshot




