Recopilación y análisis de datos con Agents Platform en Next.js

Recopila y analiza datos en webhooks posteriores a la llamada con Agents Platform y Next.js.

Tutorial · Da por hecho que has completado la guía de inicio rápido de ElevenAgents y que tienes un proyecto de Next.js configurado.

Introducción

En este tutorial aprenderás a crear un agente de voz que recopile información del usuario mediante una conversación, analice y extraiga los datos de forma estructurada y los envíe a tu aplicación a través del webhook posterior a la llamada.

Requisitos

  • Una cuenta de ElevenLabs con una clave de API.
  • Node.js v18 o superior instalado en tu equipo.

Configuración

Crea un nuevo proyecto de Next.js

Te recomendamos usar nuestra plantilla de Agents Platform en v0.dev como punto de partida para tu aplicación. Esta plantilla es una aplicación de Next.js lista para producción que ya incluye el agente de ElevenLabs integrado.

Configura Agents Platform

Sigue nuestra guía de Next.js para conocer los pasos de instalación y configuración. Después, vuelve aquí para implementar las funciones avanzadas.

Configuración del agente

1

Inicia sesión en ElevenLabs

Ve a elevenlabs.io e inicia sesión en tu cuenta.

2

Crea un agente նոր

Ve a Agents Platform > Agents y crea un agente nuevo a partir de la plantilla en blanco.

3

Configura el primer mensaje

Configura el primer mensaje y especifica la variable dinámica para la plataforma.

Hi {{user_name}}, I'm Jess from the ElevenLabs team. I'm here to help you design your very own ElevenLabs agent! To kick things off, let me know what kind of agent you're looking to create. For example, do you want a support agent, to help your users answer questions, or a sales agent to sell your products, or just a friend to chat with?
4

Configura el prompt de sistema

Configura el prompt de sistema. También puedes incluir aquí variables dinámicas.

You are Jess, a helpful agent helping {{user_name}} to design their very own ElevenLabs agent. The design process involves the following steps:
"initial": In the first step, collect the information about the kind of agent the user is looking to create. Summarize the user's needs back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"training": Tell the user to create the agent's knowledge base by uploading documents, or submitting URLs to public websites with information that should be available to the agent. Wait patiently without talking to the user. Only when the user confirms that they've provided everything then proceed to the next step.
"voice": Tell the user to describe the voice they want their agent to have. For example: "A professional, strong spoken female voice with a slight British accent." Repeat the description of their voice back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"email": Tell the user that we've collected all necessary information to create their ElevenLabs agent and ask them to provide their email address to get notified when the agent is ready.
Always call the `set_ui_state` tool when moving between steps!
5

Configura las herramientas de cliente

Configura la siguiente herramienta de cliente para navegar entre los pasos:

  • Nombre: set_ui_state
    • Descripción: Usa esta herramienta del lado del cliente para navegar entre los distintos estados de la interfaz de usuario.
    • Esperar respuesta: true
    • Tiempo de espera de respuesta (segundos): 1
    • Parámetros:
      • Tipo de datos: string
      • Identificador: step
      • Obligatorio: true
      • Tipo de valor: Prompt de LLM
      • Descripción: El paso al que navegar en la interfaz de usuario. Usa solo los pasos definidos en el prompt de sistema.
6

Configura la voz de tu agente

Ve a la pestaña Voice y configura la voz de tu agente. Encontrarás una lista de voces recomendadas para Agents Platform en la documentación sobre diseño de voz conversacional.

7

Configura los criterios de evaluación

Ve a la pestaña Analysis y añade un nuevo criterio de evaluación.

  • Nombre: all_data_provided
    • Prompt: Evalúa si el usuario ha proporcionado una descripción del agente que quiere generar y una descripción de la voz que debe tener el agente.
8

Configura la recopilación de datos

Puedes usar el análisis posterior a la llamada para extraer datos de la conversación. En la pestaña Analysis, en Data Collection, añade los siguientes elementos:

  • Identificador: voice_description
    • data-type: String
    • Descripción: Basándote en la descripción de la voz que el usuario quiere que tenga el agente, genera una descripción concisa de la voz que incluya edad, acento, tono y carácter, si están disponibles.
  • Identificador: agent_description
    • data-type: String
    • Descripción: Basándote en la descripción del agente que el usuario quiere diseñar, genera un prompt que pueda utilizarse para entrenar un modelo que actúe como el agente.
