Panoramica
Le app possono definire capacità di IA che risiedono all’interno dello spazio di lavoro — istruzioni di skill riutilizzabili e agenti con prompt di sistema personalizzati.defineSkill
Definisci le skill degli agenti IA
defineSkill
Definisci le skill degli agenti IA
Le skill definiscono istruzioni e capacità riutilizzabili che gli agenti IA possono utilizzare all’interno del tuo spazio di lavoro. Usa Punti chiave:
defineSkill() per definire skill con convalida integrata:src/skills/example-skill.ts
nameè una stringa identificativa univoca per la skill (kebab-case consigliato).labelè il nome di visualizzazione leggibile mostrato nell’UI.contentcontiene le istruzioni della skill — questo è il testo che l’agente IA utilizza.icon(opzionale) imposta l’icona visualizzata nell’UI.description(opzionale) fornisce contesto aggiuntivo sullo scopo della skill.
defineAgent
Definisci agenti IA con prompt personalizzati
defineAgent
Definisci agenti IA con prompt personalizzati
Gli agenti sono assistenti IA che vivono all’interno del tuo spazio di lavoro. Usa Punti chiave:Note sullo schema:
defineAgent() per creare agenti con un prompt di sistema personalizzato:src/agents/example-agent.ts
nameè la stringa identificativa univoca dell’agente (kebab-case consigliato).labelè il nome visualizzato nell’UI.promptè il prompt di sistema che definisce il comportamento dell’agente.description(opzionale) fornisce contesto su ciò che fa l’agente.icon(opzionale) imposta l’icona visualizzata nell’UI.modelId(opzionale) sostituisce il modello di IA predefinito utilizzato dall’agente.responseFormat(facoltativo) imposta il formato di output predefinito dell’agente. Per il testo in formato libero, il valore predefinito è{ type: 'text' }. Usa{ type: 'json', schema }per forzare un output JSON strutturato. Una chiamata arunAgent()o un passaggio AI di un workflow può richiedere un formato diverso, che ha la precedenza.roleUniversalIdentifier(facoltativo) associa l’agente a un ruolo definito dalla tua app. Quel ruolo non deve concedere più del ruolo dell’applicazione, altrimenti la build non riesce; vedi Ruoli degli agenti.modelConfiguration(facoltativo) attiva gli strumenti nativi del modello, come fa la pagina delle impostazioni dell’agente:{ webSearch: { enabled: true }, twitterSearch: { enabled: true } }. Ogni strumento funziona solo con i modelli che lo supportano. Omettilo per mantenerli disattivati.isSystem(facoltativo, il valore predefinito èfalse) nasconde l’agente dagli elenchi di agenti, come gli agenti che Twenty crea per i passaggi IA dei workflow. Modifica solo ciò che viene visualizzato: gli agenti di un’app possono essere modificati solo tramite l’app, indipendentemente da quanto indica questo flag.
responseFormat su { type: 'json' } e fornisci uno schema:src/agents/structured-agent.ts
- Lo schema è un oggetto piatto: il
typedi ogni proprietà deve essere un primitivo (string,numberoboolean). Gli oggetti annidati e gli array non sono supportati. description(facoltativo) su ogni proprietà guida il modello su cosa inserire lì.required(facoltativo) elenca le proprietà che il modello deve sempre restituire.additionalProperties: false(facoltativo) vieta qualsiasi proprietà non dichiarata inproperties.
defineMcpServer
Esponi un server MCP esterno all'IA dello spazio di lavoro (beta)
defineMcpServer
Esponi un server MCP esterno all'IA dello spazio di lavoro (beta)
Usa Punti chiave:
defineMcpServer() per distribuire un server MCP remoto (Streamable HTTP) con la tua app, in modo che l’IA dello spazio di lavoro possa usare i suoi strumenti senza sincronizzare tali dati in Twenty:src/mcp-servers/github.ts
nameè in snake_case (lettere minuscole, cifre e trattini bassi, inizia con una lettera, massimo 32 caratteri) ed è univoco nello spazio di lavoro. Funge da prefisso per i nomi degli strumenti del server.urldeve essere un URL HTTP o HTTPS.connectionProvider(facoltativo) fa riferimento a un provider di connessione dichiarato dalla stessa app. Le chiamate inviano il token di accesso di tale connessione come token Bearer. Omettilo per un server pubblico.- L’URL non può cambiare in una versione successiva quando è collegato un provider di connessione, affinché i token non vengano mai inviati a un altro host.
- Gli spazi di lavoro possono disattivare il server; una versione successiva dell’app mantiene questa scelta.
- Richiede
"engines": { "twenty": ">=2.53.0" }.
runAgent
Esegui un agente da una funzione di logica
runAgent
Esegui un agente da una funzione di logica
runAgent() permette a una funzione di logica di eseguire uno degli agenti della tua app (con le sue skill e i suoi strumenti). Identifica l’agente tramite l’universalIdentifier che hai passato a defineAgent() e passa ciò su cui vuoi che lavori come input:src/logic-functions/run-enricher.ts
inputè una stringa o un elenco da 1 a 100 messaggi ({ role: 'user' | 'assistant', content: string }). Un messaggiouserpuò anche includere allegati di file (vedi sotto). Per mantenere una conversazione, passa unthreadinvece di riprodurre tu stesso i turni precedenti.- Per impostazione predefinita, la chiamata attende l’agente, che può leggere/aggiornare i record
autonomamente tramite i propri strumenti —
runAgent()viene risolta una volta completata l’esecuzione. PassawaitForCompletion: falseper risolvere non appena l’esecuzione si avvia:statusè quindiRUNNINGeresult: null, e la risposta arriva nella conversazionethreadId. Un’esecuzione che supera i 15 minuti restituisce anch’essaRUNNING. - L’agente può anche sospendersi finché un record non cambia o non è trascorso del tempo.
runAgent()viene quindi risolto consuccess: true,status: 'SUSPENDED'eresult: null; l’agente prosegue autonomamente in seguito e la sua risposta arriva nella conversazionethreadId. Una chiamata a unthreadil cui agente è ancora in attesa non riesce finché non termina. - Ogni chiamata viene archiviata con il relativo input e stato, oltre alla risposta e all’utilizzo
al completamento, così da comparire nelle esecuzioni dell’agente. Senza un
thread, una chiamata riceve una conversazione propria:threadIdla denomina e una chiamata successiva non la vede mai. - Un’app può eseguire solo i propri agenti.
- Il ruolo predefinito dell’app deve concedere il flag di autorizzazione
AI— aggiungiSystemPermissionFlag.AIai suoipermissionFlagUniversalIdentifiers(oppure impostacanAccessAllTools: true). In mancanza di ciò,runAgent()non riesce con un errore di autorizzazione. - Imposta un valore generoso per
timeoutSecondssulla funzione di logica — l’esecuzione degli agenti può richiedere diversi secondi. statusèCOMPLETEDcon unresultnon null quando l’esecuzione viene completata senza attendere;SUSPENDEDconsuccess: trueeresult: nullmentre attende;RUNNINGconsuccess: trueeresult: nullquando la chiamata non ha atteso l’esecuzione;FAILEDconsuccess: falseeerrorche contiene il motivo (ad esempio, quando lo spazio di lavoro esaurisce i crediti AI durante l’esecuzione).
src/roles/default-role.ts
Invio di file con un messaggio
Un messaggiouser può includere attachments, quindi l’agente vede uno screenshot o un
PDF anziché solo il relativo nome. Ogni voce fa riferimento a un file che la tua app ha già
caricato nella cartella agent-chat:src/logic-functions/describe-screenshot.ts
UPLOAD_FILE nel ruolo predefinito dell’app,
insieme ad AI. filename è facoltativo e serve solo a etichettare il file per il modello.- Solo i messaggi
userpossono contenere allegati. Non possono essere rappresentati in un turno dell’assistente, quindirunAgent()li rifiuta lì anziché eliminarli. - Al massimo 10 allegati per messaggio.
- Il
fileIddeve indicare un file completamente caricato nello stesso spazio di lavoro, nella cartellaagent-chat. Qualsiasi altra cosa fa fallire la chiamata anziché rispondere silenziosamente senza il file.
Richiedere un risultato strutturato
PassaresponseFormat per ottenere un formato solo per questa chiamata. Ha la precedenza su
responseFormat dell’agente stesso, quindi un agente può servire chiamanti che necessitano di
output diversi. Utilizza lo stesso schema di defineAgent():src/logic-functions/score-lead.ts
Proseguire una conversazione
Passa unthread e Twenty mantiene la conversazione per te: ogni chiamata invia solo
il nuovo input e l’agente vede i turni precedenti, inclusi i risultati restituiti
dai suoi strumenti. Usa come chiave qualsiasi identificatore stabile dal tuo lato, ad esempio un canale
Slack e il timestamp del thread:src/logic-functions/reply-in-thread.ts
- Le chiavi hanno ambito limitato alla tua app e al tuo agente, quindi due app non condividono mai una
conversazione. Solo i token di accesso dell’app possono passare un
thread. inputpuò contenere solo messaggiuser: la conversazione contiene già le risposte dell’agente.additionalInstructionsvengono fornite al modello solo per questa chiamata e non vengono archiviate, il che è adatto per indicazioni specifiche per richiesta, come regole di formattazione o una scadenza. Funzionano anche senza unthread.- Ogni esecuzione viene archiviata come turno con il relativo stato, incluse quelle non riuscite. Il suo utilizzo viene archiviato quando l’agente restituisce il controllo, anche se ha esaurito i crediti. Un’ esecuzione non riuscita non fa parte della conversazione, quindi la chiamata successiva non la vede.
- Le chiamate sullo stesso thread vengono eseguite una alla volta: una seconda chiamata attende che la prima termini, così ciascuna vede i turni precedenti.
- Quando i turni vengono eseguiti come membri diversi (vedi sotto), ciascuno vede solo il testo dei turni degli altri, mai i risultati dei loro strumenti, poiché questi sono stati letti con le autorizzazioni di qualcun altro.
Esecuzione per conto di un membro dello spazio di lavoro
PassarunAsWorkspaceMemberId quando l’esecuzione viene attivata da una persona — ad esempio un chatbot che risponde a un messaggio — in modo che l’agente agisca come quel membro invece che come l’app:src/logic-functions/answer-question.ts
- Un token senza alcun utente associato può specificare qualsiasi membro. Una funzione logica viene eseguita con uno, e così anche i token generati tramite
client_credentialso da una chiave API. - Un token emesso per conto di un utente, come quello che riceve un front component, può specificare solo il membro dell’utente stesso.
Seguire una conversazione
Un’app legge le conversazioni che intrattiene con i propri agenti tramite la stessa API della chat:listConversationMessages() restituisce la relativa cronologia e
streamConversation() produce i relativi eventi in tempo reale, token per token. Assegna un nome alla
conversazione tramite l’agente e la chiave thread che passi a runAgent(); puoi
iniziare a seguirla prima che la prima esecuzione la raggiunga.src/logic-functions/follow-conversation.ts
- Gli eventi sono quelli della chat stessa:
stream-chunkcontiene unUIMessageChunkdell’AI SDK e unseq, e notifiche qualimessage-persisted(un messaggio è stato salvato: quello di un membro o la risposta, che contiene il proprioturnId),credits-exhausted(la risposta si è interrotta quando lo spazio di lavoro ha esaurito i crediti),queue-updatedostream-errorindicano quando leggere nuovamente la cronologia. Unkeepalivearriva una volta stabilita la connessione, poi ogni pochi secondi: leggi la cronologia al primo, poiché ciò che è accaduto prima si trova lì. - Per trasformare i chunk in messaggi come fa la chat, inviali a
readUIMessageStream()dal pacchettoai. listConversationMessages()restituisce{ messages, streamCatchup }: i messaggi salvati comeUIMessagedell’AI SDK, come li costruisce la chat, e i chunk di una risposta ancora in streaming. Mostra tali chunk, poi ignora i chunk in tempo reale il cuiseqè al massimostreamCatchup.maxSeq, poiché li ripetono.runAgent()restituiscerunId, il turno aperto dalla chiamata: i messaggi di tale esecuzione lo contengono comemetadata.turnId. Un’esecuzione in attesa prosegue nei turni successivi della conversazione.- Lo stream rimane aperto finché non interrompi il relativo
signalo usibreak, e genera un errore se la connessione cade: avvia nuovamente lo streaming per riprendere. - Un’app legge solo le proprie conversazioni. I turni che un membro ha aperto altrove,
ad esempio nella chat, mostrano solo il proprio testo, nella cronologia, in tempo reale e nella
successiva esecuzione dell’agente: in tempo reale, gli altri chunk arrivano come chunk
message-metadatavuoti, cosìseqrimane senza lacune. I turni eseguiti dalla tua app come membro (vedi sopra) rimangono integri quando li legge autonomamente. - Richiede
"engines": { "twenty": ">=2.56.0" }.
ai.generate
Chiama un modello da una funzione logica, senza un agente
ai.generate
Chiama un modello da una funzione logica, senza un agente
ai.generate() chiama un modello da una funzione logica. A differenza di
runAgent(), non ha bisogno di defineAgent(): la chiamata porta le proprie istruzioni
e non apre conversazioni. Usalo per un lavoro one-shot come la classificazione, la sintesi o l’estrazione di
.inputè una stringa o un elenco di messaggi{ role: 'user' | 'assistant', content }.instructions(opzionale) aggiunge le istruzioni di sistema del chiamante.modelId(opzionale) sceglie il modello. Senza di esso, la chiamata utilizza il modello predefinito del workspace .responseFormat(opzionale) funziona come inrunAgent(). Con{ type: 'json', schema }, lo schema va sulla richiesta del modello stesso, così una risposta JSON richiede una richiesta (più passaggi dello strumento quando unscopeconcede strumenti ).scope(opzionale) consente alla chiamata di utilizzare il database, come descritto di seguito.
{ successo, testo, oggetto, errore, runId, use }:textè la risposta di una risposta testuale enullper una JSON.objectè il risultato di una risposta JSON, enullaltrimenti.errorspiega un’esecuzione fallita, quandosuccessèfalse. Una corsa che fallisce risolve questo modo piuttosto che lanciare; una chiamata che non può avviare un’esecuzione, come come una con input non valido, lancia.runIdidentifica l’esecuzione registrata, con il suo modello, utilizzo e crediti.usageè{ inputTokens, outputTokens }: quello che è stato speso, inclusi i passaggi che sono terminati prima di un fallimento.
scope, la chiamata legge e non scrive alcun record. Per permettergli di utilizzare il database
, passare un scope, scritto come permessi di un ruolo:onChunk. La chiamata risolve ancora
allo stesso risultato, una volta che l’ultimo pezzo è stato consegnato a onChunk. Se la fine del flusso
non arriva mai, smette di aspettare una volta che il risultato è in e 5 s
passare senza un nuovo chunk, e ancora consegna su ogni pezzo ricevuto da allora:- La chiamata segue la corsa attraverso lo stesso abbonamento di
streamConversation(): si iscrive prima, aspetta fino a quando l’abbonamento è live, poi inizia la run, quindi non manca nessun chunk. - I chunks sono di proprietà della chat: li incanalano in
readUIMessageStream()dal pacchettoaiper costruire il messaggio. signalinterrompe la chiamata: rifiuta con l’errore di abort e chiude il flusso . La corsa stessa continua, e viene registrata come al solito.- Una volta che lo stream è live, un guasto di esso ferma solo i chunk: la chiamata
si risolve ancora con il risultato della corsa, che è fatturata in entrambi i modi. La chiamata
rifiuta quando l’abbonamento fallisce prima che vada in diretta, quindi la corsa non
inizia mai, o quando
onChunklancia. - Un’app segue solo le sue funzioni, quindi
onChunkha bisogno di un token di app. - Richiede
"engines": { "twenty": ">=2.56.0" }.
ai.generate() richiede il flag dei permessi AI sul ruolo predefinito dell’app,
come runAgent().sendInboxMessage
Avvia o continua una conversazione nelle chat dei membri
sendInboxMessage
Avvia o continua una conversazione nelle chat dei membri
sendInboxMessage() pubblica un messaggio dalla tua app nelle chat di uno o più
membri dello spazio di lavoro. Può terminare con una chiamata di strumento a cui il membro risponde dalla chat,
dopo la quale la conversazione continua con l’assistente di Twenty. Consulta
Posta in arrivo per le relative opzioni, gli helper per le menzioni e come pubblicare da un componente front-end.