Salt la conținutul principal

Prezentare generală

Abilitățile și agenții sunt în prezent în stadiu alfa. Caracteristica funcționează, dar este încă în dezvoltare.
Aplicațiile pot defini capabilități AI care există în interiorul spațiului de lucru — instrucțiuni reutilizabile pentru abilități și agenți cu prompturi de sistem personalizate.
Abilitățile definesc instrucțiuni și capabilități reutilizabile pe care agenții AI le pot folosi în spațiul dvs. de lucru. Folosiți defineSkill() pentru a defini abilități cu validare încorporată:
src/skills/example-skill.ts
Puncte cheie:
  • name este un șir identificator unic pentru abilitate (se recomandă kebab-case).
  • label este numele lizibil afișat în interfața cu utilizatorul (UI).
  • content conține instrucțiunile abilității — acesta este textul pe care agentul AI îl folosește.
  • icon (opțional) setează pictograma afișată în UI.
  • description (opțional) oferă context suplimentar despre scopul abilității.
Agenții sunt asistenți AI care există în interiorul spațiului dvs. de lucru. Utilizați defineAgent() pentru a crea agenți cu un prompt de sistem personalizat:
src/agents/example-agent.ts
Puncte cheie:
  • name este un șir identificator unic pentru agent (se recomandă kebab-case).
  • label este numele de afișare din interfața cu utilizatorul (UI).
  • prompt conține promptul de sistem — acesta este textul de instrucțiuni care definește comportamentul agentului.
  • description (opțional) oferă context despre ce face agentul.
  • icon (opțional) setează pictograma afișată în UI.
  • modelId (opțional) suprascrie modelul AI implicit utilizat de agent.
  • responseFormat (opțional) stabilește formatul implicit al ieșirii agentului. Valoarea implicită este { type: 'text' } pentru text liber. Folosește { type: 'json', schema } pentru a impune ieșire JSON structurată. Un apel runAgent() sau un pas AI dintr-un flux de lucru poate solicita un format diferit, care are prioritate.
  • roleUniversalIdentifier (opțional) leagă agentul de un rol definit de aplicația ta. Acel rol nu trebuie să acorde mai multe permisiuni decât rolul aplicației, altfel compilarea eșuează; consultați Roluri de agent.
  • modelConfiguration (opțional) activează instrumentele native ale modelului, așa cum face pagina de setări a agentului: { webSearch: { enabled: true }, twitterSearch: { enabled: true } }. Fiecare instrument funcționează numai pe modelele care îl acceptă. Omiteți-l pentru a le menține dezactivate.
  • isSystem (opțional, are implicit valoarea false) ascunde agentul din listele de agenți, la fel ca agenții pe care Twenty îi creează pentru pașii de IA din fluxurile de lucru. Schimbă doar ceea ce este afișat: agenții unei aplicații pot fi modificați numai prin intermediul aplicației, indiferent de valoarea acestei opțiuni.
În mod implicit, un agent returnează text liber. Pentru a obține o ieșire structurată, setează responseFormat la { type: 'json' } și furnizează schema:
src/agents/structured-agent.ts
Note despre schemă:
  • Schema este un obiect plat: type al fiecărei proprietăți trebuie să fie un tip primitiv (string, number sau boolean). Obiectele îmbricate și tablourile nu sunt acceptate.
  • description (opțional) pe fiecare proprietate ghidează modelul cu privire la ce să pună acolo.
  • required (opțional) enumeră proprietățile pe care modelul trebuie să le returneze întotdeauna.
  • additionalProperties: false (opțional) interzice orice proprietate care nu este declarată în properties.
Serverele MCP sunt în versiune beta și nu sunt încă active: le puteți declara, dar spațiile de lucru nu le pot folosi instrumentele până când funcționalitatea nu este lansată.
Utilizați defineMcpServer() pentru a livra un server MCP la distanță (HTTP streaming) împreună cu aplicația dvs., astfel încât IA spațiului de lucru să îi poată utiliza instrumentele fără a sincroniza datele respective în Twenty:
src/mcp-servers/github.ts
Puncte cheie:
  • name este în snake_case (litere mici, cifre și caractere de subliniere, începe cu o literă, maximum 32 de caractere) și este unic în spațiul de lucru. Acesta este prefixul numelor instrumentelor serverului.
  • url trebuie să fie un URL HTTP sau HTTPS.
  • connectionProvider (opțional) face referire la un furnizor de conexiune declarat de aceeași aplicație. Apelurile trimit tokenul de acces al conexiunii respective ca token Bearer. Omiteți-l pentru un server public.
  • URL-ul nu se poate modifica într-o versiune ulterioară cât timp este asociat un furnizor de conexiune, astfel încât tokenurile să nu fie trimise niciodată către o altă gazdă.
  • Spațiile de lucru pot dezactiva serverul; o versiune ulterioară a aplicației păstrează această opțiune.
  • Necesită "engines": { "twenty": ">=2.53.0" }.