9

Configura el webhook posterior a la llamada

Los webhooks posteriores a la llamada se utilizan para avisarte cuando termina una llamada y se han completado los pasos de análisis y extracción de datos.

En este ejemplo, el webhook posterior a la llamada realiza varios pasos:

  1. Crea un diseño de voz personalizado basándose en voice_description.
  2. Crea un agente de ElevenLabs para usuarios basándose en el agent_description que han proporcionado.
  3. Recupera los documentos de la base de conocimientos del estado de la conversación almacenado en Redis y adjunta la base de conocimientos al agente.
  4. Envía un correo electrónico al usuario para avisarle de que su agente personalizado de ElevenLabs está listo para conversar.

Al ejecutarlo localmente, necesitarás una herramienta como ngrok para exponer tu servidor local a internet.

ngrok http 3000

Ve a la configuración de Agents Platform y, en Post-Call Webhook, crea un nuevo webhook y pega tu URL de ngrok: https://<your-url>.ngrok-free.app/api/convai-webhook.

Después de guardar el webhook, recibirás un secreto de webhooks. Asegúrate de guardar este secreto de forma segura, ya que tendrás que configurarlo más adelante en tu archivo .env.

Integra las funciones avanzadas

Configura una base de datos Redis para almacenar el estado de la conversación

En este ejemplo usamos Redis para almacenar el estado de la conversación. Esto nos permite recuperar los documentos de la base de conocimientos del estado de la conversación cuando termina la llamada.

Si vas a desplegar en Vercel, puedes configurar la integración de Upstash for Redis o, como alternativa, crear una cuenta gratuita de Upstash y una nueva base de datos.

Configura Resend para enviar correos electrónicos posteriores a la llamada

En este ejemplo usamos Resend para enviar al usuario el correo electrónico posterior a la llamada. Para ello, tendrás que crear una cuenta gratuita de Resend y configurar una nueva clave de API.

Configura las variables de entorno

En la raíz de tu proyecto, crea un archivo .env y añade las siguientes variables:

ELEVENLABS_CONVAI_WEBHOOK_SECRET=
ELEVENLABS_API_KEY=
ELEVENLABS_AGENT_ID=
# Resend
RESEND_API_KEY=
RESEND_FROM_EMAIL=
# Upstash Redis
KV_URL=
KV_REST_API_READ_ONLY_TOKEN=
REDIS_URL=
KV_REST_API_TOKEN=
KV_REST_API_URL=

Configura la seguridad y la autenticación

Para proteger tu agente de ElevenLabs, debes activar la autenticación en la pestaña Security de la configuración del agente.

Una vez activada la autenticación, tendrás que crear una URL firmada en un entorno seguro del lado del servidor para iniciar una conversación con el agente. En Next.js, puedes hacerlo configurando una nueva ruta de API.

./app/api/signed-url/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { NextResponse } from "next/server";
export async function GET() {
const agentId = process.env.ELEVENLABS_AGENT_ID;
if (!agentId) {
throw Error("ELEVENLABS_AGENT_ID is not set");
}
try {
const elevenlabs = new ElevenLabsClient();
const response = await elevenlabs.conversationalAi.conversations.getSignedUrl({
agentId,
});
return NextResponse.json({ signedUrl: response.signedUrl });
} catch (error) {
console.error("Error:", error);
return NextResponse.json({ error: "Failed to get signed URL" }, { status: 500 });
}
}

Inicia la sesión de conversación

Para iniciar la conversación, primero llama a tu ruta de API para obtener la URL firmada y, después, usa el hook useConversation para configurar la sesión de conversación.

