Build a Personal AI Assistant - Part 2: Core Assistant Architecture & Logic

π Build a Personal AI Assistant
Ad Space
Build a Personal AI Assistant - Part 2: Core Assistant Architecture & Logic
Now we'll architect the brain of your AI assistant. This isn't just about connecting to OpenAIβwe're building a robust, scalable system that handles failures gracefully, manages resources efficiently, and provides a delightful user experience.
Tutorial Navigation
- Previous: Part 1: Foundation & Environment Setup
- Current: Part 2: Core Assistant Architecture & Logic
- Next: Part 3: Conversation Memory
- Series: Part 4: API Integration | Part 5: Testing & Deployment
What You'll Understand & Build
By the end of this tutorial, you'll understand:
- π§ How to architect resilient AI systems
- π Why retry logic prevents user frustration
- β‘ How rate limiting saves money and prevents bans
- π Why prompt engineering shapes AI behavior
- π§ͺ How testing ensures reliability
- π¬ Why CLI interfaces accelerate development
Estimated Time: 25 minutes
The Resilient AI Assistant Architecture
Our assistant follows a layered architecture where each component has a specific responsibility:
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β CLI Interface βββββΆβ Assistant Core βββββΆβ OpenAI API β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Configuration β
β & Logging β
βββββββββββββββββββ
This architecture provides:
- Separation of concerns - UI, logic, and external services are independent
- Testability - Each layer can be tested in isolation
- Flexibility - Switch AI providers without changing the core logic
- Observability - Centralized logging and configuration management
Configuration Management: The Foundation of Flexibility
Why Configuration Matters
Configuration isn't just about storing API keysβit's about operational flexibility:
- Environment-specific behavior - Different settings for dev/staging/production
- Feature flags - Enable/disable features without code changes
- Performance tuning - Adjust rate limits and timeouts based on usage
- Personality customization - Different assistant personalities for different use cases
The Validation Strategy
Configuration validation prevents runtime surprises. Instead of discovering a misconfigured temperature setting during an important conversation, we catch it at startup.
Create src/config/assistant.js
import dotenv from 'dotenv';
dotenv.config();
export const config = {
// OpenAI Configuration
openai: {
apiKey: process.env.OPENAI_API_KEY,
model: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
maxTokens: parseInt(process.env.MAX_TOKENS) || 150,
temperature: parseFloat(process.env.TEMPERATURE) || 0.7,
timeout: parseInt(process.env.API_TIMEOUT) || 30000,
},
// Rate Limiting
rateLimiting: {
maxRequestsPerMinute: parseInt(process.env.MAX_REQUESTS_PER_MINUTE) || 20,
maxTokensPerMinute: parseInt(process.env.MAX_TOKENS_PER_MINUTE) || 40000,
},
// Retry Configuration
retry: {
maxAttempts: parseInt(process.env.MAX_RETRY_ATTEMPTS) || 3,
baseDelay: parseInt(process.env.RETRY_BASE_DELAY) || 1000,
maxDelay: parseInt(process.env.RETRY_MAX_DELAY) || 10000,
},
// Assistant Personality
personality: {
name: process.env.ASSISTANT_NAME || 'Alex',
role: process.env.ASSISTANT_ROLE || 'helpful assistant',
tone: process.env.ASSISTANT_TONE || 'friendly and professional',
},
// Logging
logging: {
level: process.env.LOG_LEVEL || 'info',
saveToFile: process.env.SAVE_LOGS === 'true',
},
};
// Validate required configuration
export function validateConfig() {
const errors = [];
if (!config.openai.apiKey) {
errors.push('OPENAI_API_KEY is required');
}
if (config.openai.temperature < 0 || config.openai.temperature > 2) {
errors.push('TEMPERATURE must be between 0 and 2');
}
if (config.openai.maxTokens < 1 || config.openai.maxTokens > 4096) {
errors.push('MAX_TOKENS must be between 1 and 4096');
}
if (errors.length > 0) {
throw new Error(`Configuration errors:\n${errors.join('\n')}`);
}
return true;
}
Enhanced .env
Configuration
# AI Model Configuration
OPENAI_MODEL=gpt-3.5-turbo
MAX_TOKENS=150
TEMPERATURE=0.7
API_TIMEOUT=30000
# Rate Limiting
MAX_REQUESTS_PER_MINUTE=20
MAX_TOKENS_PER_MINUTE=40000
# Retry Logic
MAX_RETRY_ATTEMPTS=3
RETRY_BASE_DELAY=1000
RETRY_MAX_DELAY=10000
# Assistant Personality
ASSISTANT_NAME=Alex
ASSISTANT_ROLE=helpful personal assistant
ASSISTANT_TONE=friendly and professional
# Logging
SAVE_LOGS=true
Building the Core Assistant: A Resilient AI Brain
The Assistant Class Philosophy
The Assistant class is more than just an API wrapperβit's the orchestration layer that brings together:
- State management - Conversation history and metadata
- Error resilience - Graceful failure handling and recovery
- Performance monitoring - Real-time statistics and analytics
- Resource management - Rate limiting and cost control
Why This Architecture Works
Each method in our Assistant class has a single responsibility:
getResponse()
- Orchestrates the entire conversation flowcallOpenAI()
- Handles pure API communicationbuildSystemPrompt()
- Manages AI personality and behaviorupdateStats()
- Tracks performance metrics
This separation makes the code testable, maintainable, and extensible.
Create src/services/Assistant.js
import OpenAI from 'openai';
import { config, validateConfig } from '../config/assistant.js';
import { createLogger } from '../utils/logger.js';
import { RateLimiter } from '../utils/rateLimiter.js';
import { RetryHandler } from '../utils/retryHandler.js';
export class Assistant {
constructor(options = {}) {
// Validate configuration
validateConfig();
// Initialize logger
this.logger = createLogger();
// Initialize OpenAI client
this.openai = new OpenAI({
apiKey: config.openai.apiKey,
timeout: config.openai.timeout,
});
// Initialize rate limiter
this.rateLimiter = new RateLimiter({
maxRequests: config.rateLimiting.maxRequestsPerMinute,
windowMs: 60000, // 1 minute
});
// Initialize retry handler
this.retryHandler = new RetryHandler({
maxAttempts: config.retry.maxAttempts,
baseDelay: config.retry.baseDelay,
maxDelay: config.retry.maxDelay,
});
// Assistant state
this.conversationHistory = [];
this.isInitialized = false;
this.stats = {
totalRequests: 0,
totalTokensUsed: 0,
averageResponseTime: 0,
errors: 0,
};
this.logger.info('π€ Assistant initialized successfully');
}
/**
* Initialize the assistant with system prompt
*/
async initialize() {
if (this.isInitialized) {
return;
}
const systemPrompt = this.buildSystemPrompt();
this.conversationHistory = [
{
role: 'system',
content: systemPrompt,
timestamp: new Date().toISOString(),
},
];
this.isInitialized = true;
this.logger.info('β
Assistant initialized with system prompt');
}
/**
* Generate response to user input
*/
async getResponse(userInput, options = {}) {
try {
// Ensure assistant is initialized
await this.initialize();
// Validate input
if (!userInput || typeof userInput !== 'string') {
throw new Error('User input must be a non-empty string');
}
if (userInput.trim().length === 0) {
throw new Error('User input cannot be empty');
}
// Check rate limits
await this.rateLimiter.checkLimit();
// Start timing
const startTime = Date.now();
// Add user message to history
const userMessage = {
role: 'user',
content: userInput.trim(),
timestamp: new Date().toISOString(),
};
this.conversationHistory.push(userMessage);
this.logger.info(`π€ User: ${userInput}`);
// Generate response with retry logic
const response = await this.retryHandler.execute(
() => this.callOpenAI(options)
);
// Add assistant response to history
const assistantMessage = {
role: 'assistant',
content: response.content,
timestamp: new Date().toISOString(),
metadata: {
model: response.model,
tokensUsed: response.usage?.total_tokens || 0,
responseTime: Date.now() - startTime,
},
};
this.conversationHistory.push(assistantMessage);
// Update statistics
this.updateStats(assistantMessage.metadata);
this.logger.info(`π€ ${config.personality.name}: ${response.content}`);
return {
content: response.content,
metadata: assistantMessage.metadata,
conversationId: this.generateConversationId(),
};
} catch (error) {
this.stats.errors++;
this.logger.error('Error generating response:', error);
// Return user-friendly error message
return {
content: "I'm sorry, I encountered an error while processing your request. Please try again.",
error: true,
errorType: error.name,
metadata: {
responseTime: 0,
tokensUsed: 0,
},
};
}
}
/**
* Call OpenAI API
*/
async callOpenAI(options = {}) {
const requestOptions = {
model: options.model || config.openai.model,
messages: this.conversationHistory,
max_tokens: options.maxTokens || config.openai.maxTokens,
temperature: options.temperature || config.openai.temperature,
...options,
};
this.logger.debug('Sending request to OpenAI:', {
model: requestOptions.model,
messageCount: requestOptions.messages.length,
maxTokens: requestOptions.max_tokens,
});
const completion = await this.openai.chat.completions.create(requestOptions);
const response = {
content: completion.choices[0].message.content,
model: completion.model,
usage: completion.usage,
};
this.logger.debug('Received response from OpenAI:', {
tokensUsed: response.usage?.total_tokens,
model: response.model,
});
return response;
}
/**
* Build system prompt based on configuration
*/
buildSystemPrompt() {
return `You are ${config.personality.name}, a ${config.personality.role}.
Your personality traits:
- Tone: ${config.personality.tone}
- Always be helpful and accurate
- If you don't know something, admit it
- Keep responses concise but informative
- Use emojis sparingly and appropriately
Current date and time: ${new Date().toLocaleString()}
Remember to stay in character and provide helpful, accurate responses.`;
}
/**
* Update statistics
*/
updateStats(metadata) {
this.stats.totalRequests++;
this.stats.totalTokensUsed += metadata.tokensUsed;
// Calculate running average response time
const currentAvg = this.stats.averageResponseTime;
const newAvg = (currentAvg * (this.stats.totalRequests - 1) + metadata.responseTime) / this.stats.totalRequests;
this.stats.averageResponseTime = Math.round(newAvg);
}
/**
* Generate conversation ID
*/
generateConversationId() {
return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Clear conversation history
*/
clearHistory() {
this.conversationHistory = [];
this.isInitialized = false;
this.logger.info('ποΈ Conversation history cleared');
}
/**
* Get conversation statistics
*/
getStats() {
return {
...this.stats,
conversationLength: this.conversationHistory.length,
isInitialized: this.isInitialized,
};
}
/**
* Get conversation history
*/
getHistory() {
return this.conversationHistory.filter(msg => msg.role !== 'system');
}
/**
* Export conversation for analysis
*/
exportConversation() {
return {
timestamp: new Date().toISOString(),
config: {
model: config.openai.model,
temperature: config.openai.temperature,
maxTokens: config.openai.maxTokens,
},
conversation: this.getHistory(),
stats: this.getStats(),
};
}
}
Resilience Utilities: Rate Limiting & Retry Logic
Why Rate Limiting Matters
Rate limiting isn't just about avoiding API bansβit's about cost management:
- Prevents runaway costs from accidental loops or spam
- Ensures fair resource usage in multi-user scenarios
- Provides predictable performance characteristics
- Enables graceful degradation under high load
The Exponential Backoff Strategy
Our retry handler uses exponential backoff because:
- Reduces server load during outages
- Maximizes success rate for transient failures
- Prevents thundering herd problems
- Respects service recovery time patterns
Create src/utils/rateLimiter.js
export class RateLimiter {
constructor(options = {}) {
this.maxRequests = options.maxRequests || 20;
this.windowMs = options.windowMs || 60000; // 1 minute
this.requests = [];
}
async checkLimit() {
const now = Date.now();
// Remove old requests outside the window
this.requests = this.requests.filter(
timestamp => now - timestamp < this.windowMs
);
// Check if we're at the limit
if (this.requests.length >= this.maxRequests) {
const oldestRequest = Math.min(...this.requests);
const waitTime = this.windowMs - (now - oldestRequest);
throw new Error(
`Rate limit exceeded. Please wait ${Math.ceil(waitTime / 1000)} seconds.`
);
}
// Add current request
this.requests.push(now);
}
getRemainingRequests() {
const now = Date.now();
const recentRequests = this.requests.filter(
timestamp => now - timestamp < this.windowMs
);
return Math.max(0, this.maxRequests - recentRequests.length);
}
getResetTime() {
if (this.requests.length === 0) {
return 0;
}
const oldestRequest = Math.min(...this.requests);
const resetTime = oldestRequest + this.windowMs;
return Math.max(0, resetTime - Date.now());
}
}
Create src/utils/retryHandler.js
export class RetryHandler {
constructor(options = {}) {
this.maxAttempts = options.maxAttempts || 3;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 10000;
}
async execute(fn, attempt = 1) {
try {
return await fn();
} catch (error) {
// Don't retry on certain errors
if (this.shouldNotRetry(error) || attempt >= this.maxAttempts) {
throw error;
}
// Calculate delay with exponential backoff
const delay = Math.min(
this.baseDelay * Math.pow(2, attempt - 1),
this.maxDelay
);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await this.sleep(delay);
return this.execute(fn, attempt + 1);
}
}
shouldNotRetry(error) {
// Don't retry on authentication errors or invalid requests
const nonRetryableErrors = [
'Authentication',
'Authorization',
'InvalidRequest',
'ValidationError',
];
return nonRetryableErrors.some(type =>
error.name?.includes(type) || error.message?.includes(type.toLowerCase())
);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Step 4: Create Interactive CLI Interface
Now let's create a user-friendly CLI interface to interact with our assistant.
Create src/cli/interactive.js
import readline from 'readline';
import chalk from 'chalk';
import { Assistant } from '../services/Assistant.js';
import { createLogger } from '../utils/logger.js';
export class InteractiveCLI {
constructor() {
this.assistant = new Assistant();
this.logger = createLogger();
this.rl = null;
this.isRunning = false;
}
async start() {
try {
// Initialize assistant
await this.assistant.initialize();
// Setup readline interface
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.blue('You: '),
});
// Display welcome message
this.displayWelcome();
// Setup event listeners
this.setupEventListeners();
// Start the conversation
this.isRunning = true;
this.rl.prompt();
} catch (error) {
console.error(chalk.red('Failed to start CLI:'), error.message);
process.exit(1);
}
}
setupEventListeners() {
this.rl.on('line', async (input) => {
await this.handleUserInput(input.trim());
});
this.rl.on('close', () => {
this.handleExit();
});
// Handle Ctrl+C
process.on('SIGINT', () => {
this.handleExit();
});
}
async handleUserInput(input) {
if (!input) {
this.rl.prompt();
return;
}
// Handle special commands
if (input.startsWith('/')) {
await this.handleCommand(input);
return;
}
try {
// Show thinking indicator
const thinkingInterval = this.showThinking();
// Get response from assistant
const response = await this.assistant.getResponse(input);
// Stop thinking indicator
clearInterval(thinkingInterval);
process.stdout.write('\r\x1b[K'); // Clear line
// Display response
if (response.error) {
console.log(chalk.red(`β Error: ${response.content}`));
} else {
console.log(chalk.green(`π€ Alex: ${response.content}`));
// Show metadata in debug mode
if (process.env.LOG_LEVEL === 'debug') {
console.log(chalk.gray(` β±οΈ ${response.metadata.responseTime}ms | π― ${response.metadata.tokensUsed} tokens`));
}
}
} catch (error) {
console.log(chalk.red(`β Error: ${error.message}`));
}
console.log(); // Add spacing
this.rl.prompt();
}
async handleCommand(command) {
const [cmd, ...args] = command.slice(1).split(' ');
switch (cmd.toLowerCase()) {
case 'help':
this.displayHelp();
break;
case 'stats':
this.displayStats();
break;
case 'history':
this.displayHistory();
break;
case 'clear':
this.assistant.clearHistory();
console.log(chalk.yellow('ποΈ Conversation history cleared'));
break;
case 'export':
this.exportConversation();
break;
case 'settings':
this.displaySettings();
break;
case 'quit':
case 'exit':
this.handleExit();
return;
default:
console.log(chalk.red(`Unknown command: ${cmd}`));
console.log(chalk.gray('Type /help for available commands'));
}
console.log();
this.rl.prompt();
}
showThinking() {
const frames = ['β ', 'β ', 'β Ή', 'β Έ', 'β Ό', 'β ΄', 'β ¦', 'β §', 'β ', 'β '];
let i = 0;
return setInterval(() => {
process.stdout.write(`\r${chalk.blue(frames[i % frames.length])} Thinking...`);
i++;
}, 100);
}
displayWelcome() {
console.log(chalk.cyan('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'));
console.log(chalk.cyan('β π€ Personal AI Assistant β'));
console.log(chalk.cyan('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'));
console.log();
console.log(chalk.green('Welcome! I\'m Alex, your personal AI assistant.'));
console.log(chalk.gray('Type your questions or use commands like /help, /stats, /quit'));
console.log(chalk.gray('Press Ctrl+C or type /quit to exit'));
console.log();
}
displayHelp() {
console.log(chalk.cyan('Available Commands:'));
console.log(chalk.yellow('/help ') + '- Show this help message');
console.log(chalk.yellow('/stats ') + '- Display conversation statistics');
console.log(chalk.yellow('/history ') + '- Show conversation history');
console.log(chalk.yellow('/clear ') + '- Clear conversation history');
console.log(chalk.yellow('/export ') + '- Export conversation to file');
console.log(chalk.yellow('/settings ') + '- Show current settings');
console.log(chalk.yellow('/quit ') + '- Exit the assistant');
}
displayStats() {
const stats = this.assistant.getStats();
console.log(chalk.cyan('π Conversation Statistics:'));
console.log(` π¬ Total requests: ${stats.totalRequests}`);
console.log(` π― Total tokens used: ${stats.totalTokensUsed}`);
console.log(` β±οΈ Average response time: ${stats.averageResponseTime}ms`);
console.log(` π Conversation length: ${stats.conversationLength} messages`);
console.log(` β Errors: ${stats.errors}`);
}
displayHistory() {
const history = this.assistant.getHistory();
if (history.length === 0) {
console.log(chalk.gray('No conversation history yet.'));
return;
}
console.log(chalk.cyan('π Conversation History:'));
history.forEach((message, index) => {
const role = message.role === 'user' ? 'π€ You' : 'π€ Alex';
const color = message.role === 'user' ? chalk.blue : chalk.green;
console.log(color(`${index + 1}. ${role}: ${message.content}`));
});
}
displaySettings() {
console.log(chalk.cyan('βοΈ Current Settings:'));
console.log(` π€ Model: ${process.env.OPENAI_MODEL || 'gpt-3.5-turbo'}`);
console.log(` π‘οΈ Temperature: ${process.env.TEMPERATURE || '0.7'}`);
console.log(` π Max tokens: ${process.env.MAX_TOKENS || '150'}`);
console.log(` π Assistant name: ${process.env.ASSISTANT_NAME || 'Alex'}`);
}
exportConversation() {
try {
const conversation = this.assistant.exportConversation();
const filename = `conversation_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
// In a real implementation, you'd save to file
console.log(chalk.green(`π Conversation exported to: ${filename}`));
console.log(chalk.gray('(Export functionality would save to file in production)'));
} catch (error) {
console.log(chalk.red(`β Export failed: ${error.message}`));
}
}
handleExit() {
if (this.isRunning) {
console.log(chalk.yellow('\nπ Thanks for chatting! Goodbye!'));
// Display final stats
const stats = this.assistant.getStats();
if (stats.totalRequests > 0) {
console.log(chalk.gray(`Final stats: ${stats.totalRequests} requests, ${stats.totalTokensUsed} tokens used`));
}
}
if (this.rl) {
this.rl.close();
}
this.isRunning = false;
process.exit(0);
}
}
Step 5: Install Additional Dependencies
We need a few more packages for our enhanced CLI:
npm install chalk
Step 6: Update Main Application File
Let's update our main file to use the new interactive CLI.
Update src/index.js
import dotenv from 'dotenv';
import { InteractiveCLI } from './cli/interactive.js';
import { createLogger } from './utils/logger.js';
// Load environment variables
dotenv.config();
// Initialize logger
const logger = createLogger();
async function main() {
try {
logger.info('π Starting Personal AI Assistant...');
// Create and start CLI
const cli = new InteractiveCLI();
await cli.start();
} catch (error) {
logger.error('π₯ Application failed to start:', error);
process.exit(1);
}
}
// Start the application
main();
Step 7: Create Tests
Let's add comprehensive tests for our Assistant class.
Create tests/Assistant.test.js
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
import { Assistant } from '../src/services/Assistant.js';
// Mock OpenAI
jest.mock('openai', () => {
return {
__esModule: true,
default: jest.fn().mockImplementation(() => ({
chat: {
completions: {
create: jest.fn().mockResolvedValue({
choices: [{ message: { content: 'Test response' } }],
model: 'gpt-3.5-turbo',
usage: { total_tokens: 10 }
})
}
}
}))
};
});
describe('Assistant', () => {
let assistant;
beforeEach(() => {
// Set required environment variables
process.env.OPENAI_API_KEY = 'test-key';
assistant = new Assistant();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Initialization', () => {
test('should initialize successfully with valid config', () => {
expect(assistant).toBeInstanceOf(Assistant);
expect(assistant.isInitialized).toBe(false);
});
test('should initialize conversation with system prompt', async () => {
await assistant.initialize();
expect(assistant.isInitialized).toBe(true);
expect(assistant.conversationHistory).toHaveLength(1);
expect(assistant.conversationHistory[0].role).toBe('system');
});
});
describe('Response Generation', () => {
test('should generate response for valid input', async () => {
const response = await assistant.getResponse('Hello');
expect(response).toHaveProperty('content');
expect(response).toHaveProperty('metadata');
expect(response.content).toBe('Test response');
expect(response.error).toBeUndefined();
});
test('should handle empty input', async () => {
const response = await assistant.getResponse('');
expect(response.error).toBe(true);
expect(response.content).toContain('error');
});
test('should handle non-string input', async () => {
const response = await assistant.getResponse(null);
expect(response.error).toBe(true);
expect(response.content).toContain('error');
});
});
describe('Statistics', () => {
test('should track conversation statistics', async () => {
await assistant.getResponse('Test message');
const stats = assistant.getStats();
expect(stats.totalRequests).toBe(1);
expect(stats.totalTokensUsed).toBe(10);
expect(stats.errors).toBe(0);
});
test('should track errors in statistics', async () => {
// Mock API failure
assistant.openai.chat.completions.create.mockRejectedValueOnce(
new Error('API Error')
);
await assistant.getResponse('Test message');
const stats = assistant.getStats();
expect(stats.errors).toBe(1);
});
});
describe('History Management', () => {
test('should maintain conversation history', async () => {
await assistant.getResponse('Hello');
await assistant.getResponse('How are you?');
const history = assistant.getHistory();
expect(history).toHaveLength(4); // 2 user + 2 assistant messages
expect(history[0].role).toBe('user');
expect(history[1].role).toBe('assistant');
});
test('should clear conversation history', async () => {
await assistant.getResponse('Test message');
assistant.clearHistory();
expect(assistant.getHistory()).toHaveLength(0);
expect(assistant.isInitialized).toBe(false);
});
});
describe('Configuration', () => {
test('should export conversation data', async () => {
await assistant.getResponse('Test message');
const exported = assistant.exportConversation();
expect(exported).toHaveProperty('timestamp');
expect(exported).toHaveProperty('config');
expect(exported).toHaveProperty('conversation');
expect(exported).toHaveProperty('stats');
});
});
});
Step 8: Testing Your Assistant
Let's test our assistant to make sure everything works correctly.
Run Your Tests
npm test
Expected output:
β Assistant βΊ Initialization βΊ should initialize successfully with valid config
β Assistant βΊ Response Generation βΊ should generate response for valid input
β All tests passed!
Test the Interactive CLI
npm run dev
You should see:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π€ Personal AI Assistant β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Welcome! I'm Alex, your personal AI assistant.
Type your questions or use commands like /help, /stats, /quit
Press Ctrl+C or type /quit to exit
You:
Try These Test Commands
-
Basic conversation:
You: Hello, what's your name? π€ Alex: Hello! My name is Alex, and I'm your personal AI assistant...
-
Check statistics:
You: /stats π Conversation Statistics: π¬ Total requests: 1 π― Total tokens used: 15 β±οΈ Average response time: 1250ms
-
View history:
You: /history π Conversation History: 1. π€ You: Hello, what's your name? 2. π€ Alex: Hello! My name is Alex...
Troubleshooting Common Issues
OpenAI API Errors
Problem: Error: Incorrect API key provided
Solution:
- Verify your API key in
.env
file - Ensure no extra spaces or quotes around the key
- Check that your OpenAI account has credits
Problem: Rate limit exceeded
Solution:
- Our rate limiter should handle this automatically
- If persistent, increase
RETRY_BASE_DELAY
in.env
Module Import Errors
Problem: Cannot use import statement outside a module
Solution:
- Ensure
"type": "module"
is in yourpackage.json
- Use
.js
extensions in import statements
Memory Issues
Problem: Application runs out of memory Solution:
- Clear conversation history regularly with
/clear
- Reduce
MAX_TOKENS
in.env
- Implement conversation trimming (covered in Part 3)
Performance Optimization Tips
1. Token Management
- Keep
MAX_TOKENS
reasonable (150-300 for chat) - Monitor token usage with
/stats
- Clear history when conversations get long
2. Response Time
- Use
gpt-3.5-turbo
for faster responses - Implement conversation context trimming
- Consider streaming responses for longer outputs
3. Cost Management
- Set up usage monitoring
- Use rate limiting effectively
- Implement conversation archiving
Security Best Practices
1. API Key Protection
- Never commit
.env
files to version control - Use environment variables in production
- Rotate API keys regularly
2. Input Validation
- Our Assistant class validates all inputs
- Consider adding content filtering
- Implement user authentication for production
3. Error Handling
- Log errors securely (no sensitive data)
- Provide user-friendly error messages
- Implement proper retry logic
What We've Built
Congratulations! π You now have a sophisticated AI assistant with:
- Core Assistant Class - Handles all AI interactions
- Configuration Management - Centralized settings
- Rate Limiting - Prevents API abuse
- Retry Logic - Handles temporary failures
- Interactive CLI - User-friendly interface
- Comprehensive Testing - Ensures reliability
- Error Handling - Graceful failure management
- Statistics Tracking - Monitor usage and performance
Key Features Implemented
β OpenAI Integration - Full GPT integration with error handling β Conversation Management - History tracking and export β Rate Limiting - Prevents API overuse β Retry Logic - Handles temporary API failures β Configuration - Flexible settings management β CLI Interface - Interactive user experience β Testing - Comprehensive test coverage β Logging - Detailed logging and debugging
Next Steps
In Part 3: Conversation Memory, we'll add:
- Persistent conversation storage
- Context window management
- Conversation summarization
- Advanced memory techniques
- Vector embeddings for semantic search
Quick Preview of Part 3
// Coming in Part 3
class ConversationMemory {
async saveConversation(conversation) {
// Save to database or file
}
async loadConversation(id) {
// Load from storage
}
async summarizeConversation(messages) {
// Create conversation summary
}
}
Additional Resources
Ready to continue? Proceed to Part 3: Conversation Memory β
Your assistant is now ready for real conversations! Test it out and get familiar with the interface before moving to the next part.
Ad Space
Recommended Tools & Resources
* This section contains affiliate links. We may earn a commission when you purchase through these links at no additional cost to you.
π Featured AI Books
OpenAI API
AI PlatformAccess GPT-4 and other powerful AI models for your agent development.
LangChain Plus
FrameworkAdvanced framework for building applications with large language models.
Pinecone Vector Database
DatabaseHigh-performance vector database for AI applications and semantic search.
AI Agent Development Course
EducationComplete course on building production-ready AI agents from scratch.
π‘ Pro Tip
Start with the free tiers of these tools to experiment, then upgrade as your AI agent projects grow. Most successful developers use a combination of 2-3 core tools rather than trying everything at once.
π Build a Personal AI Assistant
π Join the AgentForge Community
Get weekly insights, tutorials, and the latest AI agent developments delivered to your inbox.
No spam, ever. Unsubscribe at any time.