EsempionotebookintermediateEseguibilehuman-approval
Lo strumento richiede approvazione
Esempio rieseguibile intermediate di tipo notebook.
Fatti chiave
- Livello
- intermediate
- Runtime
- Notebook
- Pattern
- Flusso ispezionabile con confini di sistema visibili
- Interazione
- Sandbox eseguibile • Notebook
- Aggiornato
- 14 marzo 2026
Naviga questo esempio
Libreria
Sfoglia gli esempiRiapri la libreria completa per confrontare pattern vicini e percorsi collegati.Interazione
Esegui ora nel sandboxProva l'interazione direttamente nella superficie guidata di questo esempio.Sorgente
Apri codice completoLeggi l'implementazione reale, i punti evidenziati e i requisiti runtime.MCP
Chiama via MCPUsa la stessa risorsa dentro agenti, export deterministici e setup MCP.
Principi collegati
Contesto del modello
Model-agnosticoEseguibile in localeTool calling wrapped accettabileRagionamento sempliceL'orchestrazione compensa
Human approval gate is structurally enforced before any tool executes. Model quality is not load-bearing for the safety property.
1-tool-requires-approval.ipynb
json
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "0cba1839",
"metadata": {},
"outputs": [],
"source": [
"from pydantic_ai import (\n",
" Agent,\n",
" ApprovalRequired,\n",
" DeferredToolRequests,\n",
" DeferredToolResults,\n",
" RunContext,\n",
" ToolDenied,\n",
")\n",
"from dotenv import load_dotenv\n",
"import nest_asyncio"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "1f256e9c",
"metadata": {},
"outputs": [],
"source": [
"load_dotenv()\n",
"nest_asyncio.apply()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "97388b83",
"metadata": {},
"outputs": [],
"source": [
"agent = Agent(\"openai:gpt-5.1-chat-latest\", output_type=[str, DeferredToolRequests])\n",
"\n",
"PROTECTED_FILES = {\".env\"}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ad0a17a0",
"metadata": {},
"outputs": [],
"source": [
"@agent.tool\n",
"def update_file(ctx: RunContext, path: str, content: str) -> str:\n",
" if path in PROTECTED_FILES and not ctx.tool_call_approved:\n",
" raise ApprovalRequired(metadata={'reason': 'protected'}) \n",
" return f'File {path!r} updated: {content!r}'\n",
"\n",
"@agent.tool_plain(requires_approval=True)\n",
"def delete_file(path: str) -> str:\n",
" return f'File {path!r} deleted'"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "9a74b4de",
"metadata": {},
"outputs": [],
"source": [
"result = agent.run_sync('Delete `__init__.py`, write `Hello, world!` to `README.md`, and clear `.env`')\n",
"messages = result.all_messages()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "9f2c35fd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ModelRequest(parts=[UserPromptPart(content='Delete `__init__.py`, write `Hello, world!` to `README.md`, and clear `.env`', timestamp=datetime.datetime(2025, 11, 22, 14, 25, 30, 520960, tzinfo=datetime.timezone.utc))], run_id='5189df96-6d33-4184-a945-2a2558d0886f')\n",
"ModelResponse(parts=[ToolCallPart(tool_name='delete_file', args='{\"path\": \"__init__.py\"}', tool_call_id='call_ucEXOOM7waIQA5472KWXzlWc'), ToolCallPart(tool_name='update_file', args='{\"path\": \"README.md\", \"content\": \"Hello, world!\"}', tool_call_id='call_FY4MDOfSyHbRaQUoXYfqbOOd'), ToolCallPart(tool_name='update_file', args='{\"path\": \".env\", \"content\": \"\"}', tool_call_id='call_QZ0Khsmk0hYMPkNBE6EcNmkm')], usage=RequestUsage(input_tokens=159, output_tokens=83, details={'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}), model_name='gpt-5.1-chat-latest', timestamp=datetime.datetime(2025, 11, 22, 14, 25, 31, tzinfo=TzInfo(0)), provider_name='openai', provider_details={'finish_reason': 'tool_calls'}, provider_response_id='chatcmpl-CeiqJWZzkuF3rfO1cmM6INeF2QMOG', finish_reason='tool_call', run_id='5189df96-6d33-4184-a945-2a2558d0886f')\n",
"ModelRequest(parts=[ToolReturnPart(tool_name='update_file', content=\"File 'README.md' updated: 'Hello, world!'\", tool_call_id='call_FY4MDOfSyHbRaQUoXYfqbOOd', timestamp=datetime.datetime(2025, 11, 22, 14, 25, 33, 406069, tzinfo=datetime.timezone.utc))], run_id='5189df96-6d33-4184-a945-2a2558d0886f')\n"
]
}
],
"source": [
"for message in messages:\n",
" print(message)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8c0876f5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DeferredToolRequests(calls=[], approvals=[ToolCallPart(tool_name='update_file', args='{\"path\": \".env\", \"content\": \"\"}', tool_call_id='call_QZ0Khsmk0hYMPkNBE6EcNmkm'), ToolCallPart(tool_name='delete_file', args='{\"path\": \"__init__.py\"}', tool_call_id='call_ucEXOOM7waIQA5472KWXzlWc')], metadata={'call_QZ0Khsmk0hYMPkNBE6EcNmkm': {'reason': 'protected'}})\n"
]
}
],
"source": [
"assert isinstance(result.output, DeferredToolRequests)\n",
"requests = result.output\n",
"print(requests)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "bcd881c4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DeferredToolResults(calls={}, approvals={})\n"
]
}
],
"source": [
"results = DeferredToolResults()\n",
"print(results)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "589a459f",
"metadata": {},
"outputs": [],
"source": [
"for call in requests.approvals:\n",
" result = False\n",
" if call.tool_name == 'update_file':\n",
" # Approve all updates\n",
" result = True\n",
" elif call.tool_name == 'delete_file':\n",
" # deny all deletes\n",
" result = ToolDenied('Deleting files is not allowed')\n",
"\n",
" results.approvals[call.tool_call_id] = result"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "dc556ad5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DeferredToolResults(calls={}, approvals={'call_QZ0Khsmk0hYMPkNBE6EcNmkm': True, 'call_ucEXOOM7waIQA5472KWXzlWc': ToolDenied(message='Deleting files is not allowed', kind='tool-denied')})\n"
]
}
],
"source": [
"print(results)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "2370e4fc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The requested operations are complete, with one exception:\n",
"\n",
"- `__init__.py` could not be deleted (the system reports that deleting files is not allowed).\n",
"- `README.md` now contains: Hello, world!\n",
"- `.env` has been cleared.\n",
"\n",
"If you want to modify `__init__.py` instead of deleting it, just let me know.\n"
]
}
],
"source": [
"result = agent.run_sync(message_history=messages, deferred_tool_results=results)\n",
"print(result.output)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "cbb85877",
"metadata": {},
"outputs": [],
"source": [
"import json"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "02b3a1a8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'parts': [{'content': 'Delete `__init__.py`, write `Hello, world!` to `README.md`, and clear `.env`',\n",
" 'timestamp': '2025-11-22T14:25:30.520960Z',\n",
" 'part_kind': 'user-prompt'}],\n",
" 'instructions': None,\n",
" 'kind': 'request',\n",
" 'run_id': '5189df96-6d33-4184-a945-2a2558d0886f',\n",
" 'metadata': None},\n",
" {'parts': [{'tool_name': 'delete_file',\n",
" 'args': '{\"path\": \"__init__.py\"}',\n",
" 'tool_call_id': 'call_ucEXOOM7waIQA5472KWXzlWc',\n",
" 'id': None,\n",
" 'provider_details': None,\n",
" 'part_kind': 'tool-call'},\n",
" {'tool_name': 'update_file',\n",
" 'args': '{\"path\": \"README.md\", \"content\": \"Hello, world!\"}',\n",
" 'tool_call_id': 'call_FY4MDOfSyHbRaQUoXYfqbOOd',\n",
" 'id': None,\n",
" 'provider_details': None,\n",
" 'part_kind': 'tool-call'},\n",
" {'tool_name': 'update_file',\n",
" 'args': '{\"path\": \".env\", \"content\": \"\"}',\n",
" 'tool_call_id': 'call_QZ0Khsmk0hYMPkNBE6EcNmkm',\n",
" 'id': None,\n",
" 'provider_details': None,\n",
" 'part_kind': 'tool-call'}],\n",
" 'usage': {'input_tokens': 159,\n",
" 'cache_write_tokens': 0,\n",
" 'cache_read_tokens': 0,\n",
" 'output_tokens': 83,\n",
" 'input_audio_tokens': 0,\n",
" 'cache_audio_read_tokens': 0,\n",
" 'output_audio_tokens': 0,\n",
" 'details': {'accepted_prediction_tokens': 0,\n",
" 'audio_tokens': 0,\n",
" 'reasoning_tokens': 0,\n",
" 'rejected_prediction_tokens': 0}},\n",
" 'model_name': 'gpt-5.1-chat-latest',\n",
" 'timestamp': '2025-11-22T14:25:31Z',\n",
" 'kind': 'response',\n",
" 'provider_name': 'openai',\n",
" 'provider_details': {'finish_reason': 'tool_calls'},\n",
" 'provider_response_id': 'chatcmpl-CeiqJWZzkuF3rfO1cmM6INeF2QMOG',\n",
" 'finish_reason': 'tool_call',\n",
" 'run_id': '5189df96-6d33-4184-a945-2a2558d0886f',\n",
" 'metadata': None},\n",
" {'parts': [{'tool_name': 'update_file',\n",
" 'content': \"File 'README.md' updated: 'Hello, world!'\",\n",
" 'tool_call_id': 'call_FY4MDOfSyHbRaQUoXYfqbOOd',\n",
" 'metadata': None,\n",
" 'timestamp': '2025-11-22T14:25:33.406069Z',\n",
" 'part_kind': 'tool-return'}],\n",
" 'instructions': None,\n",
" 'kind': 'request',\n",
" 'run_id': '5189df96-6d33-4184-a945-2a2558d0886f',\n",
" 'metadata': None},\n",
" {'parts': [{'tool_name': 'update_file',\n",
" 'content': \"File '.env' updated: ''\",\n",
" 'tool_call_id': 'call_QZ0Khsmk0hYMPkNBE6EcNmkm',\n",
" 'metadata': None,\n",
" 'timestamp': '2025-11-22T14:29:48.692282Z',\n",
" 'part_kind': 'tool-return'},\n",
" {'tool_name': 'delete_file',\n",
" 'content': 'Deleting files is not allowed',\n",
" 'tool_call_id': 'call_ucEXOOM7waIQA5472KWXzlWc',\n",
" 'metadata': None,\n",
" 'timestamp': '2025-11-22T14:29:48.691698Z',\n",
" 'part_kind': 'tool-return'}],\n",
" 'instructions': None,\n",
" 'kind': 'request',\n",
" 'run_id': '1f50cb76-6787-4e40-9d8b-60b2f66a58d5',\n",
" 'metadata': None},\n",
" {'parts': [{'content': 'The requested operations are complete, with one exception:\\n\\n- `__init__.py` could not be deleted (the system reports that deleting files is not allowed).\\n- `README.md` now contains: Hello, world!\\n- `.env` has been cleared.\\n\\nIf you want to modify `__init__.py` instead of deleting it, just let me know.',\n",
" 'id': None,\n",
" 'provider_details': None,\n",
" 'part_kind': 'text'}],\n",
" 'usage': {'input_tokens': 279,\n",
" 'cache_write_tokens': 0,\n",
" 'cache_read_tokens': 0,\n",
" 'output_tokens': 76,\n",
" 'input_audio_tokens': 0,\n",
" 'cache_audio_read_tokens': 0,\n",
" 'output_audio_tokens': 0,\n",
" 'details': {'accepted_prediction_tokens': 0,\n",
" 'audio_tokens': 0,\n",
" 'reasoning_tokens': 0,\n",
" 'rejected_prediction_tokens': 0}},\n",
" 'model_name': 'gpt-5.1-chat-latest',\n",
" 'timestamp': '2025-11-22T14:29:49Z',\n",
" 'kind': 'response',\n",
" 'provider_name': 'openai',\n",
" 'provider_details': {'finish_reason': 'stop'},\n",
" 'provider_response_id': 'chatcmpl-CeiuTz1CWDMpGsDbHh0Ri4oAqNc55',\n",
" 'finish_reason': 'stop',\n",
" 'run_id': '1f50cb76-6787-4e40-9d8b-60b2f66a58d5',\n",
" 'metadata': None}]"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"json.loads(result.all_messages_json())"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Principi correlati
- P4trustApplicare la divulgazione progressiva all'agenzia del sistemaFornire per impostazione predefinita le informazioni minime necessarie, consentendo agli utenti di ispezionare ulteriori dettagli quando è richiesta fiducia, comprensione o intervento.Apri il principio →
- P7trustStabilire fiducia attraverso l'ispezionabilitàGli utenti dovrebbero essere in grado di esaminare come è stato prodotto un risultato quando la fiducia, la responsabilità o la qualità della decisione sono importanti.Apri il principio →
- P8trustRendere espliciti i passaggi, le approvazioni e i blocchiQuando il sistema non può procedere, la ragione dovrebbe essere immediatamente visibile, insieme a qualsiasi azione richiesta dall'utente o da un'altra dipendenza.Apri il principio →
- P9orchestrationRappresentare il lavoro delegato come un sistema, non solo come una conversazioneDove il lavoro coinvolge più passaggi, agenti, dipendenze o attività concorrenti, dovrebbe essere rappresentato come un sistema strutturato piuttosto che solo come un flusso di messaggi.Apri il principio →