./page.tsx
import { useConversation } from "@elevenlabs/react";
async function getSignedUrl(): Promise<string> {
const response = await fetch("/api/signed-url");
if (!response.ok) {
throw Error("Failed to get signed url");
}
const data = await response.json();
return data.signedUrl;
}
export default function Home() {
// ...
const [currentStep, setCurrentStep] = useState<
"initial" | "training" | "voice" | "email" | "ready"
>("initial");
const [conversationId, setConversationId] = useState("");
const [userName, setUserName] = useState("");
const conversation = useConversation({
onConnect: () => console.log("Connected"),
onDisconnect: () => console.log("Disconnected"),
onMessage: (message: string) => console.log("Message:", message),
onError: (error: Error) => console.error("Error:", error),
});
const startConversation = useCallback(async () => {
try {
// Request microphone permission
await navigator.mediaDevices.getUserMedia({ audio: true });
// Start the conversation with your agent
const signedUrl = await getSignedUrl();
const convId = await conversation.startSession({
signedUrl,
dynamicVariables: {
user_name: userName,
},
clientTools: {
set_ui_state: ({ step }: { step: string }): string => {
// Allow agent to navigate the UI.
setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
return `Navigated to ${step}`;
},
},
});
setConversationId(convId);
console.log("Conversation ID:", convId);
} catch (error) {
console.error("Failed to start conversation:", error);
}
}, [conversation, userName]);
const stopConversation = useCallback(async () => {
await conversation.endSession();
}, [conversation]);
// ...
}

Herramienta de cliente y variables dinámicas

En la configuración anterior del agente, registraste la herramienta de cliente set_ui_state para permitir que el agente navegue entre los distintos estados de la interfaz de usuario. Para integrarlo todo, debes pasar la implementación de la herramienta de cliente a las opciones de conversation.startSession.

Aquí también puedes pasar las variables dinámicas a la conversación.

./page.tsx
const convId = await conversation.startSession({
signedUrl,
dynamicVariables: {
user_name: userName,
},
clientTools: {
set_ui_state: ({ step }: { step: string }): string => {
// Allow agent to navigate the UI.
setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
return `Navigated to ${step}`;
},
},
});

Sube documentos a la base de conocimientos

En el paso Training, el agente pedirá al usuario que suba documentos o envíe URL de sitios web públicos con información que debería estar disponible para su agente. Aquí puedes utilizar la nueva función after de Next.js 15 para permitir la carga de documentos en segundo plano.

Crea una nueva acción de servidor upload para gestionar la creación de la base de conocimientos al enviar el formulario. Una vez creados todos los documentos de la base de conocimientos, almacena el ID de la conversación y los ID de la base de conocimientos en la base de datos Redis.

./app/actions/upload.ts
"use server";
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import { redirect } from "next/navigation";
import { after } from "next/server";
// Initialize Redis
const redis = Redis.fromEnv();
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
export async function uploadFormData(formData: FormData) {
const knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}> = [];
const files = formData.getAll("file-upload") as File[];
const email = formData.get("email-input");
const urls = formData.getAll("url-input");
const conversationId = formData.get("conversation-id");
after(async () => {
// Upload files as background job
// Create knowledge base entries
// Loop through files and create knowledge base entries
for (const file of files) {
if (file.size > 0) {
const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromFile({
file,
});
if (response.id) {
knowledgeBase.push({
id: response.id,
type: "file",
name: file.name,
});
}
}
}
// Append all urls
for (const url of urls) {
const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromUrl({
url: url as string,
});
if (response.id) {
knowledgeBase.push({
id: response.id,
type: "url",
name: `url for ${conversationId}`,
});
}
}
// Store knowledge base IDs and conversation ID in database.
const redisRes = await redis.set(
conversationId as string,
JSON.stringify({ email, knowledgeBase })
);
console.log({ redisRes });
});
redirect("/success");
}

Gestionar el webhook posterior a la llamada

El webhook posterior a la llamada se activa cuando termina una llamada y se han completado los pasos de análisis y extracción de datos.

Aquí ocurren varios pasos:

  1. Verificar el secreto del webhook y crear la carga útil del webhook.
  2. Crear un diseño de voz personalizado a partir de voice_description.
  3. Crear un agente de ElevenLabs para usuarios a partir de agent_description que han proporcionado.
  4. Recuperar los documentos de la base de conocimientos del estado de la conversación almacenado en Redis y adjuntar la base de conocimientos al agente.
  5. Enviar un correo electrónico al usuario para avisarle de que su agente personalizado de ElevenLabs está listo para conversar.
./app/api/convai-webhook/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import crypto from "crypto";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { Resend } from "resend";
import { EmailTemplate } from "@/components/email/post-call-webhook-email";
// Initialize Redis
const redis = Redis.fromEnv();
// Initialize Resend
const resend = new Resend(process.env.RESEND_API_KEY);
const elevenlabs = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
export async function GET() {
return NextResponse.json({ status: "webhook listening" }, { status: 200 });
}
export async function POST(req: NextRequest) {
const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET; // Add this to your env variables
const { event, error } = await constructWebhookEvent(req, secret);
if (error) {
return NextResponse.json({ error: error }, { status: 401 });
}
if (event.type === "post_call_transcription") {
const { conversation_id, analysis, agent_id } = event.data;
if (
agent_id === process.env.ELEVENLABS_AGENT_ID &&
analysis.evaluation_criteria_results.all_data_provided?.result === "success" &&
analysis.data_collection_results.voice_description?.value
) {
try {
// Design the voice
const voicePreview = await elevenlabs.textToVoice.createPreviews({
voiceDescription: analysis.data_collection_results.voice_description.value,
text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
});
const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
voiceName: `voice-${conversation_id}`,
voiceDescription: `Voice for ${conversation_id}`,
generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
});
// Get the knowledge base from redis
const redisRes = await getRedisDataWithRetry(conversation_id);
if (!redisRes) throw new Error("Conversation data not found!");
// Handle agent creation
const agent = await elevenlabs.conversationalAi.agents.create({
name: `Agent for ${conversation_id}`,
conversationConfig: {
tts: { voiceId: voice.voiceId },
agent: {
prompt: {
prompt:
analysis.data_collection_results.agent_description?.value ??
"You are a helpful assistant.",
knowledgeBase: redisRes.knowledgeBase,
},
firstMessage: "Hello, how can I help you today?",
},
},
});
console.log("Agent created", { agent: agent.agentId });
// Send email to user
console.log("Sending email to", redisRes.email);
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: redisRes.email,
subject: "Your ElevenLabs agent is ready to chat!",
react: EmailTemplate({ agentId: agent.agentId }),
});
} catch (error) {
console.error(error);
return NextResponse.json({ error }, { status: 500 });
}
}
}
return NextResponse.json({ received: true }, { status: 200 });
}
const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
const body = await req.text();
const signatureHeader = req.headers.get("ElevenLabs-Signature");
return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Veamos cada paso en detalle.

Verificar el secreto del webhook y crear la carga útil del webhook

Cuando se recibe la solicitud del webhook, primero verificamos el secreto del webhook y creamos la carga útil del webhook.

./app/api/convai-webhook/route.ts
// ...
export async function POST(req: NextRequest) {
const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET;
const { event, error } = await constructWebhookEvent(req, secret);
// ...
}
// ...
const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
const body = await req.text();
const signatureHeader = req.headers.get("ElevenLabs-Signature");
return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Crear un diseño de voz personalizado a partir de voice_description

Usamos voice_description de la carga útil del webhook para crear un diseño de voz personalizado.

./app/api/convai-webhook/route.ts
// ...
// Design the voice
const voicePreview = await elevenlabs.textToVoice.createPreviews({
voiceDescription: analysis.data_collection_results.voice_description.value,
text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
});
const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
voiceName: `voice-${conversation_id}`,
voiceDescription: `Voice for ${conversation_id}`,
generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
});
// ...

Recuperar los documentos de la base de conocimientos del estado de la conversación almacenado en Redis

La subida de documentos puede tardar más que el análisis de datos del webhook, por lo que tendremos que consultar el estado de la conversación en Redis hasta que se hayan subido los documentos.

./app/api/convai-webhook/route.ts
// ...
// Get the knowledge base from redis
const redisRes = await getRedisDataWithRetry(conversation_id);
if (!redisRes) throw new Error("Conversation data not found!");
// ...
async function getRedisDataWithRetry(
conversationId: string,
maxRetries = 5
): Promise<{
email: string;
knowledgeBase: Array<{
id: string;
type: "file" | "url";
name: string;
}>;
} | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const data = await redis.get(conversationId);
return data as any;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Redis get attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
return null;
}

Crear un agente de ElevenLabs para usuarios a partir de agent_description que han proporcionado

Crea el agente de ElevenLabs para el usuario a partir de agent_description que ha proporcionado y adjunta al agente el diseño de voz y la base de conocimientos recién creados.

./app/api/convai-webhook/route.ts
// ...
// Handle agent creation
const agent = await elevenlabs.conversationalAi.agents.create({
name: `Agent for ${conversationId}`,
conversationConfig: {
tts: { voiceId: voice.voiceId },
agent: {
prompt: {
prompt:
analysis.data_collection_results.agent_description?.value ??
"You are a helpful assistant.",
knowledgeBase: redisRes.knowledgeBase,
},
firstMessage: "Hello, how can I help you today?",
},
},
});
console.log("Agent created", { agent: agent.agentId });
// ...

Enviar un correo electrónico al usuario para avisarle de que su agente personalizado de ElevenLabs está listo para conversar

Una vez creado el agente, puedes enviar un correo electrónico al usuario para avisarle de que su agente personalizado de ElevenLabs está listo para conversar.

./app/api/convai-webhook/route.ts
import { Resend } from "resend";
import { EmailTemplate } from "@/components/email/post-call-webhook-email";
// ...
// Send email to user
console.log("Sending email to", redisRes.email);
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: redisRes.email,
subject: "Your ElevenLabs agent is ready to chat!",
react: EmailTemplate({ agentId: agent.agentId }),
});
// ...

Puedes usar new.email, una práctica herramienta del equipo de Resend, para diseñar con IA las plantillas de tus correos electrónicos. Cuando estés satisfecho con la plantilla, crea un componente nuevo y añade el ID del agente como prop.

./components/email/post-call-webhook-email.tsx
import {
Body,
Button,
Container,
Head,
Html,
Section,
Text,
Tailwind,
} from "@react-email/components";
import * as React from "react";
const EmailTemplate = (props: any) => {
const { agentId } = props;
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-[#151516] font-sans">
<Container className="mx-auto my-[40px] max-w-[600px] rounded-[8px] bg-[#0a1929] p-[20px]">
{/* Top Section */}
<Section className="mb-[32px] mt-[32px] text-center">
<Text className="m-0 text-[28px] font-bold text-[#9c27b0]">
Your ElevenLabs agent is ready to chat!
</Text>
</Section>
{/* Content Area with Icon */}
<Section className="mb-[32px] text-center">
{/* Circle Icon with Checkmark */}
<div className="mx-auto mb-[24px] flex h-[80px] w-[80px] items-center justify-center rounded-full bg-gradient-to-r from-[#9c27b0] to-[#3f51b5]">
<div className="text-[40px] text-white">✓</div>
</div>
{/* Descriptive Text */}
<Text className="mb-[24px] text-[18px] text-white">
Your ElevenLabs agent is ready to chat!
</Text>
</Section>
{/* Call to Action Button */}
<Section className="mb-[32px] text-center">
<Button
href={`https://elevenlabs.io/app/talk-to?agent_id=${agentId}`}
className="box-border rounded-[8px] bg-[#9c27b0] px-[40px] py-[20px] text-[24px] font-bold text-white no-underline"
>
Chat now!
</Button>
</Section>
{/* Footer */}
<Section className="mt-[40px] border-t border-[#2d3748] pt-[20px] text-center">
<Text className="m-0 text-[14px] text-white">
Powered by{" "}
<a
href="https://elevenlabs.io/conversational-ai"
target="_blank"
rel="noopener noreferrer"
className="underline transition-colors hover:text-gray-400"
>
ElevenLabs Agents
</a>
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export { EmailTemplate };

Ejecutar la aplicación

Para ejecutar la aplicación localmente de principio a fin, primero tendrás que iniciar el servidor de desarrollo de Next.js y, después, ejecutar en otra terminal el túnel de ngrok para exponer al público el gestor del webhook.

  • Terminal 1:
    • Ejecuta pnpm dev para iniciar el servidor de desarrollo de Next.js.
pnpm dev
  • Terminal 2:
    • Ejecuta ngrok http 3000 para exponer al público el gestor del webhook.
ngrok http 3000

Ahora abre http://localhost:3000 y empieza a diseñar tu agente personalizado de ElevenLabs con tu propia voz.

Siguientes pasos