# Pepper — API Agente (v1)
Questo documento definisce la **modalità agente** di Pepper: multi-turno, tool-use
(function calling) e streaming. È un contratto **separato e aggiuntivo** rispetto a
[API.md](?f=api), che resta valido e invariato per tutti gli altri progetti.
| | API classica (`/api.php`) | API agente (`/agent`) |
|---|---|---|
| Cosa manda il chiamante | `progetto` + `encode` + `contenuto` | `system` + `messages` + `tools` |
| Istruzioni (system prompt) | le decide Pepper (le "ricette") | le decide il chiamante |
| Memoria | la tiene Pepper (`conversazione`) | la tiene il chiamante (`messages`) |
| Attrezzi | no | sì |
| Streaming | no | sì (SSE) |
**Principio**: Pepper Plus è il cervello, non le mani. Capisce, pianifica e decide
quale attrezzo chiamare, ma **non esegue niente**: gli attrezzi li esegue il
chiamante, con i propri permessi e le proprie conferme.
## Come si attiva
La modalità agente va abilitata **per progetto** dal pannello di Pepper:
**Progetti → apri il progetto → "Modalità agente"**. Un progetto in modalità "Standard"
che chiama `/agent` riceve `403 agente_non_abilitato`.
Un progetto in modalità agente può comunque continuare a usare `/api.php`.
## Endpoint
```
POST https://gateway.pepperai.it/agent
Content-Type: application/json
Accept: text/event-stream # quando stream = true
X-Pepper-Key: <chiave del progetto>
X-Pepper-Project: <nome del progetto>
```
Funziona anche `POST /agent.php` (utile se il rewrite non è configurato — vedi
[Configurazione nginx](#configurazione-nginx) in fondo).
Chiave e progetto sono gli stessi di `/api.php`; qui il nome del progetto sta
nell'header `X-Pepper-Project` invece che nel corpo (è accettato anche come campo
`project` del corpo, come ripiego).
## Richiesta
```jsonc
{
"session_id": "andrea-dashboard-8f3a", // facoltativo, solo per il log
"stream": true, // true = SSE, false = JSON unico
"max_tokens": 1024,
"system": "Sei Pepper, l'assistente di casa di Andrea. Regole: proponi le azioni,
non darle per fatte; chiedi se ambiguo; sii conciso.",
"messages": [
{"role": "user", "content": "prepara casa per stanotte"},
{"role": "assistant", "content": null,
"tool_calls": [{"id": "c1", "name": "get_home_status", "arguments": {}}]},
{"role": "tool", "tool_call_id": "c1",
"content": "{\"presenza\":\"solo Andrea\",\"cancelletto\":\"aperto\"}"}
],
"tools": [
{
"name": "get_home_status",
"description": "Stato sintetico di casa: presenza, cancelli, clima, allarme.",
"parameters": {"type": "object", "properties": {}, "required": []}
},
{
"name": "set_climate",
"description": "Accende o spegne il condizionatore di una stanza o zona.",
"parameters": {
"type": "object",
"properties": {
"target": {"type": "string", "enum": ["salone", "zona_notte"]},
"action": {"type": "string", "enum": ["on", "off"]}
},
"required": ["target", "action"]
}
}
]
}
```
| Campo | Tipo | Obbl. | Note |
|---|---|:---:|---|
| `messages` | array | ✅ | La cronologia completa, in ordine. Formato qui sotto. |
| `system` | string | ❌ | Le istruzioni dell'agente. Se manca, valgono le istruzioni del modello Pepper. Se c'è, di serie **prende il posto** di quelle del modello; con la modalità «voce nelle app» invece **si somma** (vedi [Modello, limiti, comportamento](#modello-limiti-comportamento)). |
| `tools` | array | ❌ | Gli attrezzi disponibili. Può cambiare a ogni richiesta. |
| `stream` | bool | ❌ | `true` → SSE (§ Streaming). Predefinito `false`. |
| `max_tokens` | int | ❌ | Predefinito 4096. Limitato fra 256 e 32000. |
| `session_id` | string | ❌ | Serve solo a raggruppare le chiamate nel log di Pepper. **Pepper non tiene stato**: non è una sessione. |
### Formato `messages`
- `{"role": "user", "content": "<testo>"}`
— dalla 6d `content` può essere anche una **lista di blocchi**, per allegare
documenti: vedi [Documenti](#documenti-pdf). Chi manda stringhe non cambia niente.
- `{"role": "assistant", "content": "<testo>"}` — risposta testuale del modello.
- `{"role": "assistant", "content": null, "tool_calls": [{"id", "name", "arguments"}]}`
— il modello ha chiesto uno o più attrezzi. `arguments` è un **oggetto JSON**.
`content` può contenere anche testo insieme alle `tool_calls`.
- In ogni messaggio `assistant` può esserci anche **`"blocchi": [...]`**: la lista
che Pepper ha mandato nell'evento `done` di quel turno, **tale e quale**. Serve ai
modelli che ragionano: vedi [Il ragionamento e i `blocchi`](#il-ragionamento-e-i-blocchi).
Chi non la manda funziona come prima.
- `{"role": "tool", "tool_call_id": "<id>", "content": "<risultato>"}` — il risultato
che il chiamante ridà al modello. `content` è una **stringa** (testo o JSON serializzato).
Gli `id` delle `tool_calls` e i `tool_call_id` devono corrispondere: ogni attrezzo
richiesto deve avere il suo risultato prima della chiamata successiva.
### Formato `tools`
Function calling standard: `name`, `description`, `parameters` in **JSON Schema**.
`parameters` deve essere un oggetto (anche vuoto: `{"type":"object","properties":{}}`).
## Risposta
A ogni chiamata il modello può rispondere in testo, chiedere uno o più attrezzi, o
entrambe le cose. Chiude sempre con uno `stop_reason`:
- `"end_turn"` → ha finito, il testo è la risposta per l'utente.
- `"tool_use"` → vuole che il chiamante esegua gli attrezzi e richiami l'endpoint.
### Streaming — `stream: true`
`Content-Type: text/event-stream`, HTTP `200`. Sequenza di eventi tipizzati:
```
event: delta
data: {"text": "sto controllando"}
event: delta
data: {"text": " lo stato di casa…"}
event: tool_call
data: {"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}
event: done
data: {"stop_reason": "tool_use", "usage": {"input_tokens": 1840, "output_tokens": 96}, "versione": "Pepper 7", "novita": "…", "blocchi": [{"type": "thinking", "thinking": "", "signature": "…"}, {"type": "tool_use", "id": "toolu_01ABC", "name": "get_home_status", "input": {}}]}
```
- **`delta`** — un pezzo incrementale di testo. Concatenati danno il testo completo.
- **`tool_call`** — emesso quando gli argomenti dell'attrezzo sono **completi**
(già come oggetto JSON, non a pezzi). Possono essercene più d'uno.
- **`done`** — chiude il turno, con `stop_reason` (`end_turn` | `tool_use`), `usage`
(token consumati da **questa** chiamata, non dalla conversazione), `versione`
(il **modello** che ha risposto — «Pepper 7» — cioè il cervello scelto per il
progetto nel pannello: chi lo mostra lo prende da qui invece di tenerne una copia,
vedi I modelli), `novita` (una riga
del modello che dice cosa ha in più) e `blocchi` (la risposta com'era, pezzo per
pezzo: da conservare nel messaggio `assistant` di questo turno, vedi
[Il ragionamento e i `blocchi`](#il-ragionamento-e-i-blocchi)). `blocchi` c'è con
i modelli Claude.
`novita` c'è qui e non su `/api.php` per un motivo preciso: qui dall'altra parte
c'è una persona che guarda una chat, e quando il numero cambia il
[kit della chat](#il-kit-della-chat) può scriverglielo da sé. Su `/api.php` le
risposte se le legge una macchina e finiscono nei log: una riga di novità a ogni
chiamata sarebbe solo peso. Chi la vuole comunque la trova su `/capacita.php`.
- **`error`** — `{"message": "...", "code": "..."}`; subito dopo lo stream si chiude.
- Ogni ~15 secondi di silenzio Pepper manda un commento di keep-alive (`: ping`),
così i timeout intermedi non chiudono la connessione.
### Non-streaming — `stream: false`
Risposta unica `application/json`:
```json
{
"stop_reason": "tool_use",
"content": "sto controllando lo stato di casa…",
"tool_calls": [{"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}],
"usage": {"input_tokens": 1840, "output_tokens": 96},
"versione": "Pepper 7",
"novita": "…",
"blocchi": [ … ]
}
```
`content` è `null` se il modello non ha prodotto testo; `tool_calls` è `[]` se non
ha chiesto attrezzi. `usage`, `versione`, `novita` e `blocchi` sono gli stessi
dell'evento `done`.
In `usage` ci sono anche `cache_read_input_tokens` (letti dalla cache: costano un
decimo) e `cache_creation_input_tokens` (scritti in cache: un quarto in più).
Pepper mette in cache da sé attrezzi + istruzioni e la cronologia già mandata: la
cache regge solo se quella parte resta **identica** da un giro all'altro. Un
orario al minuto nel `system` la azzera a ogni messaggio: l'ora va nel messaggio.
### Come sommare `usage` senza contare due volte
`usage` è per singola chiamata. In un loop di attrezzi la cronologia viene rimandata
da capo a ogni giro, quindi **sommare gli `input_tokens` dei vari giri conta lo stesso
testo più volte**: cinque giri non costano cinque volte la cronologia, la contano solo
in modo crescente. Per il costo reale di un turno dell'utente:
- `output_tokens`: **somma** di tutti i giri;
- `input_tokens`: prendi **solo l'ultimo** giro (è il più grande e comprende i precedenti).
È un'approssimazione per difetto sull'input, ma è molto più vicina al vero della somma.
## Il loop di tool-use
1. `POST /agent` con `messages` + `tools`.
2. Pepper risponde: eventuale testo + eventuali `tool_call` + `done{stop_reason}`.
3. `stop_reason == "end_turn"` → fine, si mostra il testo. **Stop.**
4. `stop_reason == "tool_use"`:
- si accoda il turno assistant (testo + `tool_calls` + i `blocchi` del `done`) a `messages`;
- si eseguono gli attrezzi **in locale**, con permessi e conferme;
- si accoda un messaggio `{"role": "tool", tool_call_id, content}` per ciascuno
(anche per quelli non ancora eseguiti: es. `{"status":"in_attesa_conferma"}`);
- si richiama `POST /agent` con la cronologia aggiornata. Torna al punto 2.
5. Conviene mettere un tetto di sicurezza (es. 8 giri per messaggio utente):
quello sta al chiamante, Pepper non lo impone.
### Tre regole da non sbagliare
1. **Il testo dei `delta` è esattamente il `content` da rimandare.** I pezzi
concatenati danno la stringa da mettere nel turno assistant: non va rielaborata.
2. **Testo e attrezzi stanno nello stesso messaggio assistant, non in due.** Se il
turno ha prodotto sia `delta` sia `tool_call`, si rimanda un solo messaggio:
`{"role": "assistant", "content": "...", "tool_calls": [...]}`.
3. **Ogni `tool_call` deve avere il suo risultato, senza eccezioni.** Anche quando
l'azione è in attesa della conferma dell'utente va mandato il messaggio `tool`
con un contenuto qualsiasi (es. `{"status":"in_attesa_conferma"}`). Se ne manca
anche uno solo, la chiamata successiva viene **rifiutata dal provider**: è il
modo più facile di rompere il loop, e capita proprio nel caso più frequente —
le azioni che passano dalla conferma.
Gli `id` delle `tool_call` li genera Pepper e vanno riecheggiati **identici**, sia in
`assistant.tool_calls[].id` sia nel `tool_call_id` del risultato.
## Errori
Formato: HTTP `4xx`/`5xx` con corpo
```json
{"error": {"code": "agente_non_abilitato", "message": "La modalità agente non è attiva per il progetto 'pepper-home'."}}
```
| HTTP | `code` | Quando |
|---|---|---|
| 400 | `richiesta_non_valida` | JSON malformato, `messages` mancante o messaggi con ruoli/campi errati |
| 401 | `non_autorizzato` | Chiave assente, errata o non abbinata al progetto |
| 403 | `agente_non_abilitato` | Il progetto non ha la modalità agente attiva |
| 405 | `metodo_non_valido` | Non è una POST |
| 413 | `richiesta_troppo_grande` | Corpo oltre 32 MB |
| 429 | `troppe_richieste` | Superato il limite orario del progetto (header `Retry-After`) |
| 429 | `budget_esaurito` | Superato il tetto di spesa giornaliero del progetto (header `Retry-After`) |
| 422 | `richiesta_rifiutata` | Il modello si è rifiutato di rispondere per motivi di sicurezza: riprovare uguale non serve |
| 502 | `errore_ai` | Il provider AI ha risposto con un errore |
| 503 | `servizio_non_disponibile` | Database o configurazione non raggiungibili |
Se l'errore capita **a stream già aperto** non si può più cambiare il codice HTTP:
esce come `event: error` sullo stream, che poi si chiude. Il chiamante deve quindi
gestire l'errore in tutti e due i modi.
## Modello, limiti, comportamento
- **Motore**: quello del **modello Pepper** del progetto (pannello → Progetti, e i
modelli in pannello → Modelli). Provider Claude o OpenAI secondo il modello: il contratto
qui sopra non cambia, l'adattatore traduce.
- **Istruzioni**: il `system` lo decide chi chiama. Se non lo manda, valgono le
istruzioni del modello. Se lo manda, dipende dal modello:
- di serie il `system` dell'app **prende il posto** delle istruzioni del modello:
l'app parla con un assistente che non sa di essere Pepper, se non glielo dice lei;
- con la modalità **«voce nelle app»** accesa si **sommano**: prima le istruzioni
del modello (chi è Pepper), poi il `system` dell'app. In questo caso l'app manda
solo le sue regole operative, e la voce resta quella di Pepper.
Quale dei due vale lo dice `/capacita.php` (`pepper.voce_nelle_app`). La modalità si
sceglie quando nasce un modello e non cambia più mentre qualcuno lo usa: un
progetto non si ritrova un comportamento diverso senza aver cambiato modello.
- **Ragionamento**: lo decide il modello, non l'app. Vedi qui sotto.
- **Timeout**: nessun limite imposto da Pepper oltre i 900 secondi di sicurezza dello
script e i 600 verso il provider. In streaming i ping tengono viva la connessione.
- **Contesto**: il limite è quello del modello (1M token su Opus/Sonnet recenti). Chi
chiama è responsabile di non mandare cronologie infinite.
- **Dimensione dei risultati degli attrezzi**: Pepper non mette un limite per singolo
campo. I tetti veri sono tre: il corpo della richiesta (32 MB, come il provider),
il contesto del modello, e il costo. Un risultato da 100 KB di testo sono circa
25.000 token, che vengono **rimandati a ogni giro successivo** del loop: conviene che
gli attrezzi restituiscano già il sunto (le ultime N righe, i totali) invece del
dump completo, e che sia l'agente a chiedere il dettaglio con un secondo attrezzo
se gli serve.
- **Log**: ogni chiamata finisce nel monitor di Pepper come encode `agente`, con
token consumati, durata ed esito.
## Il ragionamento e i `blocchi`
Un modello può **ragionare prima di rispondere** («Pepper 7»). Non lo sceglie l'app:
lo sceglie chi gestisce Pepper, nel pannello, modello per modello. Risponde meglio
sulle cose difficili, ci mette di più e costa di più. Su alcuni motori (Opus 5.5,
Fable 5.1) il ragionamento non si può spegnere affatto.
**Il lavoro in più per l'app, e perché.** Quando Pepper ragiona e poi chiede un
attrezzo, al giro dopo vuole indietro anche i pezzi del suo ragionamento,
**identici e nello stesso ordine**. Per questo `done` porta `blocchi`: la risposta
com'era, pezzo per pezzo. L'app non la guarda e non la tocca — è una scatola chiusa
— e la rimette nel messaggio `assistant` di quel turno:
```json
{"role": "assistant", "content": "Controllo il salone.",
"tool_calls": [{"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}],
"blocchi": [ …esattamente quello arrivato nel `done`… ]}
```
Quando ci sono, Pepper rimanda al provider **quelli** e non ricostruisce niente da
`content` e `tool_calls`.
Tre regole:
1. **I `blocchi` vanno conservati come JSON, non ricostruiti.** Niente campi tolti,
niente riordinati, e `{}` deve restare `{}` (non `[]`).
2. **La cronologia si allunga soltanto.** Mai togliere, accorciare o riscrivere un
messaggio vecchio mentre la conversazione è aperta, e mai cambiare `system` o
`tools` a metà: il ragionamento dopo quel punto non varrebbe più. Pepper in quel
caso lo scarta invece di dare errore (la risposta è un po' meno ragionata, e nel
log di Pepper si vede), ma è meglio non succeda.
3. **Chi non manda i `blocchi` non si rompe.** Se in un giro di attrezzi ancora
aperto un messaggio `assistant` arriva senza, per quel giro risponde la
**riserva** del modello: un motore che sa andare avanti senza ragionamento. La
conversazione continua, e nel pannello di Pepper il progetto risulta «app senza
blocchi» finché non viene aggiornata. Nessuna levetta da accendere: Pepper se ne
accorge da solo, a ogni chiamata.
Con un modello che non ragiona i `blocchi` arrivano lo stesso (testo e attrezzi):
conservarli da subito non costa niente, e il giorno che il progetto passa a un
modello che ragiona l'app è già pronta.
Chi ragiona ha bisogno di spazio: se il modello ragiona, Pepper alza da solo
`max_tokens` ad almeno 16.000.
## Documenti (PDF)
Dalla 6d un messaggio dell'utente può portarsi dietro dei PDF, senza uscire dal
contratto: `content`, invece di una stringa, diventa una lista di blocchi.
```jsonc
{
"role": "user",
"content": [
{"tipo": "documento", "nome": "verbale.pdf", "dati": "<il PDF in base64>"},
{"tipo": "testo", "testo": "Di cosa parla? Chi erano i presenti?"}
]
}
```
Un blocco è `{"tipo": "testo", "testo": "..."}` oppure
`{"tipo": "documento", "nome": "...", "dati": "<base64>"}` — o `"url"` al posto di
`"dati"`, con un indirizzo `https://` raggiungibile.
Vale tutto quello che è scritto in [API.md → Documenti](?f=api#documenti-pdf):
stessi limiti (5 documenti, 10 MB l'uno, 600 pagine — 100 sul modello economico),
stesso permesso per progetto (**Progetti → Documenti**, `403
documenti_non_abilitati` senza), e solo con Claude.
Le **foto** viaggiano nello stesso blocco, con `tipo_file`:
`{"tipo": "documento", "nome": "foto.jpg", "tipo_file": "image/jpeg", "dati": "<base64>"}`.
Valgono le regole di [API.md → Foto](?f=api#foto): JPEG, PNG, GIF, WebP, 5 MB
l'una, e un permesso loro (**Progetti → Immagini**, `403 immagini_non_abilitate`
senza).
**Le citazioni qui non ci sono.** In modalità agente la risposta esce a pezzi via
streaming e il contratto non ha un posto dove metterle. È la stessa scelta fatta
per il ragionamento esteso: meglio una capacità dichiaratamente assente che una
che si perde per strada. Chi ha bisogno delle citazioni usa `/api.php`.
## Il kit della chat
Se stai costruendo una chat sopra questo endpoint, non serve disegnarla da zero:
Pepper serve lui `pepper-chat.css` e `pepper-chat.js`, e si collegano così:
```html
<link rel="stylesheet" href="https://<pepper>/kit.php?f=css">
<div id="chat"></div>
<script src="https://<pepper>/kit.php?f=js"></script>
```
Sono due file senza librerie e sanno già leggere gli eventi di questa pagina:
bolla che si allunga mentre il testo arriva, pallino dell'attrezzo che Pepper sta
usando, riga di stato, il nome del Pepper con cui si parla sulla chat vuota — e la
riga di novità quando il modello cambia.
Per il nome sulla chat vuota, la pagina dice al kit a che progetto appartiene:
`PepperChat.attacca(el, {indirizzo: '/mio-server/chat', progetto: 'dossiero'})`.
Il kit chiede a `kit.php?progetto=dossiero` e scrive «Pepper 7» sopra «Come posso
aiutarti?». È il **nome** del progetto, non la chiave: `kit.php` restituisce solo il
nome del modello, niente altro.
**Collegati così, si aggiornano da soli.** Il giorno che nel kit si sistema una
cosa, arriva al ricaricamento dopo, in tutte le chat insieme — anche in quelle
che non abbiamo scritto noi. Non serve nessuna chiave: la chat parla col *tuo*
server, non con Pepper. `kit.php` risponde con un `ETag` e `Cache-Control:
public, no-cache`, quindi il browser se lo tiene e quasi sempre riceve un `304`
invece del file; `https://<pepper>/kit.php` senza parametri dice quale versione
del kit c'è adesso, se un deploy vuole saperlo senza scaricarlo.
Nessuna delle chat di casa ne tiene più una copia: il pannello di Pepper,
Dossiero, il pannello workspace e la V5 collegano tutti `kit.php`, e nessuno dei
quattro ha l'indirizzo scritto a mano — se lo ricava da quello dell'API che ha
già in configurazione, così il kit arriva sempre dallo *stesso* Pepper che poi
risponde.
Copiarli si può ancora, e per qualcuno resta obbligatorio: una chat che deve
funzionare con internet giù non può prendere il suo JavaScript da fuori. In quel
caso però la copia non si aggiorna a mano — vedi `scripts/kit-chat.sh` nel
repository di Pepper, che il gancio `post-commit` fa scattare da solo ogni volta
che il kit cambia.
Il kit capisce `delta`, `tool_call`, `done` ed `error` così come sono definiti qui,
più due eventi che **puoi mandare tu**: `tool_done {id, ok}`, per far diventare
verde (o rosso) il pallino dell'attrezzo quando l'hai eseguito, e `pending
{actions}`, per i bottoni di conferma di un'azione che Pepper ha proposto.
Pepper non può mandarli — gli attrezzi li esegui tu, e solo tu sai quando hanno
finito e cosa richiede una conferma.
Dentro ci sono anche allegati, lettura ad alta voce, microfono push-to-talk, i
bottoni di conferma e «Nuova chat» (che vuole `nuova: { quando }`: la cronologia
sta sul tuo server, e la cancella lui): si accendono una per una (`funzioni`), e di fabbrica sono
quasi tutte spente. L'elenco completo è nella pagina **API** del pannello di
Pepper.
Quello che **non** si accende è lo stile: l'a capo dopo ogni frase e il
grassetto valgono sempre, in tutte le chat, e la levetta per spegnerli non c'è
più. Erano due opzioni, e il risultato è stato che la stessa risposta di Pepper
si leggeva in due modi a seconda del progetto.
**Se ti serve qualcosa che non c'è, si aggiunge al kit e non al progetto**: è
l'unico modo perché il progetto dopo se la trovi già pronta. E se è una cosa di
aspetto, si cambia nel kit senza farne un'opzione.
Una raccomandazione che vale più del kit: **la chiave del progetto non deve mai
finire nel browser**. Il giro è sempre *pagina → il tuo server → Pepper*.
## Cosa non c'è nella v1
- **`tool_choice`** (forzare o vietare l'uso degli attrezzi). Attenzione se un
giorno si aggiunge: sui motori nuovi (Opus 5.5, Fable 5.1) *forzare* un attrezzo
(`any` o un attrezzo preciso) è un errore del provider; resta possibile solo
`auto` o `none`.
- **La ricerca sul web di Pepper**: su `/api.php` si accende dal pannello, qui no —
e non è una dimenticanza. In modalità agente gli attrezzi li dichiari tu: se
vuoi che Pepper cerchi, la ricerca la metti fra i tuoi `tools` e la esegui tu,
con le tue regole. È lo stesso principio di tutto il resto: **Pepper è il
cervello, non le mani**.
- **Memoria lato Pepper**: in modalità agente la cronologia è del chiamante, per scelta.
## Configurazione nginx
L'URL `/agent` (senza `.php`) richiede una riga nel vhost:
```nginx
location = /agent {
rewrite ^ /agent.php last;
}
```
Senza questa riga funziona comunque `POST /agent.php`.
Va inoltre verificato che il buffering non spezzi lo streaming: Pepper manda già
`X-Accel-Buffering: no`, che nginx rispetta, quindi normalmente non serve altro.
## Esempio — un giro completo
```bash
curl -N https://gateway.pepperai.it/agent \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "X-Pepper-Key: <chiave>" \
-H "X-Pepper-Project: pepper-home" \
-d '{
"stream": true,
"max_tokens": 1024,
"system": "Sei Pepper, l assistente di casa. Proponi le azioni, non darle per fatte.",
"messages": [{"role": "user", "content": "che aria fa in salone?"}],
"tools": [{
"name": "get_home_status",
"description": "Stato sintetico di casa.",
"parameters": {"type": "object", "properties": {}, "required": []}
}]
}'
```