Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:46 +08:00
commit 49178918d7
24 changed files with 3940 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
{
"name": "Support Agent",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "You are a helpful customer support agent...",
"llm": "gpt-4o-mini",
"temperature": 0.7,
"max_tokens": 500,
"tool_ids": ["tool_123"],
"knowledge_base": ["doc_456"],
"custom_llm": {
"endpoint": "https://api.openai.com/v1/chat/completions",
"api_key": "{{secret__openai_api_key}}",
"model": "gpt-4"
}
},
"first_message": "Hello! How can I help you today?",
"language": "en"
},
"tts": {
"model_id": "eleven_turbo_v2_5",
"voice_id": "your_voice_id",
"stability": 0.5,
"similarity_boost": 0.75,
"speed": 1.0,
"output_format": "pcm_22050"
},
"asr": {
"quality": "high",
"provider": "deepgram",
"keywords": ["product_name", "company_name"]
},
"turn": {
"mode": "normal",
"turn_timeout": 5000
},
"conversation": {
"max_duration_seconds": 600
},
"language_presets": [
{
"language": "en",
"voice_id": "en_voice_id",
"first_message": "Hello! How can I help you?"
},
{
"language": "es",
"voice_id": "es_voice_id",
"first_message": "¡Hola! ¿Cómo puedo ayudarte?"
}
]
},
"workflow": {
"nodes": [
{
"id": "node_1",
"type": "subagent",
"config": {
"system_prompt": "You are now handling technical support...",
"turn_eagerness": "patient",
"voice_id": "tech_voice_id"
}
},
{
"id": "node_2",
"type": "tool",
"tool_name": "transfer_to_human"
}
],
"edges": [
{
"from": "node_1",
"to": "node_2",
"condition": "user_requests_escalation"
}
]
},
"platform_settings": {
"widget": {
"theme": {
"primaryColor": "#3B82F6",
"backgroundColor": "#1F2937",
"textColor": "#F9FAFB"
},
"position": "bottom-right"
},
"authentication": {
"type": "signed_url",
"session_duration": 3600
},
"privacy": {
"transcripts": {
"retention_days": 730
},
"audio": {
"retention_days": 2190
},
"zero_retention": false
}
},
"webhooks": {
"post_call": {
"url": "https://api.example.com/webhook",
"headers": {
"Authorization": "Bearer {{secret__webhook_auth_token}}"
}
}
},
"tags": ["customer-support", "production"]
}

77
assets/ci-cd-example.yml Normal file
View File

@@ -0,0 +1,77 @@
name: Deploy ElevenLabs Agent
on:
push:
branches: [main]
paths:
- 'agent_configs/**'
- 'tool_configs/**'
- 'test_configs/**'
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install ElevenLabs CLI
run: npm install -g @elevenlabs/cli
- name: Dry Run (Preview Changes)
run: elevenlabs agents push --env staging --dry-run
env:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY_STAGING }}
- name: Push to Staging
if: github.event_name == 'pull_request'
run: elevenlabs agents push --env staging
env:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY_STAGING }}
- name: Run Tests
if: github.event_name == 'pull_request'
run: |
elevenlabs tests push --env staging
elevenlabs agents test "Support Agent"
env:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY_STAGING }}
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install ElevenLabs CLI
run: npm install -g @elevenlabs/cli
- name: Deploy to Production
run: elevenlabs agents push --env prod
env:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY_PROD }}
- name: Verify Deployment
run: elevenlabs agents status
env:
ELEVENLABS_API_KEY: ${{ secrets.ELEVENLABS_API_KEY_PROD }}
- name: Notify on Success
if: success()
run: echo "✅ Agent deployed to production successfully"
- name: Notify on Failure
if: failure()
run: echo "❌ Deployment failed"

View File

