Integration Guide
This comprehensive guide provides enterprise-grade strategies and patterns for integrating RunAnythingAI's APIs across web, mobile, and backend environments. Learn how to implement character-driven AI features that scale with your application architecture and deliver exceptional user experiences.
Integration Strategy Overview
Before diving into specific framework implementations, consider these architectural approaches:
Integration Patterns
| Pattern | Description | Best For |
|---|---|---|
| Backend Proxy | Route all AI requests through a server backend | Security, rate limiting, logging |
| Direct Client | Connect directly from client to RunAnythingAI | Prototyping, simple applications |
| Hybrid | Core logic on backend with stream forwarding | Real-time experiences with security |
| Serverless | Function-as-a-Service mediating AI requests | Scalable, event-driven applications |
Key Integration Decisions
- Authentication Management: Determine where API keys will be stored and managed
- Caching Strategy: Identify opportunities for response caching and reuse
- Error Handling: Design graceful degradation paths for API unavailability
- Scaling Approach: Plan for concurrent requests during high-traffic periods
- Performance Budget: Set response time targets and optimization strategies
Web Frameworks
React Integration
Here's how to create a simple character chat component in React:
import React, { useState, useEffect, useRef } from 'react';
import './CharacterChat.css';
const CharacterChat = ({ apiKey, characterPersona, characterName }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [audioUrl, setAudioUrl] = useState(null);
const audioPlayer = useRef(null);
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const sendMessage = async () => {
if (!input.trim()) return;
// Add user message to chat
const userMessage = {
id: `user-${Date.now()}`,
messageIndex: messages.length,
chatId: 'chat-session',
userId: 'user-id',
content: input,
createdAt: new Date().toISOString(),
role: 'You',
index: messages.length
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
// Step 1: Generate character response
const genResponse = await fetch('https://api.runanythingai.com/api/text/Witch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
messages: [...messages, userMessage].map(msg => ({
...msg,
upvote: 0,
image: null
})),
persona: characterPersona,
botName: characterName,
samplingParams: { max_tokens: 150 }
})
});
const { id } = await genResponse.json();
// Step 2: Poll for completion
let completed = false;
let characterResponse;
while (!completed) {
const statusResponse = await fetch(`https://api.runanythingai.com/api/v2/status/${id}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const statusData = await statusResponse.json();
if (statusData.status === 'completed') {
completed = true;
characterResponse = statusData.reply;
} else if (statusData.status === 'error') {
throw new Error(`Generation failed: ${statusData.error}`);
} else {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// Add character response to chat
const botMessage = {
id: `bot-${Date.now()}`,
messageIndex: messages.length + 1,
chatId: 'chat-session',
userId: 'bot-id',
content: characterResponse,
createdAt: new Date().toISOString(),
role: 'Bot',
index: messages.length + 1
};
setMessages(prev => [...prev, botMessage]);
// Step 3: Generate audio for the character's response
const ttsResponse = await fetch('https://api.runanythingai.com/api/audio/full', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
text: characterResponse,
voice: "af_nicole",
speed: 1
})
});
if (ttsResponse.ok) {
const audioBlob = await ttsResponse.blob();
const url = URL.createObjectURL(audioBlob);
setAudioUrl(url);
// Play audio automatically
if (audioPlayer.current) {
audioPlayer.current.src = url;
audioPlayer.current.play();
}
}
} catch (error) {
console.error('Error:', error);
setMessages(prev => [...prev, {
id: `error-${Date.now()}`,
messageIndex: messages.length + 1,
chatId: 'chat-session',
userId: 'system',
content: 'Sorry, something went wrong. Please try again.',
createdAt: new Date().toISOString(),
role: 'System',
index: messages.length + 1
}]);
} finally {
setIsLoading(false);
}
};
return (
<div className="character-chat">
<div className="chat-history">
{messages.map((msg, index) => (
<div
key={msg.id}
className={`message ${msg.role.toLowerCase()}`}
>
<div className="message-content">{msg.content}</div>
</div>
))}
{isLoading && (
<div className="message bot loading">
<div className="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="chat-input">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
disabled={isLoading}
/>
<button onClick={sendMessage} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send'}
</button>
</div>
<audio ref={audioPlayer} style={{ display: 'none' }} controls />
</div>
);
};
export default CharacterChat;
Usage in a parent component:
import CharacterChat from './CharacterChat';
function App() {
const characterPersona = `Emma's Persona: Emma is a friendly AI assistant with expertise in
technology topics. She has a cheerful demeanor and likes to use examples to explain complex
concepts. Emma occasionally uses tech-related metaphors and has a slight tendency to get
excited about innovations.`;
return (
<div className="App">
<h1>Chat with Emma</h1>
<CharacterChat
apiKey="YOUR_API_KEY"
characterPersona={characterPersona}
characterName="Emma"
/>
</div>
);
}
Vue.js Integration
Here's how to create a character chat component in Vue.js:
<template>
<div class="character-chat">
<div class="chat-messages" ref="chatContainer">
<div
v-for="(message, index) in messages"
:key="index"
:class="['message', message.role.toLowerCase()]"
>
{{ message.content }}
</div>
<div v-if="isLoading" class="message bot typing">
<span></span><span></span><span></span>
</div>
</div>
<div class="chat-input">
<input
v-model="userInput"
@keyup.enter="sendMessage"
placeholder="Type your message..."
:disabled="isLoading"
/>
<button @click="sendMessage" :disabled="isLoading">
{{ isLoading ? 'Sending...' : 'Send' }}
</button>
</div>
<audio ref="audioPlayer" style="display: none"></audio>
</div>
</template>
<script>
export default {
name: 'CharacterChat',
props: {
apiKey: {
type: String,
required: true
},
characterPersona: {
type: String,
required: true
},
characterName: {
type: String,
required: true
}
},
data() {
return {
messages: [],
userInput: '',
isLoading: false
};
},
watch: {
messages() {
this.$nextTick(this.scrollToBottom);
}
},
methods: {
scrollToBottom() {
const container = this.$refs.chatContainer;
container.scrollTop = container.scrollHeight;
},
async sendMessage() {
if (!this.userInput.trim()) return;
// Add user message
const userMessage = {
id: `user-${Date.now()}`,
messageIndex: this.messages.length,
chatId: 'chat-session',
userId: 'user-id',
content: this.userInput,
createdAt: new Date().toISOString(),
role: 'You',
index: this.messages.length
};
this.messages.push(userMessage);
this.userInput = '';
this.isLoading = true;
try {
// Generate character response
const genResponse = await fetch('https://api.runanythingai.com/api/text/Witch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
messages: this.messages.map(msg => ({
...msg,
upvote: 0,
image: null
})),
persona: this.characterPersona,
botName: this.characterName,
samplingParams: { max_tokens: 150 }
})
});
const { id } = await genResponse.json();
// Poll for completion
let completed = false;
let characterResponse;
while (!completed) {
const statusResponse = await fetch(`https://api.runanythingai.com/api/v2/status/${id}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
const statusData = await statusResponse.json();
if (statusData.status === 'completed') {
completed = true;
characterResponse = statusData.reply;
} else if (statusData.status === 'error') {
throw new Error(`Generation failed: ${statusData.error}`);
} else {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// Add character response
const botMessage = {
id: `bot-${Date.now()}`,
messageIndex: this.messages.length,
chatId: 'chat-session',
userId: 'bot-id',
content: characterResponse,
createdAt: new Date().toISOString(),
role: 'Bot',
index: this.messages.length
};
this.messages.push(botMessage);
// Generate TTS
const ttsResponse = await fetch('https://api.runanythingai.com/api/audio/full', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
text: characterResponse,
voice: "af_nicole",
speed: 1
})
});
if (ttsResponse.ok) {
const audioBlob = await ttsResponse.blob();
const url = URL.createObjectURL(audioBlob);
// Play audio
this.$refs.audioPlayer.src = url;
this.$refs.audioPlayer.play();
}
} catch (error) {
console.error('Error:', error);
this.messages.push({
id: `error-${Date.now()}`,
messageIndex: this.messages.length,
chatId: 'chat-session',
userId: 'system',
content: 'Sorry, something went wrong. Please try again.',
createdAt: new Date().toISOString(),
role: 'System',
index: this.messages.length
});
} finally {
this.isLoading = false;
}
}
}
};
</script>
<style scoped>
.character-chat {
display: flex;
flex-direction: column;
height: 500px;
width: 100%;
max-width: 600px;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
margin: 0 auto;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
background-color: #f9f9f9;
}
.message {
margin-bottom: 12px;
padding: 8px 12px;
border-radius: 18px;
max-width: 80%;
}
.message.you {
background-color: #dcf8c6;
align-self: flex-end;
margin-left: auto;
}
.message.bot {
background-color: #ffffff;
align-self: flex-start;
margin-right: auto;
}
.typing span {
display: inline-block;
width: 8px;
height: 8px;
background-color: #888;
border-radius: 50%;
margin-right: 4px;
animation: typing 1.4s infinite both;
}
.typing span:nth-child(2) {
animation-delay: 0.2s;
}
.typing span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0% { opacity: 0.3; transform: translateY(0); }
50% { opacity: 1; transform: translateY(-5px); }
100% { opacity: 0.3; transform: translateY(0); }
}
.chat-input {
display: flex;
padding: 10px;
border-top: 1px solid #eee;
background: white;
}
.chat-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-right: 8px;
}
.chat-input button {
padding: 8px 16px;
background-color: #5181ff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
}
.chat-input button:disabled {
background-color: #b0c4ff;
}
</style>
Mobile Integration
React Native
Here's a simplified example of integrating RunAnythingAI with React Native for a mobile character chat app:
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
FlatList,
ActivityIndicator,
StyleSheet,
} from 'react-native';
import { Audio } from 'expo-av';
const CharacterChat = ({ apiKey, characterPersona, characterName }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [sound, setSound] = useState();
const flatListRef = useRef(null);
async function playSound(uri) {
console.log('Loading Sound');
const { sound } = await Audio.Sound.createAsync({ uri });
setSound(sound);
console.log('Playing Sound');
await sound.playAsync();
}
useEffect(() => {
return sound
? () => {
console.log('Unloading Sound');
sound.unloadAsync();
}
: undefined;
}, [sound]);
const sendMessage = async () => {
if (!input.trim()) return;
// Add user message to chat
const userMessage = {
id: `user-${Date.now()}`,
messageIndex: messages.length,
chatId: 'chat-session',
userId: 'user-id',
content: input,
createdAt: new Date().toISOString(),
role: 'You',
index: messages.length
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
// Step 1: Generate character response
const genResponse = await fetch('https://api.runanythingai.com/api/text/Witch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
messages: [...messages, userMessage].map(msg => ({
...msg,
upvote: 0,
image: null
})),
persona: characterPersona,
botName: characterName,
samplingParams: { max_tokens: 150 }
})
});
const { id } = await genResponse.json();
// Step 2: Poll for completion
let completed = false;
let characterResponse;
while (!completed) {
const statusResponse = await fetch(`https://api.runanythingai.com/api/v2/status/${id}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const statusData = await statusResponse.json();
if (statusData.status === 'completed') {
completed = true;
characterResponse = statusData.reply;
} else if (statusData.status === 'error') {
throw new Error(`Generation failed: ${statusData.error}`);
} else {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// Add character response to chat
const botMessage = {
id: `bot-${Date.now()}`,
messageIndex: messages.length + 1,
chatId: 'chat-session',
userId: 'bot-id',
content: characterResponse,
createdAt: new Date().toISOString(),
role: 'Bot',
index: messages.length + 1
};
setMessages(prev => [...prev, botMessage]);
// Step 3: Generate audio for the character's response
const ttsResponse = await fetch('https://api.runanythingai.com/api/audio/full', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
text: characterResponse,
voice: "af_nicole",
speed: 1
})
});
if (ttsResponse.ok) {
const blob = await ttsResponse.blob();
const uri = URL.createObjectURL(blob);
playSound(uri);
}
} catch (error) {
console.error('Error:', error);
setMessages(prev => [...prev, {
id: `error-${Date.now()}`,
messageIndex: messages.length + 1,
chatId: 'chat-session',
userId: 'system',
content: 'Sorry, something went wrong. Please try again.',
createdAt: new Date().toISOString(),
role: 'System',
index: messages.length + 1
}]);
} finally {
setIsLoading(false);
}
};
const renderMessage = ({ item }) => (
<View style={[
styles.messageBubble,
item.role === 'You' ? styles.userBubble : styles.botBubble
]}>
<Text>{item.content}</Text>
</View>
);
return (
<View style={styles.container}>
<FlatList
ref={flatListRef}
data={messages}
renderItem={renderMessage}
keyExtractor={item => item.id}
onContentSizeChange={() => flatListRef.current.scrollToEnd({ animated: true })}
style={styles.messageList}
/>
{isLoading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color="#0000ff" />
</View>
)}
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Type your message..."
editable={!isLoading}
/>
<TouchableOpacity
style={[styles.sendButton, isLoading && styles.disabledButton]}
onPress={sendMessage}
disabled={isLoading}
>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
messageList: {
flex: 1,
padding: 10,
},
messageBubble: {
padding: 10,
marginVertical: 5,
borderRadius: 15,
maxWidth: '80%',
},
userBubble: {
backgroundColor: '#dcf8c6',
alignSelf: 'flex-end',
},
botBubble: {
backgroundColor: 'white',
alignSelf: 'flex-start',
},
loadingContainer: {
padding: 10,
alignItems: 'center',
},
inputContainer: {
flexDirection: 'row',
padding: 10,
backgroundColor: 'white',
borderTopWidth: 1,
borderTopColor: '#eee',
},
input: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 20,
paddingHorizontal: 15,
paddingVertical: 10,
marginRight: 10,
},
sendButton: {
backgroundColor: '#5181ff',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 20,
justifyContent: 'center',
},
disabledButton: {
backgroundColor: '#b0c4ff',
},
sendButtonText: {
color: 'white',
fontWeight: '600',
},
});
export default CharacterChat;
Server-Side Integration
Node.js Express Server
You can also integrate RunAnythingAI on the server side to handle API keys securely:
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
const app = express();
const port = process.env.PORT || 3000;
// Replace with your API key from environment variables
const API_KEY = process.env.RUNANYTHING_API_KEY;
app.use(cors());
app.use(express.json());
// Endpoint to generate character responses
app.post('/api/character-response', async (req, res) => {
try {
const { messages, persona, characterName } = req.body;
const genResponse = await fetch('https://api.runanythingai.com/api/text/Witch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
messages,
persona,
botName: characterName,
samplingParams: { max_tokens: 150 }
})
});
const data = await genResponse.json();
res.json({ id: data.id });
} catch (error) {
console.error('Error generating response:', error);
res.status(500).json({ error: 'Failed to generate response' });
}
});
// Status checking endpoint
app.get('/api/status/:id', async (req, res) => {
try {
const { id } = req.params;
const statusResponse = await fetch(`https://api.runanythingai.com/api/v2/status/${id}`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
const statusData = await statusResponse.json();
res.json(statusData);
} catch (error) {
console.error('Error checking status:', error);
res.status(500).json({ error: 'Failed to check status' });
}
});
// Text-to-speech endpoint
app.post('/api/tts', async (req, res) => {
try {
const { text, voice, speed } = req.body;
const ttsResponse = await fetch('https://api.runanythingai.com/api/audio/full', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
text,
voice: voice || 'af_nicole',
speed: speed || 1
})
});
if (!ttsResponse.ok) {
throw new Error(`TTS API error: ${await ttsResponse.text()}`);
}
// Pipe the audio response directly to the client
ttsResponse.body.pipe(res);
} catch (error) {
console.error('Error generating TTS:', error);
res.status(500).json({ error: 'Failed to generate speech' });
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
Authentication and Security
Secure access to RunAnythingAI APIs through these best practices:
Secure API Key Management
Never expose API keys in client-side code. Instead, implement a backend token service:
// Express.js API Key Management Service
const express = require('express');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const app = express();
// Rate limiting to prevent token abuse
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false
});
// Apply rate limiting to token issuance
app.use('/api/token', limiter);
// Environment variables for security
const API_KEY = process.env.RUNANYTHING_API_KEY;
const JWT_SECRET = process.env.JWT_SECRET;
// Token issuance endpoint (authenticate users first)
app.post('/api/token', authenticateUser, (req, res) => {
// User is authenticated at this point
const userContext = {
userId: req.user.id,
role: req.user.role,
permissions: req.user.permissions,
exp: Math.floor(Date.now() / 1000) + (60 * 15) // 15 minute expiration
};
// Sign client token that doesn't contain the API key
const token = jwt.sign(userContext, JWT_SECRET);
res.json({ token });
});
// Proxy endpoint for AI API calls
app.post('/api/proxy/text/:model', authenticateToken, (req, res) => {
const { model } = req.params;
const requestBody = req.body;
// Add request validation, logging, and user context here
// Forward to RunAnythingAI with your secured API key
fetch(`https://api.runanythingai.com/api/text/${model}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify(requestBody)
})
.then(apiRes => apiRes.json())
.then(data => res.json(data))
.catch(error => {
console.error('API error:', error);
res.status(500).json({ error: 'API request failed' });
});
});
// JWT verification middleware
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Access token required' });
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid or expired token' });
req.user = user;
next();
});
}
// Implement your user authentication logic
function authenticateUser(req, res, next) {
// Your authentication implementation
}
app.listen(3000, () => {
console.log('Security proxy server running on port 3000');
});
Request Signing and Verification
For high-security environments, implement request signing:
const crypto = require('crypto');
// Generate a signature for API requests
function signRequest(payload, timestamp, secretKey) {
const data = JSON.stringify(payload) + timestamp;
return crypto
.createHmac('sha256', secretKey)
.update(data)
.digest('hex');
}
// Client-side request with signature
async function secureAPIRequest(endpoint, payload) {
const timestamp = Date.now().toString();
const signature = signRequest(payload, timestamp, CLIENT_SECRET);
const response = await fetch(`/api/proxy/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Signature': signature,
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify(payload)
});
return response.json();
}
Content Security
Implement content filtering for user inputs and AI outputs:
// Content moderation middleware
function moderateContent(req, res, next) {
const userContent = req.body.messages
.filter(m => m.role === 'You')
.map(m => m.content)
.join(' ');
// Check against banned words/patterns
const bannedPatterns = [/offensive\s+term/i, /abusive\s+content/i];
for (const pattern of bannedPatterns) {
if (pattern.test(userContent)) {
return res.status(400).json({
error: 'Content policy violation detected',
code: 'CONTENT_POLICY_VIOLATION'
});
}
}
next();
}
// Apply to API routes
app.post('/api/proxy/text/:model', authenticateToken, moderateContent, handleRequest);
Testing and Monitoring
Ensure reliability of your RunAnythingAI integration with comprehensive testing and monitoring strategies.
Automated Integration Testing
Example using Jest for testing character interactions:
// character-service.test.js
const axios = require('axios');
const MockAdapter = require('axios-mock-adapter');
const characterService = require('../services/character-service');
const mock = new MockAdapter(axios);
describe('Character Service Integration Tests', () => {
// Reset mocks between tests
afterEach(() => {
mock.reset();
});
test('should handle successful character generation and TTS flow', async () => {
// Mock generation request
mock.onPost('https://api.runanythingai.com/api/text/Witch').reply(200, {
id: 'mock-request-123'
});
// Mock status check - first processing, then completed
mock.onGet('https://api.runanythingai.com/api/v2/status/mock-request-123')
.replyOnce(200, { status: 'processing' })
.replyOnce(200, {
status: 'completed',
reply: 'This is a test character response.'
});
// Mock TTS response
mock.onPost('https://api.runanythingai.com/api/audio/full')
.reply(200, Buffer.from('mock-audio-data'), {
'Content-Type': 'audio/mpeg'
});
// Test character interaction flow
const result = await characterService.characterInteraction(
'Witch',
[{ role: 'You', content: 'Hello character', index: 0 }],
'Test persona description',
'TestBot'
);
// Assertions
expect(result).toBeDefined();
expect(result.text).toBe('This is a test character response.');
expect(result.audio).toBeDefined();
});
test('should handle API errors gracefully', async () => {
// Mock failed generation request
mock.onPost('https://api.runanythingai.com/api/text/Witch').reply(429, {
error: 'Rate limit exceeded'
});
// Test error handling
await expect(characterService.characterInteraction(
'Witch',
[{ role: 'You', content: 'Hello character', index: 0 }],
'Test persona description',
'TestBot'
)).rejects.toThrow(/Rate limit exceeded/);
});
});
End-to-End Testing
For full end-to-end testing with Cypress:
// cypress/integration/character-chat.spec.js
describe('Character Chat Component', () => {
beforeEach(() => {
// Set up API mocks
cy.intercept('POST', '/api/proxy/text/*', {
statusCode: 200,
body: { id: 'cypress-mock-id' }
}).as('textGeneration');
cy.intercept('GET', '/api/proxy/status/*', {
statusCode: 200,
body: {
status: 'completed',
reply: 'This is a mock character response from Cypress.'
}
}).as('statusCheck');
// Mock audio endpoint with a test MP3
cy.intercept('POST', '/api/proxy/audio/full', {
statusCode: 200,
fixture: 'test-audio.mp3',
}).as('ttsRequest');
// Visit the chat page
cy.visit('/character-chat');
});
it('should send message and display character response', () => {
// Type and send a message
cy.get('.chat-input input')
.type('Hello, AI character!');
cy.get('.chat-input button')
.click();
// Check that user message appears in chat
cy.get('.message.user')
.should('contain', 'Hello, AI character!');
// Verify API calls
cy.wait('@textGeneration');
cy.wait('@statusCheck');
// Verify character response displays
cy.get('.message.bot')
.should('contain', 'This is a mock character response from Cypress.');
// Verify audio player appears and has src
cy.get('audio')
.should('exist')
.and('have.attr', 'src')
.should('include', 'blob:');
});
it('should show loading indicator while waiting for response', () => {
// Override status check to first return processing
cy.intercept('GET', '/api/proxy/status/*', {
statusCode: 200,
body: { status: 'processing' }
}).as('processingCheck');
// Type and send message
cy.get('.chat-input input')
.type('Are you still thinking?');
cy.get('.chat-input button')
.click();
// Verify loading indicator appears
cy.get('.typing-indicator')
.should('be.visible');
// Now return completed response
cy.intercept('GET', '/api/proxy/status/*', {
statusCode: 200,
body: {
status: 'completed',
reply: 'Yes, I was thinking, but now I have an answer.'
}
}).as('completedCheck');
// Wait for completed response
cy.wait('@completedCheck');
// Verify loading indicator disappears and response appears
cy.get('.typing-indicator')
.should('not.exist');
cy.get('.message.bot')
.should('contain', 'Yes, I was thinking');
});
});
Production Monitoring
Set up comprehensive monitoring for your RunAnythingAI integration:
// Monitor API performance and errors with OpenTelemetry
const { trace } = require('@opentelemetry/api');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
// Configure OpenTelemetry
const provider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'character-service',
}),
});
// Configure exporter
const exporter = new ZipkinExporter({
url: 'http://zipkin:9411/api/v2/spans',
serviceName: 'character-service'
});
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
// Get tracer
const tracer = trace.getTracer('character-service-tracer');
// Instrumented character interaction function
async function monitoredCharacterInteraction(character, messages, persona, botName) {
// Create a span for the entire operation
return tracer.startActiveSpan('character-interaction', async (span) => {
try {
// Add context attributes
span.setAttribute('character.type', character);
span.setAttribute('message.count', messages.length);
// Start text generation
const generationStart = Date.now();
span.addEvent('Starting text generation');
// Step 1: Generate text with sub-span
let generationSpan = tracer.startSpan('text-generation');
generationSpan.setAttribute('character.model', character);
const { id } = await sendGenerationRequest(character, messages, persona, botName);
generationSpan.end();
// Step 2: Poll with monitoring
let pollSpan = tracer.startSpan('status-polling');
const result = await pollStatus(id);
pollSpan.setAttribute('polling.duration', Date.now() - generationStart);
pollSpan.end();
// Step 3: TTS with monitoring
let ttsSpan = tracer.startSpan('text-to-speech');
ttsSpan.setAttribute('tts.text_length', result.length);
const audio = await generateSpeech(result);
ttsSpan.end();
// Record metrics
span.setAttribute('total.duration', Date.now() - generationStart);
return { text: result, audio };
} catch (error) {
span.recordException(error);
span.setStatus({
code: trace.SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
span.end();
}
});
}
// Example health check endpoint for monitoring
app.get('/health', (req, res) => {
Promise.all([
checkAPIAccess('Witch'),
checkDatabaseConnection(),
checkCacheService()
])
.then(([apiStatus, dbStatus, cacheStatus]) => {
const allHealthy = apiStatus.healthy && dbStatus.healthy && cacheStatus.healthy;
res.status(allHealthy ? 200 : 503).json({
status: allHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
services: {
api: apiStatus,
database: dbStatus,
cache: cacheStatus
}
});
})
.catch(error => {
res.status(500).json({
status: 'error',
message: error.message
});
});
});
Production Deployment Checklist
Before launching your RunAnythingAI integration, ensure you've addressed these critical areas:
Performance & Scalability
- Implement caching for repetitive interactions
- Set up request throttling and queuing
- Configure timeout and retry mechanisms
- Test system behavior under high concurrency
- Benchmark load capacity and scale accordingly
Security
- Secure API key storage in environment variables or secrets management
- Implement backend proxy for API requests
- Set up content filtering for user inputs
- Configure appropriate CORS policies
- Audit all data flows for exposure risks
Error Handling
- Implement graceful degradation for API failures
- Create fallback content for offline scenarios
- Set up comprehensive error logging
- Design user-friendly error messages
- Test recovery from various failure scenarios
Monitoring
- Configure API call tracking and logging
- Set up latency and error rate alerting
- Implement usage tracking for billing management
- Create dashboards for key performance metrics
- Establish on-call procedures for critical issues
User Experience
- Design appropriate loading states
- Implement progress indicators for long-running tasks
- Test accessibility of AI interaction components
- Create feedback mechanisms for response quality
- Validate multi-device and responsive behavior
Compliance
- Document AI-generated content for audit purposes
- Implement appropriate user consent mechanisms
- Verify GDPR/CCPA compliance for user data
- Ensure terms of service cover AI interactions
- Create data retention policies for conversations
Integration Case Studies
Case Study: Enterprise Customer Service Platform
A customer service platform integrated RunAnythingAI to provide multiple AI characters representing different specialist roles, resulting in:
- 42% reduction in first-response times
- 67% improvement in customer satisfaction scores
- 3x increase in simultaneous customer handling capacity
Key implementation details:
- Used Redis-backed distributed caching for common inquiries
- Implemented specialized character personas for different support tiers
- Created seamless handoff between AI characters and human specialists
- Built comprehensive analytics to identify knowledge gaps
Case Study: Mobile Gaming Companion App
A mobile game developer integrated RunAnythingAI character endpoints to create in-game companions:
- Created 12 distinct character personas matching game world NPCs
- Implemented offline-mode with cached common interactions
- Built adaptive memory system to track player interactions across sessions
- Designed voice customization matching character appearances
Technical approach:
- React Native implementation with native audio modules
- Background audio processing for seamless voice transitions
- Progressive loading of character responses during gameplay
- Intelligent local caching to reduce API calls