runAgent() permite unei funcții logice să ruleze unul dintre agenții aplicației tale (cu abilitățile și instrumentele sale). Identifică agentul prin universalIdentifier pe care l-ai transmis către defineAgent() și transmite ca input ceea ce vrei să proceseze:
src/logic-functions/run-enricher.ts
Puncte cheie:
  • input este un șir sau o listă de 1 până la 100 de mesaje ({ role: 'user' | 'assistant', content: string }). Un mesaj user poate include și atașamente de fișiere (vezi mai jos). Pentru a menține o conversație, transmiteți un thread în loc să reluați dvs. rândurile anterioare.
  • În mod implicit, apelul așteaptă agentul, care poate citi/actualiza înregistrări direct prin propriile sale instrumente — runAgent() se finalizează după ce execuția se încheie. Transmiteți waitForCompletion: false pentru a se finaliza de îndată ce începe execuția: status este apoi RUNNING și result: null, iar răspunsul ajunge în conversația threadId. O execuție care depășește 15 minute se finalizează, de asemenea, ca RUNNING.
  • Agentul poate, de asemenea, să se întrerupă până când se modifică o înregistrare sau a trecut un anumit timp. runAgent() se rezolvă apoi cu success: true, status: 'SUSPENDED' și result: null; agentul continuă singur mai târziu, iar răspunsul său ajunge în conversația threadId. Un apel către un thread al cărui agent încă așteaptă eșuează până când acesta se finalizează.
  • Fiecare apel este stocat împreună cu intrarea și starea sa, precum și cu răspunsul și utilizarea sa atunci când se finalizează, astfel încât să apară în execuțiile agentului. Fără un thread, un apel primește o conversație proprie: threadId o denumește, iar un apel ulterior nu o vede niciodată.
  • O aplicație poate rula doar propriii săi agenți.
  • Rolul implicit al aplicației trebuie să acorde indicatorul de permisiune AI — adaugă SystemPermissionFlag.AI la permissionFlagUniversalIdentifiers (sau setează canAccessAllTools: true). Fără acesta, runAgent() eșuează cu o eroare de permisiune.
  • Setează un timeoutSeconds generos pentru funcția logică — rulările agentului pot dura câteva secunde.
  • status este COMPLETED cu un result care nu este null atunci când execuția se finalizează fără a aștepta; SUSPENDED cu success: true și result: null cât timp așteaptă; RUNNING cu success: true și result: null atunci când apelul nu a așteptat execuția; FAILED cu success: false și error care conține motivul (de exemplu, când spațiul de lucru rămâne fără credite AI în timpul execuției).
src/roles/default-role.ts
Evită buclele: dacă apelezi runAgent() dintr-un declanșator de tip eveniment de bază de date *.updated, iar agentul actualizează aceeași înregistrare, restrânge declanșatorul cu updatedFields la un câmp pe care agentul nu îl scrie niciodată (de exemplu, URL-ul sursă) sau verifică dacă vreun câmp țintă este încă gol înainte de a apela runAgent().

Trimiterea fișierelor cu un mesaj

Un mesaj user poate include attachments, astfel încât agentul vede o captură de ecran sau un PDF în loc să vadă doar numele acestuia. Fiecare intrare face referire la un fișier pe care aplicația dvs. l-a încărcat deja în folderul agent-chat:
src/logic-functions/describe-screenshot.ts
Încărcarea necesită indicatorul de permisiune UPLOAD_FILE în rolul implicit al aplicației, alături de AI. filename este opțional și doar etichetează fișierul pentru model.
  • Doar mesajele user pot include atașamente. Acestea nu pot fi reprezentate într-un tur al asistentului, astfel încât runAgent() le respinge acolo, în loc să le elimine.
  • Cel mult 10 atașamente per mesaj.
  • fileId trebuie să indice un fișier încărcat complet în același spațiu de lucru, în folderul agent-chat. Orice altceva face ca apelul să eșueze, în loc să răspundă în mod discret fără fișier.

Solicitarea unui rezultat structurat

Transmiteți responseFormat pentru a obține un format numai pentru acest apel. Are prioritate față de propriul responseFormat al agentului, astfel încât un agent poate deservi apelanți care au nevoie de ieșiri diferite. Folosește aceeași schemă ca defineAgent():
src/logic-functions/score-lead.ts

Continuarea unei conversații

