Desenvolupem la teva web presencial SPA DES DE 300€. Si, es una bogeria. Web SPA DES DE 300€ — Si, es una bogeria. Parlem →

Per què un agent d'IA és molt més que cridar un model

Per què un agent d'IA és molt més que cridar un model

Gairebé tothom que integra per primera vegada un model de llenguatge en una aplicació arriba a la mateixa conclusió equivocada: que ja està fet. La crida funciona, el model contesta i sembla que l'únic que queda és polir el prompt. El que separa aquesta demo d'un agent que pots posar davant d'un client no és el model, és tot el que cal construir al voltant perquè no es trenqui, no es dispari de cost i no es quedi mut a mitja conversa. Els exemples van en Laravel, un dels stacks en què ens especialitzem a AndorraDev, però la llista de peces que cal construir és la mateixa en Python, Node o el que facis servir.

La crida al model són quatre línies

Això és la petició HTTP sencera a l'API del model, tal com és al codi que fem servir en producció:

$response = Http::withHeaders([
    'Authorization' => 'Bearer ' . $this->apiKey,
    'Content-Type' => 'application/json',
])->timeout($this->timeout)->post($this->baseUrl . '/chat/completions', $body);

Quatre línies. El paquet que les envolta en té gairebé set mil. Aquesta proporció és l'article sencer: fer que un model contesti és trivial, i no és la feina.

El model no respon, demana eines

Així que li dones capacitats, el model deixa de contestar en una sola volta. Contesta demanant que executis alguna cosa, tu l'executes, li tornes el resultat, i torna a parlar. De vegades demana una altra cosa. De vegades cinc vegades seguides.

Això és un bucle, i aquí apareix el primer problema que ningú preveu a la demo: cal tornar-li el seu propi torn encara que vingui buit. Quan el model només demana eines, el seu missatge no porta text. Si l'omets de l'historial perquè sembla inútil, els resultats que envies després apunten a crides que ja no existeixen i el proveïdor rebutja la petició sencera.

I les eines fallen. La resposta correcta a una eina que peta no és propagar l'excepció, és tornar-li l'error al model com si fos un resultat més. Des d'una traça de pila no se'n pot recuperar; des d'un {"error": "..."} sí que pot triar una altra via.

Aquest bucle necessita un sostre, i no per elegància

Un model que insisteix en una cerca que no funciona la reformularà indefinidament. Sense límit, el bucle corre fins que alguna cosa esgota el seu temps, havent pagat cada volta.

El sostre tampoc és un error: en esgotar-se, l'última resposta torna marcada, es registra un avís i l'usuari rep alguna cosa. Un agent que es queda mut perquè va arribar al seu límit és pitjor que un que diu el que té.

El detall que separa un agent usable d'un que t'arruïna

Un model no té memòria dels seus propis intents dins d'una mateixa volta. Si una cerca no retorna res, no conclou que aquella dada no existeix: conclou que l'ha formulada malament, i ho torna a provar amb altres paraules. Aquesta insistència és raonable en una persona i ruïnosa quan cada intent es factura.

Quan el model no troba res, la seva reacció natural és reformular i tornar a cercar. És raonable una vegada. A la quarta està cremant diners per arribar a la mateixa conclusió, que és que allò no hi és.

El problema per detectar-ho és que el sistema no sap què tornen les teves eines. Cada projecte té les seves. La solució va ser mirar la forma en comptes del contingut: si el resultat porta alguna de les claus típiques d'una cerca, items, results, matches, és una cerca; i si totes vénen buides, no va trobar res.

Al segon buit seguit, el resultat que rep el model porta afegit això:

Van dues cerques seguides sense resultats. Si t'has quedat sense reformulacions raonables, digues a l'usuari que no has trobat res en lloc de tornar a cercar.

Funciona perquè no li prohibeixes res, li dones la dada que li falta. El model no sap quantes vegades porta cercant; ningú l'hi havia dit.

Un detall fi: un resultat que no sembla una cerca no trenca la ratxa. Si entre dues cerques mortes hi ha una consulta d'una altra cosa, el compte continua viu.

La conversa creix fins que no hi cap

Tota conversa llarga acaba superant la finestra del model. Cal resumir, i aquí gairebé tothom comet el mateix error: resumir-ho tot.

Dues regles que ens va costar aprendre:

Els últims missatges no es comprimeixen mai. És on viu el fil del que s'està dient, i "un resum del que acabem de parlar" és exactament on aquests sistemes es tornen vagues i contesten al costat.

Els resultats d'eines no entren mai al resum. Pertanyen al torn que els va produir. Arrossegar-los entre torns és com un bucle acaba rellegint les seves pròpies canonades en lloc de la conversa.

El resum a més és incremental: es desa fins a quin missatge s'ha resumit, i la següent compressió parteix del resum anterior en comptes de refer-lo. I si la compressió falla, es conserva el resum vell. Perdre'l llençaria tot el que la conversa havia establert; mantenir-ne un una mica desactualitzat costa una mica de context i res més.

Andie recomana

Per decidir si toca comprimir no cal un tokenitzador de debò: dividir els caràcters entre quatre n'hi ha prou. Equivocar-se un deu per cent només significa comprimir un torn abans o després, i arrossegar una dependència sencera per afinar això no compensa. Guarda la precisió per al que factura, que és la resposta del proveïdor.

Preguntar si hi ha saldo abans de gastar, no després

Si vens això, cada volta del bucle costa diners. I com que el bucle crida el model una vegada per iteració, la comprovació de saldo es fa diverses vegades en un mateix torn, així que ha de ser barata o memoritzada.