@@ -0,0 +1,215 @@
import { Conversation } from '@elevenlabs/client';
// Configuration
const AGENT_ID = 'your-agent-id';
const API_KEY = process.env.ELEVENLABS_API_KEY; // Server-side only, never expose in browser
// Initialize conversation
const conversation = new Conversation({
agentId: AGENT_ID,
// Authentication (choose one)
// Option 1: API key (for private agents)
apiKey: API_KEY,
// Option 2: Signed URL (most secure)
// signedUrl: 'https://api.elevenlabs.io/v1/convai/auth/...',
// Client tools (browser-side functions)
clientTools: {
updateCart: {
description: "Update shopping cart",
parameters: {
type: "object",
properties: {
item: { type: "string" },
quantity: { type: "number" }
},
required: ["item", "quantity"]
},
handler: async ({ item, quantity }) => {
console.log('Cart updated:', item, quantity);
// Your cart logic here
return { success: true };
}
}
},
// Event handlers
onConnect: () => {
console.log('Connected to agent');
updateStatus('connected');
clearTranscript();
},
onDisconnect: () => {
console.log('Disconnected from agent');
updateStatus('disconnected');
},
onEvent: (event) => {
switch (event.type) {
case 'transcript':
addToTranscript('user', event.data.text);
break;
case 'agent_response':
addToTranscript('agent', event.data.text);
break;
case 'tool_call':
console.log('Tool called:', event.data.tool_name);
break;
case 'error':
console.error('Agent error:', event.data);
showError(event.data.message);
break;
}
},
onError: (error) => {
console.error('Connection error:', error);
showError(error.message);
},
// Regional compliance
serverLocation: 'us' // 'us' | 'global' | 'eu-residency' | 'in-residency'
});
// UI Helpers
function updateStatus(status) {
const statusEl = document.getElementById('status');
if (statusEl) {
statusEl.textContent = `Status: ${status}`;
}
}
function addToTranscript(role, text) {
const transcriptEl = document.getElementById('transcript');
if (transcriptEl) {
const messageEl = document.createElement('div');
messageEl.className = `message ${role}`;
messageEl.innerHTML = `
<strong>${role === 'user' ? 'You' : 'Agent'}:</strong>
<p>${text}</p>
`;
transcriptEl.appendChild(messageEl);
transcriptEl.scrollTop = transcriptEl.scrollHeight;
}
}
function clearTranscript() {
const transcriptEl = document.getElementById('transcript');
if (transcriptEl) {
transcriptEl.innerHTML = '';
}
}
function showError(message) {
const errorEl = document.getElementById('error');
if (errorEl) {
errorEl.textContent = `Error: ${message}`;
errorEl.style.display = 'block';
}
}
function hideError() {
const errorEl = document.getElementById('error');
if (errorEl) {
errorEl.style.display = 'none';
}
}
// Button event listeners
document.getElementById('start-btn')?.addEventListener('click', async () => {
try {
hideError();
await conversation.start();
} catch (error) {
console.error('Failed to start conversation:', error);
showError(error.message);
}
});
document.getElementById('stop-btn')?.addEventListener('click', async () => {
try {
await conversation.stop();
} catch (error) {
console.error('Failed to stop conversation:', error);
showError(error.message);
}
});
// HTML Template
/*
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Voice Agent</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
button {
padding: 10px 20px;
margin: 5px;
cursor: pointer;
}
#status {
margin: 10px 0;
padding: 10px;
background: #f0f0f0;
border-radius: 4px;
}
#error {
display: none;
margin: 10px 0;
padding: 10px;
background: #ffebee;
color: #c62828;
border-radius: 4px;
}
#transcript {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
}
.message {
margin: 10px 0;
padding: 10px;
border-radius: 4px;
}
.message.user {
background: #e3f2fd;
}
.message.agent {
background: #f5f5f5;
}
</style>
</head>
<body>
<h1>ElevenLabs Voice Agent</h1>
<div>
<button id="start-btn">Start Conversation</button>
<button id="stop-btn">Stop</button>
</div>
<div id="status">Status: disconnected</div>
<div id="error"></div>
<div id="transcript"></div>
<script type="module" src="./app.js"></script>
</body>
</html>
*/

View File

@@ -0,0 +1,62 @@
import { useConversation } from '@elevenlabs/react-native';
import { View, Button, Text, ScrollView } from 'react-native';
import { z } from 'zod';
import { useState } from 'react';
export default function VoiceAgent() {
const [transcript, setTranscript] = useState<Array<{ role: string; text: string }>>([]);
const { startConversation, stopConversation, status } = useConversation({
agentId: process.env.EXPO_PUBLIC_ELEVENLABS_AGENT_ID!,
// Use signed URL (most secure)
signedUrl: async () => {
const response = await fetch('https://your-api.com/elevenlabs/auth');
const { signedUrl } = await response.json();
return signedUrl;
},
clientTools: {
updateProfile: {
description: "Update user profile",
parameters: z.object({
name: z.string()
}),
handler: async ({ name }) => {
console.log('Updating profile:', name);
return { success: true };
}
}
},
onEvent: (event) => {
if (event.type === 'transcript') {
setTranscript(prev => [...prev, { role: 'user', text: event.data.text }]);
} else if (event.type === 'agent_response') {
setTranscript(prev => [...prev, { role: 'agent', text: event.data.text }]);
}
}
});
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 20 }}>Voice Agent</Text>
<View style={{ flexDirection: 'row', gap: 10, marginBottom: 20 }}>
<Button title="Start" onPress={startConversation} disabled={status === 'connected'} />
<Button title="Stop" onPress={stopConversation} disabled={status !== 'connected'} />
</View>
<Text>Status: {status}</Text>
<ScrollView style={{ marginTop: 20, maxHeight: 400 }}>
{transcript.map((msg, i) => (
<View key={i} style={{ padding: 10, marginBottom: 10, backgroundColor: msg.role === 'user' ? '#e3f2fd' : '#f5f5f5' }}>
<Text style={{ fontWeight: 'bold' }}>{msg.role === 'user' ? 'You' : 'Agent'}</Text>
<Text>{msg.text}</Text>
</View>
))}
</ScrollView>
</View>
);
}