Transmiteți un thread, iar Twenty păstrează conversația pentru dvs.: fiecare apel trimite doar noua intrare, iar agentul vede rândurile anterioare, inclusiv ceea ce au returnat instrumentele sale. Utilizați ca cheie orice identificator stabil din partea dvs., cum ar fi un canal Slack și marca temporală a firului de discuție:
src/logic-functions/reply-in-thread.ts
  • Cheile sunt limitate la aplicația și agentul dvs., astfel încât două aplicații nu partajează niciodată o conversație. Numai tokenurile de acces ale aplicației pot transmite un thread.
  • input poate conține numai mesaje user: conversația conține deja răspunsurile agentului.
  • additionalInstructions sunt oferite modelului numai pentru acest apel și nu sunt stocate, ceea ce este potrivit pentru instrucțiuni per solicitare, cum ar fi reguli de formatare sau un termen-limită. Acestea funcționează și fără un thread.
  • Fiecare rulare este stocată ca un tur cu starea sa, inclusiv cele eșuate. Utilizarea sa este stocată când agentul revine, chiar dacă a rămas fără credite. O rulare eșuată nu face parte din conversație, astfel încât următorul apel nu o vede.
  • Apelurile din același fir de discuție rulează pe rând: un al doilea apel așteaptă ca primul să se termine, astfel încât fiecare îl vede pe cel dinaintea sa.
  • Când rândurile rulează ca membri diferiți (vedeți mai jos), fiecare vede numai textul rândurilor celorlalți, niciodată rezultatele instrumentelor lor, deoarece acestea au fost citite cu permisiunile altcuiva.

Rulare în numele unui membru al spațiului de lucru

Transmite runAsWorkspaceMemberId atunci când rularea este declanșată de o persoană — de exemplu, un chatbot care răspunde la un mesaj — astfel încât agentul să acționeze ca acel membru în loc să acționeze ca aplicația:
src/logic-functions/answer-question.ts
Rularea acționează apoi cu propriul rol al membrului: poate face orice poate face acel membru și nimic mai mult, înregistrările pe care le creează sunt atribuite lui, iar permisiunile lui la nivel de rând se aplică. Rolul agentului nu participă — este valoarea implicită a aplicației pentru rulările fără nicio persoană în spatele lor. Omite câmpul pentru rulările autonome (joburi programate, declanșatoare de evenimente din baza de date): acelea păstrează propriul rol al agentului. Aplicația care acționează rămâne atașată la contextul rulării pentru proveniență, fără a restrânge permisiunile membrului.Aplicația ta este responsabilă să facă asocierea dintre persoana care a declanșat rularea și un membru al spațiului de lucru. A denumi unul necesită un token de acces al aplicației, iar ceea ce poate denumi un token depinde dacă acesta are sau nu un utilizator asociat:
  • Un token fără niciun utilizator atașat poate denumi orice membru. O funcție logică rulează cu unul, la fel și tokenurile emise prin client_credentials sau dintr-o cheie API.
  • Un token emis în numele unui utilizator, așa cum primește un front component, poate denumi doar propriul membru al acelui utilizator.
Orice altceva — o sesiune simplă de utilizator, o cheie API fără un token al aplicației — nu poate denumi deloc un membru.
runAgent() aruncă o eroare atunci când membrul spațiului de lucru nu poate fi identificat — un membru necunoscut sau eliminat, ori unul fără niciun rol. Nu revine niciodată la propriul rol al agentului, deoarece acest lucru ar acorda mai mult acces decât a cerut apelantul.

În urma unei conversații

O aplicație citește conversațiile pe care le deține cu agenții săi prin intermediul aceluiași API ca și chat: listConversationMessages() returnează istoricul ei, și streamConversation() generează evenimentele sale live, token by token. Numește conversația de către agent și cheia thread pe care o treci la `runAgent()”; tu îl poți urmări înainte ca prima execuție să ajungă la el.
src/logic-functions/follow-conversation.ts
  • Evenimentele sunt ale chatului: stream-chunk transportă un AI SDK UIMessageChunk şi un seq, şi notificări precum mesaj persistă (un mesaj a fost salvat: un membru sau răspunsul, care transportă contul săuturnId), credits-exhausted(răspunsul s-a oprit atunci când spațiul de lucru a expirat din credite),queue-updatedsaustream-error` spune când să citească din nou istoria. Un “keepalive” vine odată conectat, apoi la fiecare câteva secunde: citește istoria din primul, de când ce s-a întâmplat înainte de este acolo.
  • Pentru a transforma bucățile în mesaje cum face chatul, țineți-le în readUIMessageStream() din pachetul ai.
  • listConversationMessages() returnează { mesaje, streamCatchup }: mesajele salvate ca UIMessages, pe măsură ce chat-ul le construiește, și bucățile de sunt încă în mișcare. Arată acele chunks, apoi sari peste chunkurile în direct care seq este cel mult streamCatchup.maxSeq, din moment ce le repetă.
  • runAgent() returnează runId, apelul se deschis: mesajele care rulează îl poartă ca metadata.turnId. O cursă care așteaptă să se îndrepte mai târziu de conversația de la .
  • Fluxul rămâne deschis până când renunțați la semnal sau break, și aruncă dacă picăturile de conexiune: stream-ul pentru a relua.
  • O aplicație citește doar propriile sale conversații. Întoarce un membru deschis în altă parte, cum ar fi în chat, arată doar textul în istorie, live și în următoarea rulare a agentului : live, celelalte bucăți de joc sosesc sub formă de bucăți goale ‘mesagerie metadata’, astfel încât seq rămâne nesigur. Transformă aplicația ta rulată ca membru (a se vedea mai sus) rămâne întreagă atunci când citește de unul singur.
  • Necesită "motoare": { "twenty": ">=2.56.0" }.