Hi ha una distinció que convé tenir clara des del principi, perquè determina si reintentes:

Un 429 del proveïdor és problema seu, passa després que la petició surti, és temporal i es reintenta.

Un saldo esgotat és problema teu, passa abans que la petició surti, no és temporal i no es reintenta mai. Allà no hi ha espera que valgui: o canvia el pla o entra el període següent.

Tractar-los igual significa reintentar contra la teva pròpia manca de crèdit, que és una forma elegant de no fer res durant una estona.

Dos proveïdors, un sol dialecte

Així que vols poder canviar de proveïdor, apareix la feina avorrida de traduir. I no és un mapeig de camps, són incompatibilitats reals.

Anthropic rebutja resultats d'eina en torns separats: cal fusionar-los en un de sol. I rebutja un torn d'assistent sense contingut, així que cal omplir-lo amb alguna cosa.

Pitjor encara: les capacitats no van per proveïdor, van per model, i només es descobreixen provant. Mesurat contra l'API real:

claude-opus-4-8   + temperature  ->  400 "`temperature` is deprecated for this model"
claude-sonnet-4-6 + temperature  ->  200 OK

Mateix proveïdor, mateixa família, comportament diferent. L'única manera de saber-ho és que algú s'empassi el 400.

I aquí una decisió de disseny que recomano copiar: davant d'un model desconegut, assumir que ho permet tot. Si t'equivoques per aquí, surt un 400 visible el primer dia. Si t'equivoques a l'inrevés, estàs enviant peticions capades sense adonar-te'n, i això pot córrer mesos.

Tres decisions de disseny que convé conèixer

Les tres són deliberades i condicionen per a què encaixa la peça.

El bucle és síncron de cap a peus. La resposta s'entrega sencera, no paraula a paraula. Si vols l'efecte d'escriptura progressiva, es resol a la capa d'entrega: en el nostre cas el torn es calcula en una cua i la interfície pregunta, que és el patró correcte per a un assistent de feina.

Davant d'un 429 es canvia de proveïdor, no s'espera. I ve apagat per defecte. La decisió és deliberada: una clau mal posada o una petició malformada no aniran millor en un altre lloc, i reintentar-les a tots converteix una fallada en tantes com proveïdors tinguis, cadascuna facturada. L'espera creixent, quan la vulguis, és una política teva i va a fora.

I una que és molt fàcil de fer malament. És temptador posar la decisió d'anonimitzar dades personals dins del bucle d'eines, perquè és on es crida el model. El problema és que el bucle no és l'únic que crida: el compressor d'historial també envia transcripcions senceres fora. Si l'anonimitzador és privat del bucle, aquesta segona via no té d'on treure'l i senzillament no pregunta. La decisió d'anonimitzar ha de viure fora i compartir-la tothom que parli amb el proveïdor.

Què hi ha al mercat i en què es diferencia

En PHP hi ha tres capes diferents i convé no confondre-les, perquè no resolen el mateix.

openai-php/laravel i els clients equivalents de cada proveïdor són la capa de baix: embolcallen l'API en un objecte còmode. Fan molt bé el que fan i no pretenen ser una altra cosa. El bucle, el topall, la compressió de l'historial i el control de saldo continuen sent teus.

Prism puja un esglaó: normalitza diversos proveïdors darrere d'una mateixa interfície i porta eines i sortida estructurada. Si el que necessites és poder canviar de model sense reescriure mig projecte, és una elecció molt sòlida.

Neuron AI ja es planteja directament com a framework d'agents en PHP, amb memòria, RAG i observabilitat. Fora de PHP, les referències són LangChain i LlamaIndex, i porten bastant més superfície de la que sol caldre.

Laragents, que és el que mantenim, va néixer de necessitar el bucle complet amb topall dur, control de saldo per usuari i compressió d'historial dins d'una aplicació Laravel que ja existia. Si el teu cas és una crida puntual a un model, qualsevol dels de dalt et sobra. Si el que posaràs en producció gasta diners per conversa, la llista de peces és la d'aquest article, la muntis amb el que la muntis.

I si no treballes amb Laravel

La llista del que cal construir és la mateixa en qualsevol stack, i és el que de debò hauries de pressupostar:

El bucle d'eines amb un sostre dur. La reinjecció del torn de l'assistent encara que vingui buit. Els errors d'eina com a resultat i no com a excepció. Una compressió d'historial que respecti els últims torns. La comprovació de saldo abans de cada crida, distingint el teu límit del seu. La normalització entre proveïdors. I algun mecanisme que talli la cerca infinita.

La crida al model, insisteixo, són quatre línies. Si el teu pressupost d'"integrar IA" contempla només això, hi faltarà tota la resta.

On és el codi

El paquet està publicat a Packagist, el codi a GitHub i la documentació del paquet recull totes les opcions. És en producció a Abodara, on l'assistent cerca immobles i creua demandes amb propietats.

Si estàs pensant a posar un assistent al teu producte i vols saber què hi ha sota el titular, és del que més treballem ara mateix a intel·ligència artificial i aplicacions web a mida. Explica'ns el cas i et diem quina part és real i quina part és demo.

Escrit per
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Desenvolupador full-stack amb més de 15 anys d'experiència en Laravel, React, Node.js i arquitectures cloud. Ajudo empreses a Andorra a construir la seva presència digital.

Partner de diseño · ionospace.
Necessites ajuda? ×
Andie by AndorraDev
Assistent IA + equip humà
Assistent IA d'AndorraDev
Andie
Hola! Soc Andie, l'assistent IA d'AndorraDev. En què et puc ajudar? Si necessites parlar amb Edu, només demana-ho.
17:27