View File

@@ -0,0 +1,166 @@
import { useConversation } from '@elevenlabs/react';
import { z } from 'zod';
import { useState } from 'react';
export default function VoiceAgent() {
const [transcript, setTranscript] = useState<Array<{ role: 'user' | 'agent'; text: string }>>([]);
const [error, setError] = useState<string | null>(null);
const {
startConversation,
stopConversation,
status,
isSpeaking
} = useConversation({
// Agent Configuration
agentId: process.env.NEXT_PUBLIC_ELEVENLABS_AGENT_ID!,
// Authentication (choose one)
// Option 1: API key (for private agents, less secure)
// apiKey: process.env.NEXT_PUBLIC_ELEVENLABS_API_KEY,
// Option 2: Signed URL (most secure, recommended for production)
signedUrl: async () => {
const response = await fetch('/api/elevenlabs/auth');
const { signedUrl } = await response.json();
return signedUrl;
},
// Client-side tools (browser functions)
clientTools: {
updateCart: {
description: "Update the shopping cart with items",
parameters: z.object({
item: z.string().describe("The item name"),
quantity: z.number().describe("Quantity to add"),
action: z.enum(['add', 'remove']).describe("Add or remove item")
}),
handler: async ({ item, quantity, action }) => {
console.log(`${action} ${quantity}x ${item}`);
// Your cart logic here
return { success: true, total: 99.99 };
}
},
navigate: {
description: "Navigate to a different page",
parameters: z.object({
url: z.string().url().describe("The URL to navigate to")
}),
handler: async ({ url }) => {
window.location.href = url;
return { success: true };
}
}
},
// Event handlers
onConnect: () => {
console.log('Connected to agent');
setTranscript([]);
setError(null);
},
onDisconnect: () => {
console.log('Disconnected from agent');
},
onEvent: (event) => {
switch (event.type) {
case 'transcript':
setTranscript(prev => [
...prev,
{ role: 'user', text: event.data.text }
]);
break;
case 'agent_response':
setTranscript(prev => [
...prev,
{ role: 'agent', text: event.data.text }
]);
break;
case 'tool_call':
console.log('Tool called:', event.data.tool_name, event.data.parameters);
break;
case 'error':
console.error('Agent error:', event.data);
setError(event.data.message);
break;
}
},
onError: (error) => {
console.error('Connection error:', error);
setError(error.message);
},
// Regional compliance (for GDPR)
serverLocation: 'us' // 'us' | 'global' | 'eu-residency' | 'in-residency'
});
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Voice Agent</h1>
{/* Controls */}
<div className="flex gap-2 mb-4">
<button
onClick={startConversation}
disabled={status === 'connected'}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
>
Start Conversation
</button>
<button
onClick={stopConversation}
disabled={status !== 'connected'}
className="px-4 py-2 bg-red-500 text-white rounded disabled:bg-gray-300"
>
Stop
</button>
</div>
{/* Status */}
<div className="mb-4 p-2 bg-gray-100 rounded">
<p>Status: <span className="font-semibold">{status}</span></p>
{isSpeaking && <p className="text-blue-600">Agent is speaking...</p>}
</div>
{/* Error */}
{error && (
<div className="mb-4 p-2 bg-red-100 border border-red-400 text-red-700 rounded">
Error: {error}
</div>
)}
{/* Transcript */}
<div className="flex-1 overflow-y-auto border rounded p-4 space-y-2">
<h2 className="font-semibold mb-2">Transcript</h2>
{transcript.length === 0 ? (
<p className="text-gray-500">No conversation yet. Click "Start Conversation" to begin.</p>
) : (
transcript.map((message, i) => (
<div
key={i}
className={`p-2 rounded ${
message.role === 'user'
? 'bg-blue-100 ml-8'
: 'bg-gray-100 mr-8'
}`}
>
<p className="text-xs font-semibold mb-1">
{message.role === 'user' ? 'You' : 'Agent'}
</p>
<p>{message.text}</p>
</div>
))
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,70 @@
import SwiftUI
import ElevenLabs
struct VoiceAgentView: View {
@State private var isConnected = false
@State private var transcript: [(role: String, text: String)] = []
private let agentID = "your-agent-id"
private let apiKey = "your-api-key" // Use environment variable in production
var body: some View {
VStack {
Text("Voice Agent")
.font(.largeTitle)
.padding()
HStack {
Button("Start Conversation") {
startConversation()
}
.disabled(isConnected)
Button("Stop") {
stopConversation()
}
.disabled(!isConnected)
}
.padding()
Text("Status: \(isConnected ? "Connected" : "Disconnected")")
.padding()
ScrollView {
ForEach(transcript.indices, id: \.self) { index in
let message = transcript[index]
HStack {
VStack(alignment: .leading) {
Text(message.role == "user" ? "You" : "Agent")
.font(.caption)
.fontWeight(.bold)
Text(message.text)
}
.padding()
.background(message.role == "user" ? Color.blue.opacity(0.1) : Color.gray.opacity(0.1))
.cornerRadius(8)
Spacer()
}
.padding(.horizontal)
}
}
}
}
private func startConversation() {
// Initialize ElevenLabs conversation
// Implementation would use the ElevenLabs Swift SDK
isConnected = true
}
private func stopConversation() {
isConnected = false
}
}
#Preview {
VoiceAgentView()
}
// Note: This is a placeholder. Full Swift SDK documentation available at:
// https://github.com/elevenlabs/elevenlabs-swift-sdk

View File

@@ -0,0 +1,210 @@
# System Prompt Template
Use this template to create structured, effective agent prompts.
---
## Personality
```
You are [NAME], a [ROLE/PROFESSION] at [COMPANY].
You have [YEARS] years of experience [DOING WHAT].
Your key traits: [LIST 3-5 PERSONALITY TRAITS].
```
**Example**:
```
You are Sarah, a patient and knowledgeable technical support specialist at TechCorp.
You have 7 years of experience helping customers troubleshoot software issues.
Your key traits: patient, empathetic, detail-oriented, solution-focused, friendly.
```
---
## Environment
```
You're communicating via [CHANNEL: phone/chat/video].
Context: [ENVIRONMENTAL FACTORS].
Communication style: [GUIDELINES].
```
**Example**:
```
You're speaking with customers over the phone.
Context: Background noise and poor connections are common.
Communication style: Speak clearly, use short sentences, pause occasionally for emphasis.
```
---
## Tone
```
Formality: [PROFESSIONAL/CASUAL/FORMAL].
Language: [CONTRACTIONS/JARGON GUIDELINES].
Verbosity: [SENTENCE/RESPONSE LENGTH].
Emotional Expression: [HOW TO EXPRESS EMPATHY/ENTHUSIASM].
```
**Example**:
```
Formality: Professional yet warm and approachable.
Language: Use contractions for natural conversation. Avoid jargon unless customer uses it first.
Verbosity: 2-3 sentences per response. Ask one question at a time.
Emotional Expression: Show empathy with phrases like "I understand how frustrating that must be."
```
---
## Goal
```
Primary Goal: [MAIN OBJECTIVE]
Secondary Goals:
- [SUPPORTING OBJECTIVE 1]
- [SUPPORTING OBJECTIVE 2]
- [SUPPORTING OBJECTIVE 3]
Success Criteria:
- [MEASURABLE OUTCOME 1]
- [MEASURABLE OUTCOME 2]
```
**Example**:
```
Primary Goal: Resolve customer technical issues on the first call.
Secondary Goals:
- Verify customer identity securely
- Document issue details accurately
- Provide proactive tips to prevent future issues
Success Criteria:
- Customer verbally confirms issue is resolved
- Issue documented in CRM
- Customer satisfaction ≥ 4/5
```
---
## Guardrails
```
Never:
- [PROHIBITED ACTION 1]
- [PROHIBITED ACTION 2]
- [PROHIBITED ACTION 3]
Always:
- [REQUIRED ACTION 1]
- [REQUIRED ACTION 2]
Escalate When:
- [ESCALATION TRIGGER 1]
- [ESCALATION TRIGGER 2]
```
**Example**:
```
Never:
- Provide medical, legal, or financial advice
- Share confidential company information
- Make promises about refunds without verification
- Continue if customer becomes abusive
Always:
- Verify customer identity before accessing account details
- Document all interactions
- Offer alternative solutions if first approach fails
Escalate When:
- Customer requests manager
- Issue requires account credit/refund approval
- Technical issue beyond knowledge base
- Customer exhibits abusive behavior
```
---
## Tools
```
Available Tools:
1. tool_name(param1, param2)
Purpose: [WHAT IT DOES]
Use When: [TRIGGER CONDITION]
Example: [SAMPLE USAGE]
2. ...
Guidelines:
- Always explain to customer before calling tool
- Wait for tool response before continuing
- If tool fails, offer alternative
```
**Example**:
```
Available Tools:
1. lookup_order(order_id: string)
Purpose: Fetch order details from database
Use When: Customer mentions order number or asks about order status
Example: "Let me look that up for you. [Call lookup_order('ORD-12345')]"
2. send_password_reset(email: string)
Purpose: Trigger password reset email
Use When: Customer can't access account and identity verified
Example: "I'll send a password reset email. [Call send_password_reset('user@example.com')]"
3. transfer_to_supervisor()
Purpose: Escalate to human agent
Use When: Issue requires manager approval or customer explicitly requests
Example: "Let me connect you with a supervisor. [Call transfer_to_supervisor()]"
Guidelines:
- Always explain what you're doing before calling tool
- Wait for tool response before continuing conversation
- If tool fails, acknowledge and offer alternative solution
```
---
## Complete Prompt
Combine all sections into your final system prompt:
```
Personality:
You are [NAME], a [ROLE] at [COMPANY]. You have [EXPERIENCE]. Your traits: [TRAITS].
Environment:
You're communicating via [CHANNEL]. [CONTEXT]. [COMMUNICATION STYLE].
Tone:
[FORMALITY]. [LANGUAGE]. [VERBOSITY]. [EMOTIONAL EXPRESSION].
Goal:
Primary: [PRIMARY GOAL]
Secondary: [SECONDARY GOALS]
Success: [SUCCESS CRITERIA]
Guardrails:
Never: [PROHIBITIONS]
Always: [REQUIREMENTS]
Escalate: [TRIGGERS]
Tools:
[TOOL DESCRIPTIONS WITH EXAMPLES]
```
---
## Testing Your Prompt
1. Create test scenarios covering common use cases
2. Run conversations and analyze transcripts
3. Check for:
- Tone consistency
- Goal achievement
- Guardrail adherence
- Tool usage accuracy
4. Iterate based on findings
5. Monitor analytics dashboard for real performance

View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Voice Agent Widget</title>
</head>
<body>
<h1>Welcome to Our Support</h1>
<p>Need help? Click the voice assistant button in the bottom-right corner!</p>
<!-- ElevenLabs Widget -->
<script src="https://elevenlabs.io/convai-widget/index.js"></script>
<script>
ElevenLabsWidget.init({
// Required: Your agent ID
agentId: 'your-agent-id',
// Optional: Theming
theme: {
primaryColor: '#3B82F6', // Blue
backgroundColor: '#1F2937', // Dark gray
textColor: '#F9FAFB', // Light gray
accentColor: '#10B981' // Green
},
// Optional: Position
position: 'bottom-right', // or 'bottom-left'
// Optional: Custom branding
branding: {
logo: 'https://example.com/logo.png',
name: 'Support Assistant',
tagline: 'How can I help you today?'
},
// Optional: Customize button
button: {
size: 'medium', // 'small' | 'medium' | 'large'
icon: 'microphone', // 'microphone' | 'chat' | 'phone'
text: 'Talk to us' // Optional button label
},
// Optional: Auto-open widget
autoOpen: false,
autoOpenDelay: 3000, // milliseconds
// Optional: Welcome message
welcomeMessage: {
enabled: true,
message: "Hi! I'm here to help. Click to start a voice conversation."
},
// Optional: Callbacks
onOpen: () => {
console.log('Widget opened');
},
onClose: () => {
console.log('Widget closed');
},
onConversationStart: () => {
console.log('Conversation started');
},
onConversationEnd: () => {
console.log('Conversation ended');
}
});
</script>
<!-- Optional: Custom styling -->
<style>
/* Override widget styles if needed */
.elevenlabs-widget {
/* Custom styles */
}
</style>
</body>
</html>