Tipologie di nodi
I nodi sono i mattoni che compongono il grafo del Tree Agent.
Ogni volta che si compila con successo un grafo tramite build o update, questo viene convertito in un file pronto per l'esecuzione. Durante questo processo, alcuni nodi, detti virtuali, vengono eliminati e le informazioni in essi contenute trasferite nel file finale. I nodi virtuali sono quelli di tipo: root, component, head, tail.
Nessun nodo può mai essere figlio di più nodi, e tutti i nodi, ad eccezione di quelli virtuali root, head e tail devono sempre avere un genitore!
Per offrire la massima flessibilità, esistono diverse tipologie di nodi, ognuno dotato di un differente set di callback e di proprietà, e con una funzione di esecuzione distinta. Indipendentemente dalla loro tipologia, tutti i nodi non virtuali condividono questo set di proprietà e callback:
name(str): è il nome del nodo;instruction(str | None): è una stringa richiamabile in fase di creazione del prompt;context(str | None): è una stringa richiamabile in fase di creazione del prompt;before(Callable | None): la callbackbefore;after(Callable | None): la callbackafter;is_blocking(bool): èTruese il nodo è bloccante (fase 1 dello step explore),Falsealtrimenti (a livello grafico si ha l'icona 🔒 attiva quando la proprietà èTrue);is_anchor(bool): èTruese il nodo è un anchor (fase 2 dello step explore explore),Falsealtrimenti (a livello grafico si ha l'icona ⚓ attiva quando la proprietà èTrue);similarity_threshold(float | None): se specificato sovrascrive la similarità minima che si deve raggiungere in fase di explore per scendere su un nodo figlio. In caso di unquerynode specifica invece la similarità minima che si deve raggiungere per estrarre i chunk dal vector store.
Seppure le proprietà sopra indicate siano comuni, alcune di queste potrebbero avere dei valori predefiniti per alcune tipologie di nodi, e pertanto apparire nascoste a livello di interfaccia grafica.
Root node
Callback disponibili: before_run after_run
Questo nodo virtuale viene aggiunto automaticamente durante la creazione di un nuovo albero, e non può: nè essere duplicato nè essere cancellato. Il root node è utile per specificare alcune proprietà associate all'intero albero che, come tali, non appartengono ad alcun nodo specifico.
Nell'interfaccia, tramite il nodo root è possibile specificare:
init(Callable | None): funzione di inizializzazione delselfdisponibile a tutti i nodi definiti allo stesso livello dellaroot, quindi al di fuori di ogni componente;before_run(Callable | None): callbackbefore_run;after_run(Callable | None): callbackafter_run;default_models: dove è possibile definire il modello LLM da impiegare di default per ciascuna tipologia di nodo;default_similarity_threshold: dove è possibile definire la similarità minima da raggiungere per scendere in un nodo figlio durante lo stepexplore;memory_agent_props: le proprietà richieste dal Memory Agent.
Tutte le proprietà di default possono essere sovrascritte nodo per nodo tramite apposite proprietà
L'argomento special della callback extend_message contiene:
class SimpleNamespace:
topics: str | None # i topics eventualmente inseriti tramite interfaccia grafica
conversation_goal: str | None # il conversation goal eventualmente inserito tramite interfaccia grafica
L'argomento special della callback before_run contiene:
class SimpleNamespace:
has_state_reset: bool # diventa True quando lo stato è stato resettato rispetto all'ultima interazione per via di un avanzamento di versione con reset abilitato
L'argomento special della callback after_run contiene le seguenti informazioni:
class SpecialLlmNodeContext:
agent_output: AgentOutput # l'oggetto di risposta del TreeAgent
run_events: List[dict] # la catena di eventi che ha portato alla risposta {node_id, type}
Il root può avere solo nodi figli di tipo agent
Agent node
Callback disponibili: fallback
Tramite i nodi di tipo agent è possibile definire più agenti distinti.
L'agent node deve avere come genitore il root node e avere sempre e solo un nodo figlio.
Il nodo agent ha le seguenti proprietà extra:
is_primary(bool): seTrueallora in caso di primo messaggio, in mancanza di una callbackbefore_runche specifica un nodo da eseguire, lo step diexploresarà avviato dall'agente composto dai nodi appartenenti al sottoalbero di questo nodo;role(str) è il ruolo dell'agente. Questa informazione di default sarà accessibile tramite gli argomenti delle callbackpromptdi tutti i nodi appartenenti all'agente;fallback(Callable) funzione di fallback associata.
Sempre e solo un nodo agent ha la proprietà is_primary = True. A livello grafico è possibile riconoscerlo perchè ha l'icona ☆ attiva
L'argomento special delle callback fallback contiene le seguenti informazioni:
class SpecialLlmNodeContext:
error: Exception # l'errore che ha provocato l'esecuzione della fallback
node_id: int # l'id del nodo dove è avvenuto l'errore
LLM node
Callback disponibili: before after prompt response_format
Il nodo di tipo llm è utile quando si vuole richiamare un LLM per fare dei ragionamenti guidati.
Il nodo llm ha le seguenti proprietà extra:
model(str | None): se specificato, sovrascrive il modello LLM utilizzato di default;prompt(Callable): è la callbackprompt;response_format(Callable | None): è la callbackresponse_format.
L'argomento special delle callback after e prompt nel nodo di tipo llm contiene le seguenti informazioni:
class SpecialLlmNodeContext:
prompt_obj: dict | None # l'insieme di informazioni utili per la composizione del prompt
llm_response: str # la risposta prodotta dal LLM
fail_response_format: bool | None # diventa True se è stato impostato un response_format ed il LLM ha fallito la sua applicazione per attempts volte
con prompt_obj:
{
"role": str # il ruolo definito dentro l'agent node a capo del nodo attuale
"context": List[str] # una lista che contiene, concatenati uno dopo l'altro, i contesti definiti nella catena di nodi che parte dal nodo agente e finisce nel nodo corrente
"instructions": str # le istruzioni eventualmente inserite nel nodo attuale
}
Il prompt_obj è disponibile solo quando la callback è prompt. La llm_response è disponibile solo quando la callback è after.
La fail_response_format è disponibile solo quando la callback è after e il nodo ha specificato un response_format, in caso contrario è None.
Switch node
Callback disponibili: before
Il nodo di tipo switch è una versione più specifica e ottimizzata del llm node da utilizzare in quei casi in cui si ha bisogno di eseguire un LLM per individuare il corretto nodo da eseguire.
Il nodo switch ha le seguenti proprietà extra:
model(str | None): se specificato, sovrascrive il modello LLM utilizzato di default;alternatives: permette di indicare quali nodi eseguire in base alle descrizioni associate;
In particolare le alternatives permettono di specificare i possibili nodi da eseguire in base a delle condizioni indicate via testo. Ogni alternatives richiede le seguenti informazioni:
keyword: una parola chiave che descrive la condizione che attiva l'alternativa;node_target: il nodo da raggiungere quando la condizione si verifica secondo la descrizione fornita;step: lo step da settare quando si verifica la condizione;description: condizione che il LLM deve verificare per attivare o meno quell'alternativa;
Seguendo alternatives il framework imposta automaticamente il response_format, il prompt e la callback after.
Oltre alle alternative personalizzate, ogni nodo switch richiede obbligatoriamente la configurazione dell'alternativa speciale FALLBACK. Questa viene attivata quando nessuna delle condizioni definite è soddisfatta, garantendo che l'esecuzione continui sempre su un nodo valido.
Il sistema verifica che la risposta fornita dall'LLM rispetti il response_format indicato. Nel caso in cui:
- il LLM seleziona la condizione associata a
FALLBACK; - oppure il
response_formatviene ignorato per 2 volte consecutive; - oppure il LLM seleziona 0 o più di 1 opzione;
il sistema attiverà comunque l'alternativa associata a FALLBACK. È quindi importante che le alternative siano mutualmente esclusive: alternative sovrapposte aumentano il rischio di atterrare in FALLBACK.
Più alternatives vengono aggiunte maggiore è il rischio che il LLM fallisca nel riconoscimento di quella corretta. In caso di più di 5-6 alternative, considerare di annidarle su più livelli con 2 o più nodi in serie.
Emit node
Callback disponibili: before after prompt emit
Il nodo di tipo emit serve quando si vuole inviare un contentuo all'utente.
Un nodo di tipo emit ha le seguenti proprietà extra:
model(str | None): se specificato, sovrascrive il modello LLM utilizzato di default;emit(Callable) callbackemit;prompt(Callable) callbackprompt;translate(bool) se abilitato traduce il testo dentromessagealla lingua dell'ultimo messaggio inviato dall'utente;
Il translate si applica solo quando il message è di tipo STATIC.
L'argomento special delle callback prompt e after nel nodo di tipo emit contiene le seguenti informazioni:
class SpecialEmitNodeContext:
prompt_obj: dict | None # l'insieme di informazioni utili per la composizione del prompt (disponibile solo nella callback prompt)
original_options: List[str] # le opzioni specificate tramite callback options sempre nella lingua originale (disponibile solo nella callback after)
options: List[str] # le opzioni specificate tramite callback options o eventualmente tradotte tramite callback translate (disponibile solo nella callback after)
selected_option: str | None # l'opzione selezionata dall'utente sempre nella lingua originale (disponibile solo nella callback after)
Il campo selected_option è popolato solo dentro la callback after
L'opzione selezionata viene automaticamente individuata facendo un controllo tra stringhe non rigido (se c'è un piccolo errore di battitura comunque viene individuata la risposta più simile). selected_option, se definito è sempre un elemento di original_options, quindi riporta la voce selezionata dall'utente in lingua originale.
Visto che comunque l'utente può evitare di cliccare un'opzione e scrivere liberamente, nel caso in cui nessuna opzione sia individuata, la proprietà selected_option sarà None
Query node
Callback disponibili: before after query
Il nodo di tipo query è l'unico che permette di immagazzinare e cercare tra i chunk definiti al suo interno. Questo nodo, infatti, oltre ai chunk con proprietà for_rag = False che vengono utilizzati durante lo step explore per individuare il prossimo nodo da eseguire, può contenere i chunk con proprietà for_rag = True.
Un nodo di tipo query ha le seguenti proprietà extra:
query(Callable) callbackquery;
L'argomento special della callback after nel nodo di tipo query contiene le seguenti informazioni:
class SpecialQueryNodeContext:
chunks: List[Chunk] # i chunk estratti dal vector store
assets: List[FileMetadata] # tutti i file presenti dentro i chunk estratti
offset: str | None # l'offset restituito in caso di scroll per richiedere la pagina successiva, è None anche quando si è raggiunto l'ultima pagina #TODO
con Chunk descritto in seguito nella sezione dedicata ai chunk e FileMetadata descritto in seguito nella sezione dedicata al File Agent:
Durante lo step explore vengono considerati sia i chunk con proprietà for_rag = True che quelli con for_rag = False
Guardrail node
Callback disponibili: before after
Il nodo di tipo guardrail è un nodo privo di proprietà specifiche. L'esecuzione di questo nodo comporta l'eliminazione immediata dal database dell'ultimo messaggio dell'utente, e mostra a quest'ultimo un avviso predefinito. Il testo dell'avviso è configurabile tramite gli aspect, un meccanismo di personalizzazione della piattaforma CAITY esterno al Tree Agent.
Empty node
Callback disponibili: before after
Il nodo di tipo empty è un nodo privo di proprietà specifiche. Lo scopo di questo nodo è quello di consentire l'esecuzione delle sue callback before e after senza che venga lanciata alcun altra funzione.
Di seguito sono riportati i nodi relativi alla logica a componenti:
Component node
Il nodo virtuale di tipo component rappresenta un componente dall'esterno. Come per i nodi non virtuali, anche il nodo componente deve sempre avere un genitore.
Un nodo di tipo component ha le seguenti proprietà:
name(str): il nome del nodo;icon(str): l'icona che si vuole associare al componente recuperata da Lucid;
Per cambiare l'icona del nodo è sufficiente scegliere nel catalogo di lucid e copiare il nome in kebab-case
Head node
Il nodo virtuale di tipo head rappresenta sempre il primo nodo dentro un componente, di conseguenza NON ha mai alcun nodo genitore.
Un nodo di tipo head ha le seguenti proprietà:
init(Callable | None): funzione di inizializzazione delselfdisponibile a tutti i nodi definiti allo stesso livello dellahead, quindi all'interno del componente;
Se il componente contiene altri componenti, i nodi in essi contenuti avranno un loro self distinto ed isolato.
Tail node
Il nodo virtuale di tipo tail può essere considerato come l'ultimo nodo del componente. Questo è l'unico nodo che può essere sia collegato che non; infatti la conseguenza di collegare questo nodo è quello di permettere all'esterno di associare dei nodi figli al rispettivo nodo componente. In tal caso, i nodi figli all'esterno saranno da considerarsi come collegati direttamente al genitore del nodo tail.