initial commit: slides ready + upgraded

This commit is contained in:
2026-05-24 10:37:53 +02:00
commit f6f46972c3
173 changed files with 16756 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Módulo 5: Agentes
Tools, Structured Output, Runtime y Human-in-the-Loop
<div class="pt-12">
<span class="text-xl opacity-80">
LLMs que pueden actuar en el mundo real
</span>
</div>
+41
View File
@@ -0,0 +1,41 @@
---
layout: default
---
# ¿Qué es un Agente?
<div class="mt-10 text-sm">
<v-click>
<div class="mb-8">
Un **agente** es un sistema que usa un LLM para decidir qué acciones tomar
</div>
</v-click>
<div class="grid grid-cols-2 gap-8">
<v-click>
<div class="border-l-4 border-red-500 pl-4 bg-red-50 dark:bg-red-900/20 p-4">
<h3 class="text-base font-bold mb-3">❌ Sin Agentes</h3>
<p class="text-xs mb-2">Flujo fijo y predecible</p>
<p class="text-xs mb-2">Usuario → LLM → Respuesta</p>
<p class="text-xs opacity-70">El LLM solo genera texto</p>
</div>
</v-click>
<v-click>
<div class="border-l-4 border-green-500 pl-4 bg-green-50 dark:bg-green-900/20 p-4">
<h3 class="text-base font-bold mb-3">✅ Con Agentes</h3>
<p class="text-xs mb-2">Flujo dinámico y adaptativo</p>
<p class="text-xs mb-2">Usuario → LLM → Tool → LLM → Respuesta</p>
<p class="text-xs opacity-70">El LLM decide qué herramientas usar</p>
</div>
</v-click>
</div>
</div>
+37
View File
@@ -0,0 +1,37 @@
---
layout: default
---
# Casos de Uso de Agentes
<div class="mt-6 grid grid-cols-2 gap-4 text-xs">
<v-click>
<div class="border-l-4 border-blue-500 pl-3 bg-blue-50 dark:bg-blue-900/20 p-2">
<h3 class="text-sm font-bold mb-1">🔍 Búsqueda y análisis</h3>
<p>Buscar información en APIs, bases de datos o web</p>
</div>
</v-click>
<v-click>
<div class="border-l-4 border-green-500 pl-3 bg-green-50 dark:bg-green-900/20 p-2">
<h3 class="text-sm font-bold mb-1">🛠️ Automatización</h3>
<p>Enviar emails, crear tickets, actualizar sistemas</p>
</div>
</v-click>
<v-click>
<div class="border-l-4 border-purple-500 pl-3 bg-purple-50 dark:bg-purple-900/20 p-2">
<h3 class="text-sm font-bold mb-1">💼 Asistentes empresariales</h3>
<p>Consultar CRM, generar reportes, analizar datos</p>
</div>
</v-click>
<v-click>
<div class="border-l-4 border-orange-500 pl-3 bg-orange-50 dark:bg-orange-900/20 p-2">
<h3 class="text-sm font-bold mb-1">🤖 Agentes autónomos</h3>
<p>Tomar decisiones complejas con múltiples pasos</p>
</div>
</v-click>
</div>
+45
View File
@@ -0,0 +1,45 @@
---
layout: default
---
# createAgent: Creando el Agente
<div class="mt-6 text-xs">
<v-click>
<div class="mb-4">
**createAgent** es la forma estándar de crear agentes en LangChain v1
</div>
</v-click>
<v-click>
```typescript
import { createAgent } from "langchain";
const agent = createAgent({
model: "mistral:mistral-large-latest", // Modelo como string
tools: [weatherTool], // Array de tools
systemPrompt: "Eres un asistente útil.",
});
const result = await agent.invoke({
messages: [{ role: "user", content: "¿Qué tiempo hace en Madrid?" }]
});
```
</v-click>
<v-click>
<div class="mt-4 text-xs opacity-70">
El agente automatiza el ciclo: llamar al LLM -> ejecutar tools -> procesar resultados -> repetir
</div>
</v-click>
</div>
+42
View File
@@ -0,0 +1,42 @@
---
layout: default
---
# Configuración del Modelo
<div class="mt-4 text-xs">
<v-click>
**Opción 1: String con formato `provider:model`**
```typescript
const agent = createAgent({
model: "mistral:mistral-large-latest", // Mistral
// model: "openai:gpt-4.1", // OpenAI
// model: "anthropic:claude-sonnet-4-6", // Anthropic
tools: [],
});
```
</v-click>
<v-click>
**Opción 2: Instancia del modelo con configuración avanzada**
```typescript
import { ChatMistralAI } from "@langchain/mistralai";
const model = new ChatMistralAI({
model: "mistral-large-latest",
temperature: 0.1,
maxTokens: 1000,
});
const agent = createAgent({ model, tools: [] });
```
</v-click>
</div>
+43
View File
@@ -0,0 +1,43 @@
---
layout: default
---
# systemPrompt: Configurando el Comportamiento
<div class="mt-4 text-xs">
<v-click>
**String simple:**
```typescript
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool],
systemPrompt: "Eres un asistente meteorológico. Sé conciso y preciso.",
});
```
</v-click>
<v-click>
**SystemMessage (para features avanzados como cache de Anthropic):**
```typescript
import { SystemMessage } from "langchain";
const agent = createAgent({
model: "anthropic:claude-sonnet-4-6",
tools: [weatherTool],
systemPrompt: new SystemMessage({
content: [
{ type: "text", text: "Eres un experto en clima...", cache_control: { type: "ephemeral" } }
],
}),
});
```
</v-click>
</div>
+44
View File
@@ -0,0 +1,44 @@
---
layout: default
---
# Tools: Definiendo Herramientas
<div class="mt-6 text-xs">
<v-click>
```typescript
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const weatherTool = tool(
async ({ location }) => {
// Llamar a API del tiempo
return `El tiempo en ${location} es soleado, 22°C`;
},
{
name: "get_weather",
description: "Obtiene el tiempo actual de una ubicación",
schema: z.object({
location: z.string().describe("Ciudad o ubicación"),
}),
}
);
```
</v-click>
<v-click>
```typescript
// El agente decide automáticamente cuándo usar cada tool
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool, calculatorTool, searchTool],
});
```
</v-click>
</div>
+42
View File
@@ -0,0 +1,42 @@
---
layout: default
---
# Streaming: Modos de Transmisión
<div class="mt-4 text-xs">
<v-click>
**`updates`**: Emite evento después de cada paso del agente
```typescript
for await (const chunk of await agent.stream(
{ messages: [{ role: "user", content: "¿Tiempo en Madrid?" }] },
{ streamMode: "updates" }
)) {
console.log(chunk);
// { model: { messages: [AIMessage con tool_call] } }
// { tools: { messages: [ToolMessage con resultado] } }
// { model: { messages: [AIMessage respuesta final] } }
}
```
</v-click>
<v-click>
**`values`**: Emite el estado completo después de cada paso
```typescript
for await (const chunk of await agent.stream(
{ messages: [{ role: "user", content: "¿Tiempo en Madrid?" }] },
{ streamMode: "values" }
)) {
console.log(chunk.messages); // Array completo de mensajes hasta ese punto
}
```
</v-click>
</div>
+40
View File
@@ -0,0 +1,40 @@
---
layout: default
---
# Streaming: Tokens y Mensajes
<div class="mt-4 text-xs">
<v-click>
**`messages`**: Stream de tokens del LLM + metadata
```typescript
for await (const [mode, chunk] of await agent.stream(
{ messages: [{ role: "user", content: "Explica qué es TypeScript" }] },
{ streamMode: ["updates", "messages"] }
)) {
if (mode === "messages") {
const [token, metadata] = chunk;
if (token.content) {
process.stdout.write(token.content); // Token a token
}
} else if (mode === "updates") {
// Paso completo del agente
console.log("\n[Paso completado]", Object.keys(chunk));
}
}
```
</v-click>
<v-click>
<div class="mt-4 text-xs opacity-80">
Combina múltiples modos: `streamMode: ["updates", "messages"]`
</div>
</v-click>
</div>
+39
View File
@@ -0,0 +1,39 @@
---
layout: default
---
# Checkpointer: MemorySaver
<div class="mt-4 text-xs">
<v-click>
**MemorySaver**: Persistencia en memoria (desarrollo/testing)
```typescript
import { createAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool],
checkpointer: new MemorySaver(), // En memoria RAM
});
const config = { configurable: { thread_id: "conv-1" } };
// Primera conversación
await agent.invoke(
{ messages: [{ role: "user", content: "Hola, soy María" }] }, config
);
// Segunda conversación - recuerda el contexto
await agent.invoke(
{ messages: [{ role: "user", content: "¿Cómo me llamo?" }] }, config
);
// "Tu nombre es María"
```
</v-click>
</div>
+47
View File
@@ -0,0 +1,47 @@
---
layout: default
---
# Checkpointer: SqliteSaver
<div class="mt-4 text-xs">
<v-click>
**SqliteSaver**: Persistencia en SQLite (local)
```bash
npm install @langchain/langgraph-checkpoint-sqlite
```
</v-click>
<v-click>
```typescript
import { createAgent } from "langchain";
import { SqliteSaver } from "@langchain/langgraph-checkpoint-sqlite";
const checkpointer = SqliteSaver.fromConnString("./agent_memory.db");
await checkpointer.setup(); // Crear tablas
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool],
checkpointer,
});
// El estado persiste aunque reinicies la aplicación
```
</v-click>
<v-click>
<div class="mt-4 text-xs opacity-80">
Ideal para desarrollo local y aplicaciones de usuario único
</div>
</v-click>
</div>
+47
View File
@@ -0,0 +1,47 @@
---
layout: default
---
# Checkpointer: PostgresSaver
<div class="mt-4 text-xs">
<v-click>
**PostgresSaver**: Persistencia en PostgreSQL (producción)
```bash
npm install @langchain/langgraph-checkpoint-postgres
```
</v-click>
<v-click>
```typescript
import { createAgent } from "langchain";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const DB_URI = "postgresql://user:pass@localhost:5432/mydb";
const checkpointer = PostgresSaver.fromConnString(DB_URI);
await checkpointer.setup();
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool],
checkpointer,
});
```
</v-click>
<v-click>
<div class="mt-4 text-xs opacity-80">
También disponibles: **MongoDB** (`@langchain/langgraph-checkpoint-mongodb`),
**Redis** (`@langchain/langgraph-checkpoint-redis`), y **Custom** (implementar `BaseCheckpointSaver`)
</div>
</v-click>
</div>
+51
View File
@@ -0,0 +1,51 @@
---
layout: default
---
# Middleware: Arquitectura
<div class="mt-6 text-xs">
<v-click>
<div class="mb-4">
**Middleware**: Hooks que interceptan el flujo del agente en puntos clave
</div>
</v-click>
<v-click>
<div class="space-y-3 text-xs">
<div class="border-l-4 border-blue-500 pl-4 bg-blue-50 dark:bg-blue-900/20 p-2">
<code class="font-bold">beforeAgent</code> - Antes de iniciar el agente
</div>
<div class="border-l-4 border-green-500 pl-4 bg-green-50 dark:bg-green-900/20 p-2">
<code class="font-bold">beforeModel</code> - Antes de cada llamada al LLM
</div>
<div class="border-l-4 border-purple-500 pl-4 bg-purple-50 dark:bg-purple-900/20 p-2">
<code class="font-bold">wrapModelCall</code> - Envuelve la llamada al LLM (interceptar/modificar)
</div>
<div class="border-l-4 border-orange-500 pl-4 bg-orange-50 dark:bg-orange-900/20 p-2">
<code class="font-bold">wrapToolCall</code> - Envuelve la ejecución de tools
</div>
<div class="border-l-4 border-red-500 pl-4 bg-red-50 dark:bg-red-900/20 p-2">
<code class="font-bold">afterModel</code> - Después de cada respuesta del LLM
</div>
<div class="border-l-4 border-cyan-500 pl-4 bg-cyan-50 dark:bg-cyan-900/20 p-2">
<code class="font-bold">afterAgent</code> - Después de completar el agente
</div>
</div>
</v-click>
</div>
+36
View File
@@ -0,0 +1,36 @@
---
layout: default
---
# Middleware Custom
<div class="mt-4 text-xs">
<v-click>
```typescript
import { createAgent, createMiddleware } from "langchain";
const loggingMiddleware = createMiddleware({
name: "LoggingMiddleware",
beforeModel: (state) => {
console.log(`Llamando modelo con ${state.messages.length} mensajes`);
return; // No modificar estado
},
afterModel: (state) => {
const lastMsg = state.messages[state.messages.length - 1];
console.log(`Modelo respondió: ${lastMsg.content?.slice(0, 50)}...`);
return;
},
});
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [weatherTool],
middleware: [loggingMiddleware],
});
```
</v-click>
</div>
+46
View File
@@ -0,0 +1,46 @@
---
layout: default
---
# dynamicSystemPromptMiddleware
<div class="mt-4 text-xs">
<v-click>
```typescript
import * as z from "zod";
import { createAgent, dynamicSystemPromptMiddleware } from "langchain";
const contextSchema = z.object({
userRole: z.enum(["expert", "beginner"]),
});
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [searchTool],
contextSchema,
middleware: [
dynamicSystemPromptMiddleware<z.infer<typeof contextSchema>>((state, runtime) => {
const userRole = runtime.context.userRole;
let prompt = "Eres un asistente útil.";
if (userRole === "expert") {
prompt += " Proporciona respuestas técnicas detalladas.";
} else {
prompt += " Explica conceptos de forma simple, sin jerga técnica.";
}
return prompt;
}),
],
});
await agent.invoke(
{ messages: [{ role: "user", content: "Explica machine learning" }] },
{ context: { userRole: "beginner" } }
);
```
</v-click>
</div>
+48
View File
@@ -0,0 +1,48 @@
---
layout: default
---
# toolCallLimitMiddleware: Limitar Tools
<div class="mt-4 text-xs">
<v-click>
```typescript
import { createAgent, toolCallLimitMiddleware } from "langchain";
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [searchTool, databaseTool, scraperTool],
middleware: [
// Límite global: máx 20 por thread, 10 por run
toolCallLimitMiddleware({ threadLimit: 20, runLimit: 10 }),
// Límite específico para búsquedas
toolCallLimitMiddleware({
toolName: "search",
threadLimit: 5,
runLimit: 3,
}),
// Límite estricto para scraping (lanza error si excede)
toolCallLimitMiddleware({
toolName: "scrape_webpage",
runLimit: 2,
exitBehavior: "error", // "continue" | "error" | "end"
}),
],
});
```
</v-click>
<v-click>
<div class="mt-3 text-xs opacity-80">
Requiere checkpointer para `threadLimit`. `runLimit` se resetea en cada invocación.
</div>
</v-click>
</div>
+44
View File
@@ -0,0 +1,44 @@
---
layout: default
---
# summarizationMiddleware: Resumir Historial
<div class="mt-4 text-xs">
<v-click>
```typescript
import { createAgent, summarizationMiddleware } from "langchain";
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [searchTool, calculatorTool],
middleware: [
summarizationMiddleware({
model: "mistral:mistral-small-latest", // Modelo para resumir (más barato)
trigger: { tokens: 4000 }, // Resumir cuando supere 4000 tokens
keep: { messages: 20 }, // Mantener los últimos 20 mensajes
}),
],
});
```
</v-click>
<v-click>
<div class="mt-4 text-xs opacity-80">
**Funcionamiento automático:**
1. Detecta cuando el historial supera el límite de tokens
2. Resume los mensajes antiguos usando el modelo especificado
3. Reemplaza permanentemente los mensajes antiguos por el resumen
4. Mantiene los mensajes recientes intactos para contexto
</div>
</v-click>
</div>
+46
View File
@@ -0,0 +1,46 @@
---
layout: default
---
# Middleware: Limitar Historial (Trim)
<div class="mt-4 text-xs">
<v-click>
```typescript
import { RemoveMessage } from "@langchain/core/messages";
import { createAgent, createMiddleware } from "langchain";
import { MemorySaver, REMOVE_ALL_MESSAGES } from "@langchain/langgraph";
const trimMessages = createMiddleware({
name: "TrimMessages",
beforeModel: (state) => {
const messages = state.messages;
if (messages.length <= 5) return; // No recortar si hay pocos
// Mantener primer mensaje + últimos 4
const firstMsg = messages[0];
const recentMsgs = messages.slice(-4);
const newMessages = [firstMsg, ...recentMsgs];
return {
messages: [
new RemoveMessage({ id: REMOVE_ALL_MESSAGES }),
...newMessages,
],
};
},
});
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [],
middleware: [trimMessages],
checkpointer: new MemorySaver(),
});
```
</v-click>
</div>
+40
View File
@@ -0,0 +1,40 @@
---
layout: default
---
# Middleware: wrapModelCall
<div class="mt-4 text-xs">
<v-click>
Permite interceptar y modificar la llamada al modelo:
```typescript
import { ChatMistralAI } from "@langchain/mistralai";
import { createAgent, createMiddleware } from "langchain";
const basicModel = new ChatMistralAI({ model: "mistral-small-latest" });
const advancedModel = new ChatMistralAI({ model: "mistral-large-latest" });
const dynamicModelSelection = createMiddleware({
name: "DynamicModelSelection",
wrapModelCall: (request, handler) => {
// Elegir modelo según complejidad de la conversación
const messageCount = request.messages.length;
const model = messageCount > 10 ? advancedModel : basicModel;
return handler({ ...request, model });
},
});
const agent = createAgent({
model: basicModel,
tools: [searchTool],
middleware: [dynamicModelSelection],
});
```
</v-click>
</div>
+40
View File
@@ -0,0 +1,40 @@
---
layout: default
---
# Middleware: wrapToolCall
<div class="mt-4 text-xs">
<v-click>
Intercepta la ejecución de tools para manejo de errores:
```typescript
import { createAgent, createMiddleware, ToolMessage } from "langchain";
const handleToolErrors = createMiddleware({
name: "HandleToolErrors",
wrapToolCall: async (request, handler) => {
try {
return await handler(request);
} catch (error) {
// Devolver mensaje de error al modelo para que reintente
return new ToolMessage({
content: `Error en tool: ${error}. Por favor revisa los parámetros.`,
tool_call_id: request.toolCall.id!,
});
}
},
});
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [databaseTool, apiTool],
middleware: [handleToolErrors],
});
```
</v-click>
</div>
+51
View File
@@ -0,0 +1,51 @@
---
layout: default
---
# Middleware Built-in: Resumen
<div class="mt-6 text-xs space-y-3">
<v-click>
<div class="border-l-4 border-blue-500 pl-4 bg-blue-50 dark:bg-blue-900/20 p-2">
<code class="font-bold">summarizationMiddleware</code> - Resumir historial cuando supera tokens
</div>
</v-click>
<v-click>
<div class="border-l-4 border-green-500 pl-4 bg-green-50 dark:bg-green-900/20 p-2">
<code class="font-bold">humanInTheLoopMiddleware</code> - Pausar para aprobación humana
</div>
</v-click>
<v-click>
<div class="border-l-4 border-purple-500 pl-4 bg-purple-50 dark:bg-purple-900/20 p-2">
<code class="font-bold">toolCallLimitMiddleware</code> - Limitar llamadas a tools
</div>
</v-click>
<v-click>
<div class="border-l-4 border-orange-500 pl-4 bg-orange-50 dark:bg-orange-900/20 p-2">
<code class="font-bold">modelCallLimitMiddleware</code> - Limitar llamadas al modelo
</div>
</v-click>
<v-click>
<div class="border-l-4 border-red-500 pl-4 bg-red-50 dark:bg-red-900/20 p-2">
<code class="font-bold">modelFallbackMiddleware</code> - Fallback a modelos alternativos
</div>
</v-click>
<v-click>
<div class="border-l-4 border-cyan-500 pl-4 bg-cyan-50 dark:bg-cyan-900/20 p-2">
<code class="font-bold">modelRetryMiddleware</code> - Reintentar con backoff exponencial
</div>
</v-click>
<v-click>
<div class="border-l-4 border-yellow-500 pl-4 bg-yellow-50 dark:bg-yellow-900/20 p-2">
<code class="font-bold">piiRedactionMiddleware</code> - Redactar información personal
</div>
</v-click>
</div>
+46
View File
@@ -0,0 +1,46 @@
---
layout: default
---
# Combinando Middleware
<div class="mt-4 text-xs">
<v-click>
```typescript
import {
createAgent,
summarizationMiddleware,
humanInTheLoopMiddleware,
toolCallLimitMiddleware,
modelRetryMiddleware,
} from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "mistral:mistral-large-latest",
tools: [searchTool, sendEmailTool, databaseTool],
checkpointer: new MemorySaver(),
middleware: [
modelRetryMiddleware({ maxRetries: 3, backoffFactor: 2 }),
summarizationMiddleware({ model: "mistral:mistral-small-latest", trigger: { tokens: 4000 } }),
toolCallLimitMiddleware({ toolName: "search", runLimit: 5 }),
humanInTheLoopMiddleware({
interruptOn: { send_email: { allowedDecisions: ["approve", "reject"] } },
}),
],
});
```
</v-click>
<v-click>
<div class="mt-3 text-xs opacity-80">
El orden de los middleware importa: se ejecutan en secuencia
</div>
</v-click>
</div>
+49
View File
@@ -0,0 +1,49 @@
---
layout: default
---
<h1 class="!text-2xl">Human-in-the-Loop</h1>
<div class="mt-1">
<v-click>
````md magic-move
```typescript
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { Command, MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-4.1",
tools: [sendEmailTool, deleteRecordTool],
checkpointer: new MemorySaver(),
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
send_email: { allowedDecisions: ["approve", "edit", "reject"] },
delete_record: { allowedDecisions: ["approve", "reject"] },
},
}),
],
});
```
```typescript
const config = { configurable: { thread_id: "thread-1" } };
// Se pausa antes de ejecutar tools sensibles
let result = await agent.invoke({ messages: [...] }, config);
// Aprobar y continuar
result = await agent.invoke(
new Command({ resume: { decisions: [{ type: "approve" }] } }),
config
);
```
````
</v-click>
</div>
---
+51
View File
@@ -0,0 +1,51 @@
---
layout: default
---
# Resumen: Agentes LangChain v1
<div class="mt-6 text-xs space-y-3">
<v-click>
<div class="border-l-4 border-blue-500 pl-4 bg-blue-50 dark:bg-blue-900/20 p-2">
<code class="font-bold">createAgent</code> - Punto de entrada principal
</div>
</v-click>
<v-click>
<div class="border-l-4 border-green-500 pl-4 bg-green-50 dark:bg-green-900/20 p-2">
<code class="font-bold">model</code> - String ("mistral:mistral-large-latest") o instancia ChatModel
</div>
</v-click>
<v-click>
<div class="border-l-4 border-purple-500 pl-4 bg-purple-50 dark:bg-purple-900/20 p-2">
<code class="font-bold">systemPrompt</code> - String o SystemMessage
</div>
</v-click>
<v-click>
<div class="border-l-4 border-orange-500 pl-4 bg-orange-50 dark:bg-orange-900/20 p-2">
<code class="font-bold">tools</code> - Array de tools definidas con Zod
</div>
</v-click>
<v-click>
<div class="border-l-4 border-red-500 pl-4 bg-red-50 dark:bg-red-900/20 p-2">
<code class="font-bold">checkpointer</code> - MemorySaver | SqliteSaver | PostgresSaver
</div>
</v-click>
<v-click>
<div class="border-l-4 border-cyan-500 pl-4 bg-cyan-50 dark:bg-cyan-900/20 p-2">
<code class="font-bold">middleware</code> - Array para customización (prompts, límites, HITL)
</div>
</v-click>
<v-click>
<div class="border-l-4 border-yellow-500 pl-4 bg-yellow-50 dark:bg-yellow-900/20 p-2">
<code class="font-bold">stream()</code> - updates | values | messages
</div>
</v-click>
</div>