Generative AI este o ramură a inteligenței artificiale care poate crea conținut nou, cum ar fi text, imagini sau cod, bazat pe date și modele existente. Ca dezvoltator care vreau să îmbunătățesc aplicațiile mele cu Generative AI, am descoperit că Semantic Kernel de la Microsoft este o adevărată comoară. Semantic Kernel este un SDK open-source, incredibil de ușor de folosit, infuzat cu AI design patterns. Putem acum să ne dotăm aplicațiile cu capacități incredibil de inteligente și responsive, folosind funcții avansate ca ingineria de prompturi și orchestratorii AI. Raționamentul recursiv și planificarea inteligentă deschid noi posibilități pentru rezolvarea problemelor complexe în orice aplicație. Mai mult, Semantic Kernel oferă acces la servicii și date externe, ceea ce înseamnă că pot folosi cantități mari de informații pentru a îmbunătăți aplicațiile mele. Per total, este un set de instrumente foarte avansat, care mă ajută să duc aplicațiile mele la un nivel superior. Generative AI este o lume foarte dinamică și Semantic Kernel încearcă să țină pasul cu cele mai noi adăugiri, cum ar fi asistenții AI din OpenAI.
Cam toți știm ce este OpenAI și nu vă voi plictisi cu asta, dar una dintre cele mai recente funcționalități ale sale se referă la asistenții AI, care pot ajuta utilizatorii cu diverse sarcini, cum ar fi scrierea, codarea, jocurile sau conversațiile. OpenAI AI Assistant este noul tău companion alimentat de AI. Nu este doar un simplu asistent, ci este ca și cum ai avea un ajutor personal, care este disponibil 24/7, gata să-ți răspundă la întrebări, să te ajute cu sarcini și chiar să se angajeze într-o conversație prietenoasă. Este ca și cum ai avea un prieten care știe o mulțime de lucruri și care este mereu acolo pentru tine. Asta nu e tot ce pot asistenți AI, pentru că poți activa și unelte integrate cum sunt Code Interpreter, Retrieval și Function Calling, îmbunătățind capacitățile asistentului tău. Un lucru de luat în considerare este că doar versiunile mai noi ale modelelor GPT oferă suport pentru asistenții AI. Eu am încercat gpt-3.5-turbo și gpt-4-turbo pentru acest articol.
Cu Semantic Kernel se pot construi o varietate mare de agenți, de la simpli chat boturi la asistenți AI complet automatizați. Componentele de bază ale unui agent sunt:
pluginuri (pentru a organiza funcții native sau semantice în colecții de abilități);
planificatori (pentru a genera planuri pentru finalizarea unei sarcini folosind AI)
Este inutil să mai spun că asistenții AI sunt cele mai avansate piese din lista de mai sus. Asistenții AI sunt oferiți doar de OpenAI, dar se așteaptă ca tot mai multe modele AI (LLM) să ofere astfel de funcționalități cum sunt Asistenți AI. Cu Semantic Kernel este incredibil de ușor să lucrezi cu Asistenții AI folosind agenți.
Pentru a ilustra puterea și versatilitatea asistenților AI de la OpenAI, vreau să împărtășesc cu voi un scenariu interesant pe care l-am creat folosind Microsoft Semantic Kernel. În acest scenariu, avem patru personaje cool - Jack Sparrow, Don Quijote, Shakespeare și Yoda - fiecare cu propriul său mod special de a vorbi, fiecare fiind un agent. Avem și un agent scriitor de dialoguri, care face toata magia (!), pentru că folosește Semantic Kernel de la Microsoft pentru a încorpora asistenții AI pentru a declanșa dialoguri între personajele noastre. Rezultatul este superealist, dar haideți să vedem!
Un agent (în cazul nostru un Asistent AI), practic un prompt, se declară într-un folder numit după numele funcției semantice și conține fișierele skprompt.txt și config.json. Alternativ, este acceptat și formatul yaml, ceea ce face posibilă păstrarea promptului împreună cu setările sale în același fișier. Pentru această demonstrație, s-au folosit prompturi în fișier yaml. Uitați-vă la următorul fișier YodaDialogAgent.yaml care declară agentul pentru personajul Yoda.
name: YodaDialogAgent
template_format: semantic-kernel
template: |
You are Yoda talking in Yoda style.
Reply to the most recent message by evaluating and providing exactly one meaningful dialog line of maximum 10 words.
description: A chat bot that replies to the message in the voice of Yoda talking style.
În mod similar, agenții pentru Jack Sparrow, Don Quijote și Shakespeare sunt declarați în propriile lor fișiere yaml. Toți acești patru agenți vor fi folosiți de către un alt agent care va orchestra întreaga interacțiune, agentul scriitor de dialoguri. Agentul scriitor de dialog este declarat în fișierul DialogRunnerAgent.yaml.
name: DialogRunnerAgent
template_format: semantic-kernel
template: |
Use only the applicable tools.
If no tool is applicable, respond with "..." only.
Respond only with a valid JSON response like in the next template:
[
{
"assistant": "assistant 1",
"dialog_line": "dialog line 1"
},
{
"assistant": "assistant 2",
"dialog_line": "dialog line 2"
}
]
description: Determines if a tool can be utilized to achieve a result.
Agentul scriitor de dialoguri este un tip special de agent, deoarece este proprietarul firului de execuție (thread) care asigură contextul comun tuturor agenților din scenariul nostru, fiind agentul principal care controlează fluxul conversației și interacțiunile cu utilizatorul. Ceilalți agenți, corespunzători celor patru personaje sunt atașați agentului scriitor de dialoguri ca orice alt plugin obișnuit (care este, de fapt, un container pentru funcții native sau semantice - prompturi). Acum că avem toți agenții pe poziții, împreună cu relația lor ierarhică, putem vedea cum putem să îi creăm în Semantic Kernel folosind C#.
var yodaAgent = Track(await new AgentBuilder()
.WithOpenAIChatCompletion(
Env.Var("OpenAI:ModelId")!,
Env.Var("OpenAI:ApiKey")!)
.FromTemplatePath(
@"Agents/Agents.YodaDialogAgent.yaml")
.BuildAsync());
var jackAgent = … // similar code as before
var shakespeareAgent = … // similar code as before
var quixoteAgent = … // similar code as before
var runnerAgent = Track(await new AgentBuilder()
.WithOpenAIChatCompletion(
Env.Var("OpenAI:ModelId")!,
Env.Var("OpenAI:ApiKey")!)
.FromTemplatePath(
@"Agents/Agents.DialogRunnerAgent.yaml")
.WithPlugin(jackAgent.AsPlugin())
.WithPlugin(yodaAgent.AsPlugin())
.WithPlugin(shakespeareAgent.AsPlugin())
.WithPlugin(quixoteAgent.AsPlugin())
.BuildAsync());
În continuare, pregătim un scenariu de dialog, care este o listă de prompturi, și apoi trimitem prompturile unul câte unul către modelul GPT (API-ul OpenAI). Fiecare linie din scenariul de dialog conține una sau mai multe acțiuni verbale, menționând agenții noștri după nume atunci când este necesar. Modelul GPT va activa acești agenți atunci când sunt invocați după nume și liniile de dialog generate vor fi agregate de către agentul scriitor de dialoguri într-un răspuns. Acest răspuns va fi returnat utilizatorului și model GPT va continua să răspundă la mesaje până când toate liniile scenariului de dialog au fost trimise.
Scenariul nostru de dialog poate arăta așa:
List scriptSteps =
[
"Jack Sparrow, Shakespeare, Don Quixote, and Yoda are having a feast, eating well, each making remarks about the favorite drinks.",
"Jack Sparrow makes a bad joke about Don Quixote.",
"Jack Sparrow gets a threat from Don Quixote and Don Quixote is launching a fake attack.",
"Shakespeare sends strong words to Jack Sparrow but Jack Sparrow answers bravely and provocatively.",
"Yoda warns Jack Sparrow, but Jack Sparrow slaps Don Quixote",
"Yoda hits Jack Sparrow with an energy blast, resulting in an epic victory.",
"Jack Sparrow dies and Don Quixote falls down to his knees weeping for Jack Sparrow.",
"Shakespeare, Yoda or Don Quixote responds with 'VICTORY!'",
"Yoda says a memento for Jack Sparrow, Don Quixote says nothing.",
];
Acum să vedem cum arată codul interacțiunii între agenții AI în Semantic Kernel.
IAgentThread? thread = null;
try
{
thread = await runnerAgent.NewThreadAsync();
foreach (var messages in scriptSteps.Select(
m => thread!.InvokeAsync(runnerAgent, m)))
{
await foreach (var message in messages)
{
PrintMessage(message!);
}
}
}
catch (AgentException ex)
{
Log.Error("Agent Exception: {message}.", ex.Message);
}
finally
{
await Task.WhenAll(thread?.DeleteAsync()
?? Task.CompletedTask, Task.WhenAll(agents.Select(a => a.DeleteAsync())));
Log.Information("DIALOG IS COMPLETE");
}
În codul de mai sus, putem vedea că logica care invocă lista de mesaje (prompturile) este o iterație asincronă simplă care afișează răspunsurile imediat ce sunt generate. Toată conversația între modelul GPT prin asistenții AI definiți și lista de linii de scenariu de dialog (prompturi) împarte același fir de execuție (contextul comun) deschis de asistentul AI principal, scriitorul de dialog.
Blocul catch care prinde excepția AgentException este responsabil pentru gestionarea problemelor cu execuția dvs., mai ales atunci când atingeți limitele de cotă (credit, cotă zilnică) corespunzătoare contului OpenAI pe care îl aveți. Un alt lucru important este să nu uitați să ștergeți agenții atunci când ați terminat de lucrat cu ei (în codul de mai sus acest lucru se realizează în blocul finally din try-catch-finally) deoarece fiecare asistent AI pe care îl creați în contul dvs. OpenAI pentru acești agenți costă 0,20 cenți pe zi per asistent (la momentul scrierii acestui articol).
Să observăm câteva lucruri despre dialogul generat. De fiecare dată când un personaj din prompt este așteptat să acționeze, agentul corespunzător este declanșat. Dacă un rol de personaj în prompt este pasiv, atunci agentul său nu este neapărat apelat. În rezumat, în ciuda unor abateri, scriitorul de dialog urmează îndeaproape scenariul dialogului, folosind stilul de vorbire al personajelor și creând o narațiune coerentă și captivantă. Per total, pentru exemplul nostru, Semantic Kernel se descurcă foarte bine lucrând cu asistenții OpenAI și asta este departe de toată puterea pe care o poate oferi un asistent AI. Un asistent OpenAI poate fi folosit argumente de intrare și cu Function Calling, care permite acestor modele AI să se conecteze la surse externe de date sau la API-uri.
Anterior, am adoptat o abordare interactivă pentru a genera dialog printr-o conversație cu modelele GPT, linie cu linie. Cu toate acestea, depinzând de scenariu, am putea dori un proces mai autonom. Așadar, să explorăm cum putem genera astfel de dialoguri complexe între personajele noastre într-un singur răspuns!
Să vedem ce obținem (vezi imaginea din pagina alăturată).
Am depus un efort semnificativ pentru a reface agentul scriitor de dialoguri, care orchestrează agenții personajelor noastre. Sincer, am fost destul de impresionat de cât de mare poate fi diferența pe care o poate face un pic de inginerie a prompturilor. După multe ore de luptă și după ce am cheltuit ceva credit (cam 20 de dolari adevărați!), mi-am dat seama că indiferent cât de bine încerc să reformulez promptul pentru a-l face mai inteligent, nimic nu va fi mai eficient decât aceste patru lucruri:
Descompunerea taskului în mai multi pași;
Adăugarea de exemple (few-shot learning!);
Specificarea formatării outputului (json în cazul nostru);
Iată cum arată agentul scriitor de dialoguri după retușare:
Process each verbal action in [Verbal actions] list until all verbal actions are fully processed by following the next steps:
Identify all the applicable assistants in the current verbal action.
Carefully invoke all identified assistants, one by one, and let them generate dialog lines.
Check again and make sure you don't miss out on any verbal action in the list. If you don't miss it, I will tip you $20!
Respond only with a valid JSON response like in the next template:
Process each verbal action in [Verbal actions] list until all verbal actions are fully processed by following the next steps:
1. Identify all the applicable assistants in the current verbal action.
2. Carefully invoke all identified assistants, one by one, and let them generate dialog lines.
3. Do not include the verbal action into the dialog line.
Check again and make sure you don't miss out on any verbal action in the list. If you don't miss it, I will tip you $20!
Respond only with a valid JSON response like in the next template:
[
{
"verbal_action": "verbal action 1",
"assistant": "assistant 1",
"dialog_line": "dialog line 1"
},
{
"verbal_action": "verbal action 2",
"assistant": "assistant 2",
"dialog_line": "dialog line 2"
}
]
După cum am menționat anterior, această a doua abordare nu generează dialogul în mod interactiv, unde puteți prelua controlul asupra narațiunii. În schimb, primiți rezultatul într-un singur răspuns de la modelul GPT. Fiecare abordare are propria sa utilitate pentru scenarii specifice.
Acest lucru poate fi observat în codul corespunzător, unde avem un singur prompt trimis la modelul GPT.
await foreach (IChatMessage message in runnerAgent.InvokeAsync(goal))
{
Console.WriteLine($"{message.Role} >");
Console.WriteLine(lines);
}
AI generativ deschide ușile pentru noi paradigme în IT, făcând software-ul nostru mai asemănător oamenilor și mai inteligent. Cu cât adoptăm mai repede aceste paradigme, cu atât ne vom pregăti mai repede să rezolvăm probleme folosind limbajul natural în moduri pe care nu le-am fi putut imagina, depășind sfera programării tradiționale. Un bun punct de plecare este să citiți "Sam Schillace Laws of AI".