ai.generate() apelează un model dintr-o funcţie logică. Spre deosebire de runAgent(), nu are nevoie de defineAgent(): apelul are propriile instrucțiuni și nu deschide nicio conversație. Folosiți-l pentru o singură împuşcătură, cum ar fi clasificare, sumarizare sau extracție.
  • input este un șir sau o listă de mesaje { role: 'user' | 'assistant', content } .
  • instrucțiuni (opțional) adaugă instrucțiunile sistemului apelantului.
  • modelId (opţional) alege modelul. Fără el, apelul folosește modelul implicit al spațiului de lucru .
  • responseFormat (opţional) funcţionează ca în runAgent(). Cu { type: 'json', schema }", schema merge pe cererea modelului în sine, prin urmare un răspuns JSON face o cerere (plus unealtă pași atunci când un scope` acordă instrumente ).
  • scope (opţional) permite apelării să utilizeze baza de date, aşa cum este descrisă mai jos.
Acesta se rezolvă pentru { success, text, obiect, eroare, runId, usage }:
  • text este răspunsul unui text şi null pentru unul JSON.
  • object este rezultatul unui răspuns JSON şi null altfel.
  • error explică o rulare eșuată, când succes este false. O execuție care eșuează rezolvă în acest fel în loc să aruncați; un apel care nu poate începe o rulare, cum ar fi ca unul cu intrare nevalidă, aruncă.
  • runId identifică rularea înregistrată, cu modelul, utilizarea şi creditele sale.
  • usage este { inputTokens, outputTokens }: what the run cheltuit, including the steps that finished before a fail.
Apelul acționează ca oricine a făcut-o: membrului îi aparține un token scris de utilizator, în caz contrar, aplicația dvs., în caz contrar cheia API. Dintr-o funcție logică urmează clienții implici: acționează ca persoana care a declanșat rularea, sau ca aplicația ta atunci când nimeni nu a făcut. Nu depășește niciodată rolul aplicației tale, iar o aplicație fără un rol implicit nu o poate apela.Fără scope, apelul citeşte şi nu scrie nici o înregistrare. Pentru a o lăsa să folosească baza de date pasează un scope, scris ca permisiunile unui rol:
O sferă de aplicare se limitează: nu acordă nimic apelantului care nu are deja un telefon.Pentru a afișa răspunsul în timp ce este scris, treceți onChunk. Apelul încă se rezolvă cu în acelaşi rezultat, odată ce ultimul chunk a fost înmânat lui onChunk. Dacă runda nu soseşte niciodată, se opreşte să aştepte odată ce rezultatul este în şi 5 s trece fără un nou chunk, și chiar mâini peste fiecare bucată primită până atunci:
  • Apelul se derulează prin același abonament ca streamConversation(): se abonează mai întâi, așteaptă până când abonamentul este live, apoi începe rularea, astfel încât să nu piardă chunk.
  • Bucățile sunt ale chat-ului: țineți-le în readUIMessageStream() din pachetul pentru a construi mesajul.
  • semnal anulează apelul: acesta respinge cu eroarea de abortare și închide fluxul Drumul în sine continuă şi este înregistrat ca de obicei.
  • Odată ce fluxul este live, un eşec al acestuia opreşte doar bucăţile: apelul încă se rezolvă cu rezultatul run-ului. care este facturat în ambele sensuri. Apelul respinge atunci când abonamentul eșuează înainte de a trece în direct, astfel încât nu mai începe niciodată sau când onChunk arată.
  • O aplicație urmează doar propriile sale execuții, astfel încât onChunk are nevoie de un token de aplicație.
  • Necesită "motoare": { "twenty": ">=2.56.0" }.
ai.generate() necesită steagul de permisiune AI de pe rolul implicit al aplicației, like runAgent().
sendInboxMessage() postează un mesaj din aplicația dvs. în chaturile unuia sau mai multor membri ai spațiului de lucru. Se poate încheia cu un apel de instrument la care membrul răspunde din chat, după care conversația continuă cu asistentul Twenty. Consultați Căsuța de intrare pentru opțiunile sale, funcțiile ajutătoare pentru mențiuni și cum să publicați dintr-o componentă front.