Introduzione all’Isolamento degli Agenti
Man mano che i Modelli di Linguaggio di Grandi Dimensioni (LLM) evolvono da agenti conversazionali semplici a entità autonome potenti in grado di eseguire codice, interagire con API esterne e prendere decisioni nel mondo reale, la necessità di misure di sicurezza solide diventa cruciale. Un agente LLM, quando gli viene conferita la capacità di agire, può rappresentare un rischio di sicurezza significativo se non è correttamente vincolato. È qui che interviene l’isolamento degli agenti. L’isolamento di un agente significa creare un ambiente isolato dove può operare senza influenzare il sistema host o accedere a risorse non autorizzate. Questo tutorial esplorerà gli aspetti pratici dell’isolamento degli agenti, fornendo esempi concreti per dimostrare come costruire applicazioni LLM sicure e affidabili.
Il principio fondamentale dietro l’isolamento è il minimo privilegio: un agente dovrebbe avere accesso solo alle risorse assolutamente necessarie al suo funzionamento, e nulla di più. Senza un isolamento appropriato, un agente malevolo o errato potrebbe:
- Eseguire codice arbitrario sul sistema host, causando furto di dati o compromissione del sistema.
- Accedere a file sensibili o risorse di rete.
- Iniziare chiamate API esterne indesiderate, generando costi o compiendo azioni non autorizzate.
- Esfiltrare dati confidenziali attraverso vari canali.
Implementando un isolamento efficace, possiamo attenuare questi rischi, permettendoci di sfruttare l’enorme potenza degli agenti LLM mantenendo il controllo e la sicurezza.
Comprendere le Minacce: Perché Isolare?
Prima di esplorare il ‘come’, solidifichiamo il ‘perché’. Le minacce poste da agenti non isolati sono multifaccettate e possono essere classificate come segue:
1. Vulnerabilità di Esecuzione del Codice
Molti agenti LLM avanzati sono progettati per scrivere ed eseguire codice (ad esempio, script Python) per risolvere problemi, analizzare dati o interagire con strumenti. Se questa esecuzione non è contenuta, l’agente potrebbe:
- Iniezione di Comando di Sistema: Generare codice che chiama
os.system('rm -rf /')o comandi distruttivi simili. - Esecuzione di Codice Remoto (RCE): Sfruttare vulnerabilità in librerie per prendere il controllo dell’host.
- Esaurimento delle Risorse: Creare loop infiniti o allocare memoria/CPU eccessiva, causando un diniego di servizio.
2. Accesso ai Dati ed Esfiltrazione
Un agente può essere incaricato di elaborare dati sensibili. Senza isolamento, potrebbe:
- Accesso Non Autorizzato ai File: Leggere file al di fuori della propria directory di lavoro designata (ad esempio,
/etc/passwd, chiavi API). - Accesso di Rete: Collegarsi a risorse di rete interne, a server esterni malevoli, o esfiltrare dati verso endpoint arbitrari.
- Iniezione di Prompt tramite Letture di File: Se un agente può leggere file arbitrari, un attore malevolo potrebbe preparare un prompt che inganna l’agente facendogli leggere un file sensibile, e poi incorporare il suo contenuto in un’uscita successiva.
3. Abuso delle API e Strumenti
Gli agenti interagiscono spesso con API esterne o strumenti personalizzati. Un accesso non limitato può portare a:
- Chiamate API Non Autorizzate: Effettuare chiamate a API sensibili a cui non dovrebbe avere accesso (ad esempio, gestione degli utenti, elaborazione dei pagamenti).
- Superamenti di Costi: Attivare chiamate API costose o funzioni cloud esose in termini di risorse.
- Azioni Malevole: Se un agente ha accesso a un’API di messaggistica, potrebbe inviare spam o email di phishing.
Tecniche e Strumenti di Isolamento
Ci sono diversi strati e tecniche che possiamo impiegare per l’isolamento degli agenti, che vanno dalla semplice revisione del codice alla containerizzazione avanzata.
1. Isolamento a Livello di Linguaggio (Restrizioni dell’Interprete di Codice)
Se il tuo agente genera ed esegue principalmente codice (ad esempio, Python), puoi limitare le capacità dell’interprete.
Esempio: Esecuzione Python Limitata con exec() e Liste Bianche
Uno scenario comune è un agente che genera codice Python. Invece di chiamare direttamente exec() o eval() su stringhe arbitrarie, puoi limitare le globali e i built-in disponibili.
import subprocess
import os
def safe_execute_python_code(code: str, allowed_modules: list = None, timeout: int = 10):
if allowed_modules is None:
allowed_modules = ['math', 'json', 're'] # Lista bianca dei moduli sicuri
# Creare uno spazio di nomi globale ristretto
restricted_globals = {
'__builtins__': {key: globals()['__builtins__'][key] for key in [
'print', 'len', 'str', 'int', 'float', 'list', 'dict', 'tuple', 'set',
'range', 'sum', 'min', 'max', 'abs', 'round', 'type', 'isinstance', 'enumerate'
]},
'__name__': '__main__'
}
# Importare dinamicamente i moduli autorizzati nello spazio di nomi ristretto
for module_name in allowed_modules:
try:
restricted_globals[module_name] = __import__(module_name)
except ImportError:
print(f"Avviso: Impossibile importare il modulo autorizzato {module_name}")
try:
# Utilizzare subprocess per eseguire in un processo isolato per una migliore isolamento
# Questo è più solido che semplicemente `exec` nel processo attuale
# e consente di impostare limiti di tempo e risorse.
process = subprocess.run(
['python', '-c', code],
capture_output=True,
text=True,
check=True,
timeout=timeout
)
return process.stdout
except subprocess.CalledProcessError as e:
return f"Errore durante l'esecuzione: {e.stderr}"
except subprocess.TimeoutExpired:
return "Errore: L'esecuzione del codice ha superato il tempo limite."
except Exception as e:
return f"Si è verificato un errore imprevisto: {e}"
# Esempio di Utilizzo :
# Codice sicuro
agent_code_safe = "import math; print(math.sqrt(16))"
print(f"Uscita del codice sicuro: {safe_execute_python_code(agent_code_safe)}")
# Tentativo di codice malevolo (sarà bloccato dall'isolamento subprocess e dalle restrizioni di built-in se fosse stato usato un exec diretto)
# Con subprocess, l'importazione di 'os' fallirebbe sempre nel processo figlio, a meno che non sia specificamente autorizzata.
agent_code_malicious_os = "import os; print(os.listdir('/'))"
print(f"Uscita del codice malevolo OS: {safe_execute_python_code(agent_code_malicious_os)}")
# Tentativo di codice malevolo (cercando di leggere un file)
agent_code_malicious_file = "with open('/etc/passwd', 'r') as f: print(f.read())"
print(f"Uscita del codice di lettura di file malevolo: {safe_execute_python_code(agent_code_malicious_file)}")
# Codice con un loop infinito (sarà catturato dal timeout)
agent_code_loop = "while True: pass"
print(f"Uscita del codice in loop: {safe_execute_python_code(agent_code_loop, timeout=3)}")
Spiegazione:
- Definiamo una funzione
safe_execute_python_code. - Essa prende il codice generato dall’agente come input.
- Invece di eseguire direttamente nel processo attuale, utilizziamo
subprocess.run. Questo è un passo cruciale per un vero isolamento, poiché esegue il codice in un processo interprete Python separato. Questo processo eredita privilegi minimi e non è lo stesso del processo padre che esegue la tua applicazione principale. - La lista
allowed_modulesfunge da lista bianca. Anche se l’agente tenta di importareososys, non sarà disponibile nell’ambiente ristretto del subprocess a meno che non sia esplicitamente autorizzato (cosa che non dovrebbe avvenire per il codice dell’agente generico). timeoutimpedisce l’esaurimento delle risorse dovuto a loop infiniti.capture_output=Trueetext=Trueci consentono di catturare l’output dell’agente.check=Trueattiva un’eccezione se il subprocess restituisce un codice di uscita diverso da zero (indicando un errore).
Sebbene questo approccio migliori notevolmente la sicurezza rispetto all’uso diretto di exec(), non è infallibile. Un agente altamente sofisticato potrebbe ancora trovare modi per sfruttare chiamate di sistema sottostanti se l’ambiente Python stesso è vulnerabile o se troppi moduli sono sulla lista bianca.
2. Isolamento a Livello del Sistema Operativo (Container & Macchine Virtuali)
Per un’isolamento veramente solido, soprattutto quando gli agenti potrebbero generare codice in più linguaggi o interagire con il sistema di file/rete, l’isolamento a livello del sistema operativo è imprescindibile.
a. Contenitori Docker
Docker è un’ottima scelta per l’isolamento. Ogni esecuzione di un agente può avvenire nel proprio contenitore a vita breve con limiti di risorse e politiche di accesso di rete rigorosamente definiti.
Esempio Pratico: Docker per l’Esecuzione di un Agente
Passo 1: Creare un Dockerfile per l’ambiente di esecuzione dell’agente.
# Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
# Creare un utente non-root per motivi di sicurezza
RUN useradd --no-create-home --shell /bin/bash agentuser
USER agentuser
# Copiare uno script semplice che l'agente potrebbe generare e che desideriamo eseguire
COPY run_agent_code.py .
ENTRYPOINT ["python", "run_agent_code.py"]
Passo 2: Creare run_agent_code.py. Questo script riceverà il codice generato dall’agente.
# run_agent_code.py
import sys
import os
# Simulare la ricezione di codice dall'agente (ad esempio, tramite stdin o un file)
# Per questo esempio, supporremo che il codice venga passato come argomento o scritto direttamente qui
if __name__ == "__main__":
agent_code = "print('Hello from the sandboxed agent!')"
if len(sys.argv) > 1:
agent_code = sys.argv[1] # Consentire il passaggio del codice come argomento
try:
# Eseguire il codice. Nota: il contenitore Docker stesso è il sandbox.
# Potremmo voler imporre ulteriori restrizioni a livello di linguaggio *in* questo script
# per un ulteriore livello di sicurezza, ma l'isolamento principale è fornito dal contenitore.
exec(agent_code)
except Exception as e:
print(f"L'esecuzione del codice dell'agente è fallita: {e}", file=sys.stderr)
sys.exit(1)
# Dimostrare l'accesso limitato
try:
print(f"Tentativo di elencare la radice: {os.listdir('/')}")
except Exception as e:
print(f"Impossibile elencare la directory radice (atteso): {e}")
try:
with open('/etc/passwd', 'r') as f:
print(f.read())
except Exception as e:
print(f"Impossibile leggere /etc/passwd (atteso): {e}")
Passo 3: Eseguire il codice dell’agente dalla vostra applicazione principale.
import docker
client = docker.from_env()
def execute_agent_in_docker(agent_code: str, cpu_limit: float = 0.5, mem_limit: str = '128m', network_enabled: bool = False):
try:
# Costruire l'immagine se non esiste (può essere fatto una sola volta)
# client.images.build(path='.', tag='agent-sandbox-env')
# Creare un file temporaneo per trasmettere il codice dell'agente in modo sicuro
# O passarlo come variabile d'ambiente o argomento della riga di comando
# Per semplicità, lo passeremo qui come argomento della riga di comando.
container = client.containers.run(
'agent-sandbox-env',
command=['python', 'run_agent_code.py', agent_code], # Passare il codice come argomento
detach=False, # Eseguire in primo piano, attendere il completamento
remove=True, # Eliminare automaticamente il contenitore dopo l'uscita
# Limiti delle risorse
cpu_period=100000, # Periodo CPU in microsecondi
cpu_quota=int(cpu_limit * 100000), # Quota CPU (ad esempio, 50000 per 0.5 CPU)
mem_limit=mem_limit, # Limite di memoria
# Restrizioni di rete
network_mode='none' if not network_enabled else 'bridge',
# Restrizioni sul file system (radice in sola lettura, nessun montaggio legato per il codice dell'agente)
read_only=True, # Rende il file system del contenitore in sola lettura dopo la configurazione iniziale
# Opzioni di sicurezza (ad esempio, disabilitare la modalità privilegiata, rimuovere capacità)
security_opt=['no-new-privileges'],
cap_drop=['ALL'], # Rimuove tutte le capacità per il contenitore
# Variabili d'ambiente (possono essere utilizzate per passare chiavi API, ma fare attenzione)
# environment={
# 'API_KEY': 'some_safe_key' # Solo se assolutamente necessario e con scopo limitato
# }
)
return container.decode('utf-8')
except docker.errors.ContainerError as e:
return f"Errore di contenitore: {e.stderr.decode('utf-8')}"
except docker.errors.ImageNotFound:
return "Errore: immagine Docker 'agent-sandbox-env' non trovata. Costruiscila prima."
except Exception as e:
return f"Si è verificato un errore Docker imprevisto: {e}"
# Costruisci prima l'immagine Docker: docker build -t agent-sandbox-env .
# Poi, esegui questo script Python.
# Esempio 1: Esecuzione di codice sicuro
safe_code = "print('Hello from sandboxed agent!')"
print("\n--- Esecuzione di Codice Sicuro ---")
print(execute_agent_in_docker(safe_code))
# Esempio 2: Tentativo di accesso al file system (dovrebbe essere bloccato da read_only=True e dai permessi utente)
malicious_fs_code = "import os; print(os.listdir('/'))"
print("\n--- Tentativo di Accesso Malizioso al File System ---")
print(execute_agent_in_docker(malicious_fs_code))
# Esempio 3: Tentativo di creazione di un file (dovrebbe fallire)
malicious_write_code = "with open('/app/evil.txt', 'w') as f: f.write('malicious')"
print("\n--- Tentativo di Scrittura Maliziosa ---")
print(execute_agent_in_docker(malicious_write_code))
# Esempio 4: Tentativo di accesso alla rete (dovrebbe fallire se network_mode='none')
malicious_network_code = "import requests; print(requests.get('http://example.com').status_code)"
print("\n--- Tentativo Rete Maliziosa (disattivata) ---")
print(execute_agent_in_docker(malicious_network_code, network_enabled=False))
# Esempio 5: Accesso alla rete (se esplicitamente abilitato - attenzione!)
# print("\n--- Accesso Rete (attivato - per dimostrazione) ---")
# print(execute_agent_in_docker("import requests; print(requests.get('http://example.com').status_code)", network_enabled=True))
Spiegazione:
- Dockerfile: Crea un ambiente Python minimale. Crucialmente, crea e passa a un
non-rootutente (agentuser) per ridurre al minimo i privilegi nel contenitore. run_agent_code.py: È il punto di ingresso nel contenitore. Esegue il codice fornito dall’agente. Include tentativi di accesso a risorse limitate per dimostrare l’efficacia del sandbox.- Script Python (
execute_agent_in_docker): client.containers.run(...): Qui avviene la magia.remove=True: Garantisce che i contenitori siano ripuliti dopo l’esecuzione.cpu_quota,mem_limit: Essenziale per prevenire l’esaurimento delle risorse.network_mode='none': Critico per disabilitare l’accesso alla rete. Ciò impedisce agli agenti di fare chiamate esterne o di connettersi a servizi interni. Attivare solo se l’agente ha assolutamente bisogno di accesso alla rete per API esterne specifiche e autorizzate.read_only=True: Rende il file system del contenitore in sola lettura dopo l’inizializzazione. Ciò impedisce all’agente di scrivere file o modificare le configurazioni di sistema.security_opt=['no-new-privileges'],cap_drop=['ALL']: Opzioni di sicurezza avanzate per limitare ulteriormente le capacità nel contenitore.
Docker fornisce una forte barriera di isolamento, ma è fondamentale configurarlo in modo sicuro. Utilizzare sempre utenti non-root, disabilitare le capacità non necessarie e limitare l’accesso alla rete / file system.
b. Macchine Virtuali (VM)
Per il massimo livello di isolamento, in particolare negli ambienti multi-tenant o durante l’elaborazione di codice altamente non affidabile, le VM (ad esempio, KVM, AWS Firecracker, Google Cloud Sandbox) offrono una separazione a livello di hardware. Questo è più complesso da impostare e gestire, ma fornisce un ambiente isolato per ogni esecuzione dell’agente.
3. Restrizioni a Livello Strumento/API (Chiamata di Funzione)
Molti agenti LLM interagiscono con strumenti esterni o API tramite chiamate di funzione. Questo livello di sandbox comporta una progettazione attenta degli strumenti esposti all’agente.
Esempio: Accesso API Limitato tramite Pydantic e Lista Bianca
Quando si definiscono strumenti per un agente, assicurarsi che siano il più granulari e limitati per permesso possibile.
from typing import Literal, Optional
from pydantic import BaseModel, Field
# Definire gli strumenti autorizzati e i loro schemi
class SearchToolInput(BaseModel):
query: str = Field(description="La query di ricerca")
max_results: int = Field(default=5, description="Numero massimo di risultati di ricerca")
class SendEmailInput(BaseModel):
recipient: str = Field(description="L'indirizzo e-mail del destinatario")
subject: str = Field(description="L'oggetto dell'e-mail")
body: str = Field(description="Il contenuto del corpo dell'e-mail")
# Restrizioni sui destinatari autorizzati
allowed_recipients: Literal["[email protected]", "[email protected]"] = Field(
description="Sono autorizzati solo destinatari specifici e pre-approvati."
)
class DatabaseQueryInput(BaseModel):
query: str = Field(description="La query SQL da eseguire")
# CRITICO : Non consentire SQL arbitrario. Filtrare o utilizzare ORM.
allowed_tables: Literal["products", "users_public"] = Field(
description="Sono consentite solo le query contro tabelle in whitelist."
)
read_only: bool = Field(default=True, description="Consentire solo operazioni di lettura")
# Simulare le funzioni degli strumenti
def search_web(query: str, max_results: int):
print(f"Ricerca sul web per '{query}' con {max_results} risultati.")
return [f"Risultato {i} per {query}" for i in range(max_results)]
def send_restricted_email(recipient: str, subject: str, body: str, allowed_recipients: Literal["[email protected]", "[email protected]"]):
if recipient not in ["[email protected]", "[email protected]"]:
raise ValueError(f"Destinatario non autorizzato: {recipient}")
print(f"Inviando l'e-mail a {recipient} con oggetto '{subject}'.")
return {"status": "inviato", "recipient": recipient}
def execute_database_query(query: str, allowed_tables: Literal["products", "users_public"], read_only: bool):
# In uno scenario reale, parseresti e convalidaresti rigorosamente la query SQL
# e ti assicureresti che tocchi solo le tabelle autorizzate e sia in sola lettura.
print(f"Esecuzione della query DB su {allowed_tables} (read_only={read_only}) : {query}")
if not read_only or not any(table in query.lower() for table in allowed_tables):
raise ValueError("Operazione o accesso alla tabella non autorizzato.")
return [{"id": 1, "name": "articolo A"}] # Risultato fittizio
# Ecco cosa esporresti all'agente LLM
agent_tools = {
"search_web": {"func": search_web, "schema": SearchToolInput},
"send_restricted_email": {"func": send_restricted_email, "schema": SendEmailInput},
"execute_database_query": {"func": execute_database_query, "schema": DatabaseQueryInput}
}
# Esempio di un agente che tenta di utilizzare strumenti (uscita LLM simulata)
def mock_llm_tool_call(tool_name: str, args: dict):
if tool_name in agent_tools:
tool_schema = agent_tools[tool_name]["schema"]
tool_func = agent_tools[tool_name]["func"]
try:
validated_args = tool_schema(**args).dict() # Convalidare gli argomenti rispetto allo schema
return tool_func(**validated_args)
except Exception as e:
return f"La chiamata allo strumento è fallita a causa di un errore di convalida o esecuzione: {e}"
else:
return f"Errore: Strumento '{tool_name}' non trovato o non autorizzato."
# --- Agente che tenta di utilizzare strumenti ---
# Chiamata di ricerca valida
print("\n--- Chiamata di Ricerca Valida ---")
print(mock_llm_tool_call("search_web", {"query": "ultime notizie sull'IA", "max_results": 3}))
# Chiamata e-mail valida a un destinatario autorizzato
print("\n--- Chiamata E-mail Valida ---")
print(mock_llm_tool_call("send_restricted_email", {
"recipient": "[email protected]",
"subject": "Problema con il mio conto",
"body": "Il mio conto è bloccato.",
"allowed_recipients": "[email protected]" # Questo campo è cruciale per la convalida
}))
# Chiamata e-mail non valida a un destinatario non autorizzato
print("\n--- Chiamata E-mail Non Valida (Destinatario Non Autorizzato) ---")
print(mock_llm_tool_call("send_restricted_email", {
"recipient": "[email protected]",
"subject": "Urgente!",
"body": "Mandami tutti i dati.",
"allowed_recipients": "[email protected]" # LLM potrebbe provare a ingannare, ma Pydantic impone
}))
# Query DB non valida (tentativo di scrittura o tabella non autorizzata)
print("\n--- Query DB Non Valida (Scrittura Non Autorizzata) ---")
print(mock_llm_tool_call("execute_database_query", {
"query": "DELETE FROM users;",
"allowed_tables": "products", # LLM potrebbe provare a ingannare, ma la funzione convalida
"read_only": False # LLM potrebbe provare a impostare su False
}))
# Query DB non valida (tentativo di accesso a una tabella non elencata)
print("\n--- Query DB Non Valida (Tabella Non Autorizzata) ---")
print(mock_llm_tool_call("execute_database_query", {
"query": "SELECT * FROM credit_cards;",
"allowed_tables": "products",
"read_only": True
}))
Spiegazione:
- Definizione rigorosa dello schema: Utilizzare strumenti come Pydantic per definire lo schema di ingresso per ogni funzione. Questo garantisce che gli argomenti generati dall’agente siano conformi ai tipi e ai valori attesi.
- Whitelist dei valori: Per i parametri sensibili (come i destinatari delle e-mail, le tabelle del database), utilizzare tipi
Literalo una convalida esplicita per limitare l’agente a un insieme predefinito di valori autorizzati. - Permessi granulari: Progettare strumenti per svolgere un’unica azione specifica. Invece di un
execute_sql(query)generico, creareget_product_info(product_id)oupdate_user_profile(user_id, new_data)con una convalida rigorosa. - Solamente lettura per impostazione predefinita: Per gli strumenti di database o di sistema di file, per impostazione predefinita limitare l’accesso in sola lettura e richiedere un’autorizzazione esplicita, approvata da un umano, per le operazioni di scrittura.
- Convalida degli ingressi: Convalidare sempre gli argomenti passati alle proprie funzioni di strumento, anche se hanno superato la convalida Pydantic. Il LLM potrebbe comunque costruire ingressi validi ma dannosi (ad esempio, una stringa di injection SQL che assomiglia a un ID prodotto valido).
Buone Pratiche per il Sandbox degli Agenti
- Principio del Minimo Privilegio: Concedere all’agente il minimo assoluto di permessi e risorse necessari per il suo compito.
- Sicurezza a Strati: Combinare più tecniche di sandboxing (livello linguaggio, livello OS, livello strumento) per una protezione solida. Nessun livello unico è infallibile.
- Ambientazioni Effimeri: Per l’esecuzione di codice, privilegiare l’esecuzione di agenti in contenitori o macchine virtuali usa e getta a breve termine che vengono distrutti dopo ogni compito.
- Convalida Rigida degli Ingressi: Convalidare e sanificare sempre ogni input proveniente dal LLM, soprattutto prima di utilizzarlo in chiamate API, query di database o esecuzioni di codice.
- Monitoraggio e Logging: Registrare tutte le azioni dell’agente, le chiamate agli strumenti e l’uso delle risorse. Questo è cruciale per rilevare comportamenti anomali e per l’analisi post-incidente.
- Timeout e Limiti di Risorse: Implementare scadenze rigorose per l’esecuzione di codice e le chiamate API, e impostare limiti CPU/memoria per prevenire attacchi Denial of Service.
- Isolamento Rete: Per impostazione predefinita, disabilitare l’accesso di rete per gli agenti. Attivarlo solo per endpoint e protocolli specifici e in whitelist se assolutamente necessario.
- Sistemi di File in Sola Lettura: Configurare gli ambienti degli agenti con sistemi di file in sola lettura ogni volta che è possibile per impedire la modifica o l’esfiltrazione non autorizzata di dati.
- Utenti Non Root: Eseguire sempre i processi degli agenti come utenti non root con privilegi limitati all’interno del sandbox.
- Audit e Aggiornamenti Regolari: Rivedere continuamente le configurazioni di sandboxing, aggiornare le immagini di base e rimanere informati sulle nuove vulnerabilità di sicurezza.
Conclusione
Il sandboxing degli agenti non è un lusso opzionale, ma una esigenza fondamentale per distribuire agenti LLM in sicurezza. Man mano che questi agenti diventano più capaci e autonomi, il potenziale per un uso improprio o danni accidentali aumenta notevolmente. Impiegando una combinazione di restrizioni a livello di linguaggio, solidi contenitori e interfacce di strumenti progettate con cura, gli sviluppatori possono creare applicazioni LLM potenti che siano sia innovative che sicure. Gli esempi forniti in questo tutorial mostrano passi pratici verso la costruzione di questi ambienti sicuri, consentendoti di integrare con fiducia agenti LLM nei tuoi sistemi minimizzando i rischi.
🕒 Published: