Hoppa till navigering

Datainsamling och analys med Agents Platform i Next.js

Samla in och analysera data i webhooks efter samtal med Agents Platform och Next.js.

Handledning · Förutsätter att du har slutfört snabbstarten för ElevenAgents och har ett Next.js-projekt konfigurerat.

Introduktion

I den här handledningen lär du dig att bygga en röstagent som samlar in information från användaren genom en konversation, sedan analyserar och extraherar data på ett strukturerat sätt och skickar den till din applikation via webhooken efter samtalet.

Krav

  • Ett ElevenLabs-konto med en API-nyckel.
  • Node.js v18 eller senare installerat på din dator.

Konfiguration

Skapa ett nytt Next.js-projekt

Vi rekommenderar att du använder vår v0.dev-mall för Agents Platform som utgångspunkt för din applikation. Den här mallen är en produktionsklar Next.js-applikation med ElevenLabs-agenten redan integrerad.

Konfigurera Agents Platform

Följ vår Next.js-guide för installations- och konfigurationssteg. Kom sedan tillbaka hit för att bygga de avancerade funktionerna.

Agentkonfiguration

1

Logga in på ElevenLabs

Gå till elevenlabs.io och logga in på ditt konto.

2

Skapa en ny agent

Gå till Agents Platform > Agents och skapa en ny agent från den tomma mallen.

3

Ange det första meddelandet

Ange det första meddelandet och specificera den dynamiska variabeln för plattformen.

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

Ange systemprompten

Ange systemprompten. Du kan också inkludera dynamiska variabler här.

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

Konfigurera klientverktygen

Konfigurera följande klientverktyg för att navigera mellan stegen:

  • Namn: set_ui_state
    • Beskrivning: Använd det här verktyget på klientsidan för att navigera mellan olika UI-lägen.
    • Vänta på svar: true
    • Tidsgräns för svar (sekunder): 1
    • Parametrar:
      • Datatyp: string
      • Identifierare: step
      • Obligatoriskt: true
      • Värdetyp: LLM Prompt
      • Beskrivning: Steget att navigera till i användargränssnittet. Använd endast steg som definieras i systemprompten!
6

Ange din agents röst

Gå till fliken Voice och ange rösten för din agent. Du hittar en lista över rekommenderade röster för Agents Platform i dokumentationen för Conversational Voice Design.

7

Ange utvärderingskriterierna

Gå till fliken Analysis och lägg till ett nytt utvärderingskriterium.

  • Namn: all_data_provided
    • Prompt: Utvärdera om användaren har angett en beskrivning av agenten de vill skapa samt en beskrivning av rösten agenten ska ha.
8

Konfigurera datainsamlingen

Du kan använda analysen efter samtalet för att extrahera data från konversationen. Lägg till följande objekt på fliken Analysis, under Data Collection:

  • Identifierare: voice_description
    • data-type: String
    • Beskrivning: Utifrån beskrivningen av rösten användaren vill att agenten ska ha, generera en kortfattad beskrivning av rösten, inklusive ålder, accent, ton och karaktär om tillgängligt.
  • Identifierare: agent_description
    • data-type: String
    • Beskrivning: Utifrån beskrivningen av agenten användaren vill skapa, generera en prompt som kan användas för att träna en modell att agera som agenten.
9

Konfigurera webhooken efter samtalet

Webhooks efter samtal används för att meddela dig när ett samtal avslutas och stegen för analys och dataextrahering har slutförts.

I det här exemplet utför webhooken efter samtalet några steg, nämligen:

  1. Skapa en anpassad Voice Design baserat på voice_description.
  2. Skapa en ElevenLabs-agent för användarna baserat på agent_description som de angav.
  3. Hämta kunskapsbasdokumenten från konversationstillståndet som lagras i Redis och koppla kunskapsbasen till agenten.
  4. Skicka ett e-postmeddelande till användaren för att meddela att deras anpassade ElevenLabs-agent är redo att chatta.

När du kör lokalt behöver du ett verktyg som ngrok för att exponera din lokala server på internet.

ngrok http 3000

Gå till inställningarna för Agents Platform och skapa en ny webhook under Post-Call Webhook. Klistra sedan in din ngrok-URL: https://<your-url>.ngrok-free.app/api/convai-webhook.

När du har sparat webhooken får du en webhook-hemlighet. Se till att lagra den säkert eftersom du behöver ange den i din .env-fil senare.

Integrera de avancerade funktionerna

Konfigurera en Redis-databas för att lagra konversationstillståndet

I det här exemplet använder vi Redis för att lagra konversationstillståndet. Det gör att vi kan hämta kunskapsbasdokumenten från konversationstillståndet efter att samtalet har avslutats.

Om du distribuerar till Vercel kan du konfigurera integreringen Upstash for Redis, eller registrera ett kostnadsfritt Upstash-konto och skapa en ny databas.

Konfigurera Resend för att skicka e-post efter samtal

I det här exemplet använder vi Resend för att skicka e-postmeddelandet efter samtalet till användaren. För att göra det behöver du skapa ett kostnadsfritt Resend-konto och konfigurera en ny API-nyckel.

Ange miljövariablerna

Skapa en .env-fil i projektets rot och lägg till följande variabler:

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=

Konfigurera säkerhet och autentisering

För att skydda din ElevenLabs-agent måste du aktivera autentisering på fliken Security i agentkonfigurationen.

När autentisering är aktiverad behöver du skapa en signerad URL i en säker servermiljö för att starta en konversation med agenten. I Next.js kan du göra det genom att konfigurera en ny API-rutt.

./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 });
}
}

Starta konversationssessionen

För att starta konversationen anropar du först din API-rutt för att hämta den signerade URL:en och använder sedan hooken useConversation för att konfigurera konversationssessionen.

./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]);
// ...
}

Klientverktyg och dynamiska variabler

I agentkonfigurationen tidigare registrerade du klientverktyget set_ui_state så att agenten kan navigera mellan olika UI-lägen. För att sätta ihop allt behöver du skicka implementeringen av klientverktyget till alternativen för conversation.startSession.

Här kan du också skicka dynamiska variabler till konversationen.

./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}`;
},
},
});

Ladda upp dokument till kunskapsbasen

I steget Training ber agenten användaren att ladda upp dokument eller skicka URL:er till offentliga webbplatser med information som ska vara tillgänglig för deras agent. Här kan du använda den nya funktionen after i Next.js 15 för att tillåta uppladdning av dokument i bakgrunden.

Skapa en ny serveråtgärd upload för att hantera skapandet av kunskapsbasen när formuläret skickas in. När alla kunskapsbasdokument har skapats lagrar du konversations-ID:t och kunskapsbas-ID:na i Redis-databasen.

./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");
}

Hantera webhooken efter samtalet

Webhooken efter samtalet utlöses när ett samtal avslutas och stegen för analys och dataextrahering har slutförts.

Här sker några steg, nämligen:

  1. Verifiera webhook-hemligheten och skapa webhookens payload.
  2. Skapa en anpassad Voice Design baserat på voice_description.
  3. Skapa en ElevenLabs-agent för användarna baserat på agent_description som de angav.
  4. Hämta kunskapsbasdokumenten från konversationstillståndet som lagras i Redis och koppla kunskapsbasen till agenten.
  5. Skicka ett e-postmeddelande till användaren för att meddela att deras anpassade ElevenLabs-agent är redo att chatta.
./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;
}

Låt oss gå igenom varje steg i detalj.

Verifiera webhook-hemligheten och skapa webhookens payload

När webhookbegäran tas emot verifierar vi först webhook-hemligheten och skapar webhookens payload.

./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;
}

Skapa en anpassad Voice Design baserat på voice_description

Med voice_description från webhookens payload skapar vi en anpassad Voice Design.

./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,
});
// ...

Hämta kunskapsbasdokumenten från konversationstillståndet som lagras i Redis

Uppladdningen av dokumenten kan ta längre tid än webhookens dataanalys, så vi måste fråga efter konversationstillståndet i Redis tills dokumenten har laddats upp.

./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;
}

Skapa en ElevenLabs-agent för användarna baserat på agent_description som de angav

Skapa ElevenLabs-agenten för användaren baserat på agent_description som de angav och koppla den nyligen skapade Voice Design och kunskapsbasen till agenten.

./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 });
// ...

Skicka ett e-postmeddelande till användaren för att meddela att deras anpassade ElevenLabs-agent är redo att chatta

När agenten har skapats kan du skicka ett e-postmeddelande till användaren för att meddela att deras anpassade ElevenLabs-agent är redo att chatta.

./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 }),
});
// ...

Du kan använda new.email, ett praktiskt verktyg från Resend-teamet, för att designa dina e-postmallar med vibe coding. När du är nöjd med mallen skapar du en ny komponent och lägger till agent-ID:t som en 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 };

Kör appen

För att köra appen lokalt från början till slut måste du först starta Next.js-utvecklingsservern och sedan köra ngrok-tunneln i en separat terminal för att exponera webhookhanteraren på internet.

  • Terminal 1:
    • Kör pnpm dev för att starta Next.js-utvecklingsservern.
pnpm dev
  • Terminal 2:
    • Kör ngrok http 3000 för att exponera webhookhanteraren på internet.
ngrok http 3000

Öppna nu http://localhost:3000 och börja designa din anpassade ElevenLabs-agent med din röst!

Nästa steg