Skip to content

Commit c3ac6aa

Browse files
committed
Add HITL examples, Spanish translations, and README updates
New example files: - agent_tool_approval.py: Standalone agent with tool approval - workflow_hitl_requests.py: Simple always-ask HITL chat - workflow_hitl_requests_structured.py: Trip planner with structured outputs - workflow_hitl_tool_approval.py: Email agent workflow with tool approval - workflow_hitl_checkpoint.py: Content review with FileCheckpointStorage - workflow_hitl_checkpoint_pg.py: Custom PostgresCheckpointStorage backend - workflow_hitl_handoff.py: Interactive handoff (no autonomous mode) - workflow_hitl_handoff_approval.py: Handoff + tool approval combined - workflow_hitl_magentic.py: Magentic plan review Changes: - Renamed workflow_hitl_handoff.py -> workflow_hitl_handoff_approval.py - Created new simpler workflow_hitl_handoff.py (user input only) - Removed workflow_magenticone.py (not covered in presentation) - Added all 9 Spanish translations - Updated both README.md files with new examples - Updated plan.md with finalized slide content and speaker notes
1 parent 8b48544 commit c3ac6aa

16 files changed

Lines changed: 2156 additions & 897 deletions

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ You can run the examples in this repository by executing the scripts in the `exa
194194
| [agent_with_subagent.py](examples/agent_with_subagent.py) | Context isolation with sub-agents to keep prompts focused on relevant tools. |
195195
| [agent_without_subagent.py](examples/agent_without_subagent.py) | Context bloat example where one agent carries all tool schemas in a single prompt. |
196196
| [agent_summarization.py](examples/agent_summarization.py) | Context compaction via summarization middleware to reduce token usage in long conversations. |
197-
| [workflow_magenticone.py](examples/workflow_magenticone.py) | A MagenticOne multi-agent workflow. |
197+
| [agent_tool_approval.py](examples/agent_tool_approval.py) | Standalone agent with tool approval — gates sensitive operations before execution. |
198198
| [agent_middleware.py](examples/agent_middleware.py) | Agent, chat, and function middleware for logging, timing, and blocking. |
199199
| [agent_knowledge_aisearch.py](examples/agent_knowledge_aisearch.py) | Knowledge retrieval (RAG) using Azure AI Search with AgentFrameworkAzureAISearchRAG. |
200200
| [agent_knowledge_sqlite.py](examples/agent_knowledge_sqlite.py) | Knowledge retrieval (RAG) using a custom context provider with SQLite FTS5. |
@@ -223,6 +223,14 @@ You can run the examples in this repository by executing the scripts in the `exa
223223
| [workflow_converge.py](examples/workflow_converge.py) | A branch-and-converge workflow: Reviewer routes to Publisher or Editor, then converges before final summary output. |
224224
| [workflow_handoffbuilder.py](examples/workflow_handoffbuilder.py) | Autonomous handoff orchestration using `HandoffBuilder` (agents transfer control without human-in-the-loop). |
225225
| [workflow_handoffbuilder_rules.py](examples/workflow_handoffbuilder_rules.py) | Handoff orchestration with explicit routing rules using `HandoffBuilder.add_handoff()`. |
226+
| [workflow_hitl_requests.py](examples/workflow_hitl_requests.py) | Simple HITL chat — always pause for human input after every agent response (`ctx.request_info`, `@response_handler`). |
227+
| [workflow_hitl_requests_structured.py](examples/workflow_hitl_requests_structured.py) | Trip planner HITL with structured outputs — agent decides when to ask vs. finish via `PlannerOutput.status`. |
228+
| [workflow_hitl_tool_approval.py](examples/workflow_hitl_tool_approval.py) | Email agent workflow with `@tool(approval_mode="always_require")` for gating sensitive tool calls. |
229+
| [workflow_hitl_checkpoint.py](examples/workflow_hitl_checkpoint.py) | Content review with `FileCheckpointStorage` — pause, exit process, and resume from checkpoint. |
230+
| [workflow_hitl_checkpoint_pg.py](examples/workflow_hitl_checkpoint_pg.py) | Same content review workflow with a custom `PostgresCheckpointStorage` backend. |
231+
| [workflow_hitl_handoff.py](examples/workflow_hitl_handoff.py) | Interactive handoff (no autonomous mode) — framework pauses for user input via `HandoffAgentUserRequest`. |
232+
| [workflow_hitl_handoff_approval.py](examples/workflow_hitl_handoff_approval.py) | Handoff with user input + tool approval combined in the same event loop. |
233+
| [workflow_hitl_magentic.py](examples/workflow_hitl_magentic.py) | Magentic orchestration with plan review — human can approve or revise the plan before execution. |
226234
| [agent_otel_aspire.py](examples/agent_otel_aspire.py) | An agent with OpenTelemetry tracing, metrics, and structured logs exported to the [Aspire Dashboard](https://aspire.dev/dashboard/standalone/). |
227235
| [agent_otel_appinsights.py](examples/agent_otel_appinsights.py) | An agent with OpenTelemetry tracing, metrics, and structured logs exported to [Azure Application Insights](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview). Requires Azure provisioning via `azd provision`. |
228236
| [agent_evaluation_generate.py](examples/agent_evaluation_generate.py) | Generate synthetic evaluation data for the travel planner agent. |

examples/spanish/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ Puedes ejecutar los ejemplos en este repositorio ejecutando los scripts en el di
189189
| [agent_with_subagent.py](agent_with_subagent.py) | Aislamiento de contexto con subagentes para mantener los prompts enfocados en herramientas relevantes. |
190190
| [agent_without_subagent.py](agent_without_subagent.py) | Ejemplo de inflado de contexto cuando un solo agente carga todos los esquemas de herramientas en un mismo prompt. |
191191
| [agent_summarization.py](agent_summarization.py) | Compactación de contexto mediante middleware de resumen para reducir el uso de tokens en conversaciones largas. |
192-
| [workflow_magenticone.py](workflow_magenticone.py) | Un workflow multi-agente MagenticOne. |
192+
| [agent_tool_approval.py](agent_tool_approval.py) | Agente independiente con aprobación de herramientas — controla operaciones sensibles antes de ejecutarlas. |
193193
| [agent_middleware.py](agent_middleware.py) | Middleware de agente, chat y funciones para logging, timing y bloqueo. |
194194
| [agent_knowledge_aisearch.py](agent_knowledge_aisearch.py) | Recuperación de conocimiento (RAG) usando Azure AI Search con AgentFrameworkAzureAISearchRAG. |
195195
| [agent_knowledge_sqlite.py](agent_knowledge_sqlite.py) | Recuperación de conocimiento (RAG) usando un proveedor de contexto personalizado con SQLite FTS5. |
@@ -218,6 +218,14 @@ Puedes ejecutar los ejemplos en este repositorio ejecutando los scripts en el di
218218
| [workflow_converge.py](workflow_converge.py) | Un workflow con rama y convergencia: Revisor enruta a Publicador o Editor y luego converge antes del resumen final. |
219219
| [workflow_handoffbuilder.py](workflow_handoffbuilder.py) | Orquestación de handoff autónoma usando `HandoffBuilder` (los agentes se transfieren el control sin HITL). |
220220
| [workflow_handoffbuilder_rules.py](workflow_handoffbuilder_rules.py) | Orquestación de handoff con reglas explícitas usando `HandoffBuilder.add_handoff()`. |
221+
| [workflow_hitl_requests.py](workflow_hitl_requests.py) | Chat HITL simple — siempre pausa para entrada humana después de cada respuesta del agente (`ctx.request_info`, `@response_handler`). |
222+
| [workflow_hitl_requests_structured.py](workflow_hitl_requests_structured.py) | Planificador de viajes HITL con salidas estructuradas — el agente decide cuándo preguntar vs. finalizar vía `PlannerOutput.status`. |
223+
| [workflow_hitl_tool_approval.py](workflow_hitl_tool_approval.py) | Workflow de agente de correo con `@tool(approval_mode="always_require")` para controlar llamadas sensibles. |
224+
| [workflow_hitl_checkpoint.py](workflow_hitl_checkpoint.py) | Revisión de contenido con `FileCheckpointStorage` — pausar, salir del proceso y reanudar desde checkpoint. |
225+
| [workflow_hitl_checkpoint_pg.py](workflow_hitl_checkpoint_pg.py) | Mismo workflow de revisión con un backend personalizado `PostgresCheckpointStorage`. |
226+
| [workflow_hitl_handoff.py](workflow_hitl_handoff.py) | Handoff interactivo (sin modo autónomo) — el framework pausa para entrada del usuario vía `HandoffAgentUserRequest`. |
227+
| [workflow_hitl_handoff_approval.py](workflow_hitl_handoff_approval.py) | Handoff con entrada de usuario + aprobación de herramientas combinados en el mismo bucle de eventos. |
228+
| [workflow_hitl_magentic.py](workflow_hitl_magentic.py) | Orquestación Magentic con revisión de plan — el humano puede aprobar o revisar el plan antes de la ejecución. |
221229
| [agent_otel_aspire.py](agent_otel_aspire.py) | Un agente con trazas, métricas y logs estructurados de OpenTelemetry exportados al [Aspire Dashboard](https://aspire.dev/dashboard/standalone/). |
222230
| [agent_otel_appinsights.py](agent_otel_appinsights.py) | Un agente con trazas, métricas y logs estructurados de OpenTelemetry exportados a [Azure Application Insights](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview). Requiere aprovisionamiento de Azure con `azd provision`. |
223231
| [agent_evaluation_generate.py](agent_evaluation_generate.py) | Genera datos sintéticos de evaluación para el agente planificador de viajes. |
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Agente independiente con aprobación de herramientas — no se requiere workflow.
2+
3+
Demuestra: @tool(approval_mode="always_require") con un Agent simple,
4+
manejo de user_input_requests, y re-ejecución del agente con contexto de aprobación.
5+
6+
Un agente de reportes de gastos puede buscar recibos automáticamente pero debe obtener
7+
aprobación humana antes de enviar un reporte de gastos. Esto muestra el patrón HITL
8+
más simple: aprobación de herramientas en un agente independiente sin ningún workflow.
9+
10+
Ejecutar:
11+
uv run examples/spanish/agent_tool_approval.py
12+
"""
13+
14+
import asyncio
15+
import os
16+
from typing import Annotated, Any
17+
18+
from agent_framework import Agent, Message, tool
19+
from agent_framework.openai import OpenAIChatClient
20+
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
21+
from dotenv import load_dotenv
22+
23+
load_dotenv(override=True)
24+
API_HOST = os.getenv("API_HOST", "github")
25+
26+
# Configura el cliente según el host de la API
27+
async_credential = None
28+
if API_HOST == "azure":
29+
async_credential = DefaultAzureCredential()
30+
token_provider = get_bearer_token_provider(async_credential, "https://cognitiveservices.azure.com/.default")
31+
client = OpenAIChatClient(
32+
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
33+
api_key=token_provider,
34+
model_id=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
35+
)
36+
elif API_HOST == "github":
37+
client = OpenAIChatClient(
38+
base_url="https://models.github.ai/inference",
39+
api_key=os.environ["GITHUB_TOKEN"],
40+
model_id=os.getenv("GITHUB_MODEL", "openai/gpt-4.1-mini"),
41+
)
42+
else:
43+
client = OpenAIChatClient(
44+
api_key=os.environ["OPENAI_API_KEY"], model_id=os.environ.get("OPENAI_MODEL", "gpt-4.1-mini")
45+
)
46+
47+
48+
# --- Herramientas ---
49+
50+
submitted_reports: list[dict[str, str]] = []
51+
52+
receipts_db: dict[str, dict[str, str]] = {
53+
"R-001": {
54+
"vendor": "Librería Nacional", "amount": "$142.50", "category": "Útiles de Oficina", "date": "2026-03-01",
55+
},
56+
"R-002": {"vendor": "LATAM Airlines", "amount": "$489.00", "category": "Viajes", "date": "2026-02-28"},
57+
"R-003": {"vendor": "Rappi", "amount": "$32.75", "category": "Comidas", "date": "2026-03-03"},
58+
}
59+
60+
61+
@tool(approval_mode="never_require")
62+
def lookup_receipt(
63+
receipt_id: Annotated[str, "The receipt ID to look up"],
64+
) -> dict[str, str]:
65+
"""Look up a receipt by ID and return its details."""
66+
return receipts_db.get(receipt_id, {"error": f"Recibo {receipt_id} no encontrado"})
67+
68+
69+
@tool(approval_mode="always_require")
70+
def submit_expense_report(
71+
description: Annotated[str, "Description of the expense report"],
72+
total_amount: Annotated[str, "Total amount to reimburse"],
73+
receipt_ids: Annotated[str, "Comma-separated receipt IDs included"],
74+
) -> str:
75+
"""Submit an expense report for reimbursement. Requires manager approval."""
76+
report = {"description": description, "total_amount": total_amount, "receipt_ids": receipt_ids}
77+
submitted_reports.append(report)
78+
return f"Reporte de gastos enviado: {description} por {total_amount} (recibos: {receipt_ids})"
79+
80+
81+
# --- Principal ---
82+
83+
84+
agent = Agent(
85+
client=client,
86+
name="ExpenseAgent",
87+
instructions=(
88+
"Eres un asistente de reportes de gastos. Ayuda a los usuarios a buscar recibos y enviar reportes de gastos. "
89+
"Siempre busca los detalles del recibo antes de incluirlos en un reporte de gastos."
90+
),
91+
tools=[lookup_receipt, submit_expense_report],
92+
)
93+
94+
95+
async def main() -> None:
96+
query = "Busca los recibos R-001 y R-002, luego envía un reporte de gastos por ambos."
97+
print(f"👤 Usuario: {query}\n")
98+
99+
result = await agent.run(query)
100+
101+
# Bucle mientras haya solicitudes de aprobación pendientes
102+
while len(result.user_input_requests) > 0:
103+
new_inputs: list[Any] = [query]
104+
105+
for request in result.user_input_requests:
106+
func_call = request.function_call
107+
print(f"🔒 Aprobación solicitada: {func_call.name}")
108+
print(f" Argumentos: {func_call.arguments}")
109+
110+
# Agrega el mensaje del asistente que contiene la solicitud de aprobación
111+
new_inputs.append(Message("assistant", [request]))
112+
113+
approval = input(" ¿Aprobar/Approve? (s/n): ").strip().lower()
114+
approved = approval == "s"
115+
print(f" {'✅ Aprobado' if approved else '❌ Rechazado'}\n")
116+
117+
# Agrega la respuesta de aprobación del usuario
118+
new_inputs.append(Message("user", [request.to_function_approval_response(approved)]))
119+
120+
# Re-ejecuta con contexto de aprobación
121+
result = await agent.run(new_inputs)
122+
123+
print(f"🤖 {agent.name}: {result.text}")
124+
125+
if submitted_reports:
126+
print(f"\n📋 {len(submitted_reports)} reporte(s) enviado(s):")
127+
for report in submitted_reports:
128+
print(f" - {report['description']} | {report['total_amount']} | recibos: {report['receipt_ids']}")
129+
130+
if async_credential:
131+
await async_credential.close()
132+
133+
134+
if __name__ == "__main__":
135+
asyncio.run(main())

0 commit comments

Comments
 (0)