Chat-Based AI Agent with Memory

Chat-Based AI Agent with Memory

Versatile conversational AI assistant built with Node.js and OpenAI, featuring persistent memory, tool integration, and advanced sentiment analysis capabilities.

Node.jsTypeScriptOpenAI APIDALL-EReddit APIJSON DatabaseSentiment AnalysisCLI
#ai/ml #node.js #typescript #openai api #dall-e #reddit api #json database #sentiment analysis #cli

Project Overview

A sophisticated chat-based AI assistant that combines conversational AI with tool integration and persistent memory. Built with Node.js and TypeScript, the agent uses OpenAI's GPT models to understand user requests and intelligently decide when to use specialized tools. Features include dad joke generation with optional AI-generated images, Reddit integration for fetching latest posts, inspirational quote generation, basic and advanced sentiment analysis, and a local JSON database for conversation persistence. The agent maintains context across sessions, remembering previous interactions and building upon past conversations.

AI Agent Capabilities & Memory System

Comprehensive overview of the AI agent's conversational abilities and persistent memory features.

Conversational Memory
Local Database Storage

Conversational Memory: The agent maintains conversation history and context, demonstrating how it remembers previous interactions and builds upon them for meaningful dialogue.

Entertainment & Content Generation

Dad jokes, quotes, and image generation capabilities showcasing the agent's creative tools.

Dad Joke Generation
AI Image Generation
Inspirational Quotes

Dad Joke Generation: The agent can generate dad jokes on command, demonstrating simple entertainment functionality with consistent formatting.

Advanced Analysis & Data Tools

Sentiment analysis and Reddit integration showing the agent's analytical and data retrieval capabilities.

Basic Sentiment Analysis
Advanced Sentiment Trends
Reddit Integration

Basic Sentiment Analysis: Simple sentiment classification returning positive, negative, or neutral assessments for user-provided text.

Project Architecture & Structure

Modular design with clear separation of concerns for agent logic, memory, and tool execution.

chat-based-ai-agent/
├── src/
│   ├── index.ts           # CLI entry point & argument handling
│   ├── agent.ts           # Main agent orchestration & LLM calls
│   ├── memory.ts          # Conversation persistence to db.json
│   ├── toolRunner.ts      # Tool execution & coordination
│   └── tools/
│       ├── dadJoke.ts     # Dad joke generation
│       ├── reddit.ts      # Reddit API integration
│       ├── quotes.ts      # Quote generation
│       ├── imageGen.ts    # DALL-E image generation
│       └── sentiment.ts   # Sentiment analysis tools
├── db.json               # Local conversation database
├── package.json
└── .env                 # API keys configuration

Core Agent Logic & Tool Integration

Main agent orchestration handling conversation flow and intelligent tool selection.

// agent.ts - Main agent orchestration
import { OpenAI } from 'openai';
import { loadMemory, saveInteraction } from './memory';
import { runTool } from './toolRunner';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

export async function runAgent(userPrompt: string): Promise<string> {
  try {
    // Load conversation history
    const memory = loadMemory();
    
    // Add user message to conversation
    const messages = [
      ...memory.messages,
      { role: 'user', content: userPrompt }
    ];

    // Define available tools for the agent
    const tools = [
      {
        type: 'function',
        function: {
          name: 'dad_joke',
          description: 'Generate a dad joke, optionally with an image',
          parameters: {
            type: 'object',
            properties: {
              includeImage: { type: 'boolean', description: 'Whether to generate an accompanying image' }
            }
          }
        }
      },
      {
        type: 'function', 
        function: {
          name: 'sentiment_analysis',
          description: 'Analyze sentiment of text (simple) or trends (advanced)',
          parameters: {
            type: 'object',
            properties: {
              text: { type: 'string', description: 'Text to analyze' },
              type: { type: 'string', enum: ['simple', 'trends'], description: 'Analysis type' }
            },
            required: ['text']
          }
        }
      },
      {
        type: 'function',
        function: {
          name: 'reddit',
          description: 'Fetch latest Reddit front page posts',
          parameters: { type: 'object', properties: {} }
        }
      },
      {
        type: 'function',
        function: {
          name: 'generate_quote',
          description: 'Generate an inspirational quote',
          parameters: { type: 'object', properties: {} }
        }
      },
      {
        type: 'function',
        function: {
          name: 'generate_image',
          description: 'Generate an image using DALL-E style prompts',
          parameters: {
            type: 'object',
            properties: {
              prompt: { type: 'string', description: 'Image generation prompt' }
            },
            required: ['prompt']
          }
        }
      }
    ];

    // Get LLM response with tool capabilities
    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: messages,
      tools: tools,
      tool_choice: 'auto'
    });

    const assistantMessage = response.choices[0].message;
    
    // Handle tool calls if present
    if (assistantMessage.tool_calls) {
      const toolResults = [];
      
      for (const toolCall of assistantMessage.tool_calls) {
        console.log(`🔧 Executing: ${toolCall.function.name}`);
        
        const result = await runTool(
          toolCall.function.name,
          JSON.parse(toolCall.function.arguments || '{}')
        );
        
        toolResults.push({
          tool_call_id: toolCall.id,
          role: 'tool',
          content: result
        });
      }
      
      // Save tool interactions to memory
      saveInteraction(userPrompt, assistantMessage, toolResults);
      
      // Get final response after tool execution
      const finalMessages = [
        ...messages,
        assistantMessage,
        ...toolResults
      ];
      
      const finalResponse = await openai.chat.completions.create({
        model: 'gpt-4',
        messages: finalMessages
      });
      
      return finalResponse.choices[0].message.content || 'No response generated.';
    }
    
    // Save simple conversation to memory
    saveInteraction(userPrompt, assistantMessage);
    
    return assistantMessage.content || 'No response generated.';
    
  } catch (error) {
    console.error('Agent error:', error);
    return 'Sorry, I encountered an error processing your request.';
  }
}

Conversation Memory & Persistence

Local JSON database for maintaining conversation history and context across sessions.

// memory.ts - Conversation persistence system
import fs from 'fs';
import path from 'path';

interface Message {
  role: 'user' | 'assistant' | 'tool';
  content: string;
  tool_calls?: any[];
  tool_call_id?: string;
}

interface Memory {
  messages: Message[];
  conversations: {
    id: string;
    timestamp: string;
    user_message: string;
    assistant_response: string;
    tool_calls?: any[];
  }[];
}

const DB_PATH = path.join(__dirname, '../db.json');

export function loadMemory(): Memory {
  try {
    if (fs.existsSync(DB_PATH)) {
      const data = fs.readFileSync(DB_PATH, 'utf-8');
      return JSON.parse(data);
    }
  } catch (error) {
    console.error('Error loading memory:', error);
  }
  
  // Return default memory structure
  return {
    messages: [],
    conversations: []
  };
}

export function saveInteraction(
  userPrompt: string, 
  assistantMessage: any, 
  toolResults?: any[]
): void {
  try {
    const memory = loadMemory();
    
    // Add to message history for context
    memory.messages.push(
      { role: 'user', content: userPrompt },
      { 
        role: 'assistant', 
        content: assistantMessage.content || '',
        tool_calls: assistantMessage.tool_calls 
      }
    );
    
    // Add tool results if present
    if (toolResults) {
      memory.messages.push(...toolResults);
    }
    
    // Create conversation record
    const conversation = {
      id: generateId(),
      timestamp: new Date().toISOString(),
      user_message: userPrompt,
      assistant_response: assistantMessage.content || '',
      tool_calls: assistantMessage.tool_calls || undefined
    };
    
    memory.conversations.push(conversation);
    
    // Maintain reasonable memory size (keep last 50 interactions)
    if (memory.messages.length > 100) {
      memory.messages = memory.messages.slice(-100);
    }
    
    if (memory.conversations.length > 50) {
      memory.conversations = memory.conversations.slice(-50);
    }
    
    // Save to file
    fs.writeFileSync(DB_PATH, JSON.stringify(memory, null, 2));
    
  } catch (error) {
    console.error('Error saving interaction:', error);
  }
}

function generateId(): string {
  return Math.random().toString(36).substring(2) + Date.now().toString(36);
}

export function clearMemory(): void {
  try {
    const emptyMemory: Memory = {
      messages: [],
      conversations: []
    };
    fs.writeFileSync(DB_PATH, JSON.stringify(emptyMemory, null, 2));
    console.log('Memory cleared successfully.');
  } catch (error) {
    console.error('Error clearing memory:', error);
  }
}

Tool Execution System

Centralized tool coordination handling various AI capabilities and external API integrations.

// toolRunner.ts - Tool execution coordination
import { dadJoke, dadJokeWithImage } from './tools/dadJoke';
import { getRedditPosts } from './tools/reddit';
import { generateQuote } from './tools/quotes';
import { generateImage } from './tools/imageGen';
import { analyzeSentiment, analyzeSentimentTrends } from './tools/sentiment';

export async function runTool(toolName: string, args: any): Promise<string> {
  try {
    switch (toolName) {
      case 'dad_joke':
        if (args.includeImage) {
          return await dadJokeWithImage();
        }
        return await dadJoke();
        
      case 'sentiment_analysis':
        if (args.type === 'trends') {
          return await analyzeSentimentTrends(args.text);
        }
        return await analyzeSentiment(args.text);
        
      case 'reddit':
        return await getRedditPosts();
        
      case 'generate_quote':
        return await generateQuote();
        
      case 'generate_image':
        return await generateImage(args.prompt);
        
      default:
        return `Unknown tool: ${toolName}`;
    }
  } catch (error) {
    console.error(`Error executing tool ${toolName}:`, error);
    return `Error executing ${toolName}: ${error.message}`;
  }
}

// Advanced sentiment analysis with trends
async function analyzeSentimentTrends(text: string): Promise<string> {
  const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
  const analysis = [];
  
  for (const sentence of sentences) {
    const sentiment = await analyzeSentiment(sentence.trim());
    const confidence = calculateConfidence(sentence);
    
    analysis.push({
      sentence: sentence.trim(),
      sentiment,
      confidence: `${confidence}%`
    });
  }
  
  const overall = determineOverallSentiment(analysis);
  
  return `Sentiment Trend Analysis:
Overall Sentiment: ${overall.sentiment}
Overall Confidence: ${overall.confidence}%

Breakdown by Sentence:
${analysis.map((item, i) => 
  `${i + 1}. "${item.sentence}"
     - Sentiment: ${item.sentiment}
     - Confidence: ${item.confidence}`
).join('\n\n')}

Analysis Summary: ${generateSummary(analysis, overall)}`;
}

function calculateConfidence(text: string): number {
  // Simple confidence calculation based on text characteristics
  const positiveWords = ['love', 'great', 'wonderful', 'amazing', 'excellent', 'good'];
  const negativeWords = ['hate', 'bad', 'terrible', 'awful', 'horrible', 'worst'];
  
  const words = text.toLowerCase().split(/\s+/);
  const strongWords = words.filter(word => 
    positiveWords.includes(word) || negativeWords.includes(word)
  ).length;
  
  const baseConfidence = Math.min(90, 60 + (strongWords * 10));
  return Math.max(30, baseConfidence);
}

function determineOverallSentiment(analysis: any[]): { sentiment: string, confidence: number } {
  const sentiments = analysis.map(a => a.sentiment);
  const positive = sentiments.filter(s => s === 'Positive').length;
  const negative = sentiments.filter(s => s === 'Negative').length;
  const neutral = sentiments.filter(s => s === 'Neutral').length;
  
  let overall;
  if (positive > negative && positive > neutral) {
    overall = 'Positive';
  } else if (negative > positive && negative > neutral) {
    overall = 'Negative'; 
  } else {
    overall = 'Neutral';
  }
  
  const total = analysis.length;
  const confidence = Math.round((Math.max(positive, negative, neutral) / total) * 100);
  
  return { sentiment: overall, confidence };
}

Usage Examples & CLI Commands

Command-line interface examples showing various agent capabilities and interactions.

# Basic Installation & Setup
npm install
npm install openai

# Set up environment variables
echo "OPENAI_API_KEY=your_api_key_here" > .env

# Entertainment Commands
npm start "Give me a dad joke"
npm start "Give me a dad joke with an image"
npm start "Give me an inspiring quote"

# Analysis Commands  
npm start "What's the sentiment of: I love programming!"
npm start "Analyze sentiment trends in this text: I had a good day. Weather was bad. Dinner was amazing!"

# Data Retrieval Commands
npm start "Show me the latest Reddit posts"
npm start "Generate an image of a cat writing code"

# Memory & Context Commands
npm start "What did we talk about earlier?"
npm start "Remember my favorite programming language is Python"
npm start "What's my favorite language?" # Agent will remember

# Advanced Analysis Examples
npm start "Analyze the sentiment trends in this review: The product started great, but quality declined. Customer service was unhelpful. However, the final resolution was satisfactory."

# Tool Combination Examples  
npm start "Give me a dad joke and then analyze its sentiment"
npm start "Show me Reddit posts and generate a quote about social media"

Database Schema & Memory Structure

Local JSON database structure for conversation persistence and context management.

{
  "messages": [
    {
      "role": "user",
      "content": "Give me a dad joke"
    },
    {
      "role": "assistant", 
      "content": "",
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "dad_joke",
            "arguments": "{}"
          }
        }
      ]
    },
    {
      "tool_call_id": "call_abc123",
      "role": "tool",
      "content": "Here's a dad joke for you: Why don't scientists trust atoms? Because they make up everything!"
    },
    {
      "role": "assistant",
      "content": "Here's a dad joke for you:\n\nWhy don't scientists trust atoms? Because they make up everything!\n\nI hope that brought a smile to your face!"
    }
  ],
  "conversations": [
    {
      "id": "1e50e657-2829-48ce-a18b-f6d3940b7275",
      "timestamp": "2025-01-14T20:03:15.468Z",
      "user_message": "Give me a dad joke",
      "assistant_response": "Here's a dad joke for you: Why don't scientists trust atoms? Because they make up everything!",
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function", 
          "function": {
            "name": "dad_joke",
            "arguments": "{}"
          }
        }
      ]
    }
  ]
}

Challenges

Implementing effective conversation memory management while maintaining performance with growing conversation histories. Designing a flexible tool system that allows the AI to intelligently choose when and how to use different capabilities. Handling asynchronous tool calls and API integrations while maintaining conversational flow. Managing rate limits and error handling across multiple external APIs (OpenAI, Reddit, DALL-E). Creating a robust sentiment analysis system that can handle both simple sentiment classification and complex trend analysis across large texts. Ensuring data persistence and conversation continuity across application restarts.

Solutions

Developed a modular architecture with separate concerns for memory management, tool execution, and agent orchestration. Implemented a JSON-based local database (db.json) for lightweight conversation persistence without external database dependencies. Created a flexible tool runner system that allows easy addition of new capabilities while maintaining consistent interfaces. Built comprehensive error handling and fallback mechanisms for API failures. Designed advanced sentiment analysis that processes text sentence-by-sentence to identify emotional trends and confidence levels. Implemented intelligent conversation context management that maintains relevant history while preventing token limit issues.

Results

Successfully created a fully functional AI assistant capable of handling diverse user requests through natural conversation. The agent demonstrates intelligent tool selection, choosing appropriate capabilities based on user intent rather than requiring specific commands. Persistent memory allows for meaningful multi-turn conversations and context retention. Advanced sentiment analysis provides both simple classification and detailed trend analysis with confidence scoring. The modular design enables easy extension with new tools and capabilities. Users can interact naturally through CLI commands, with the agent maintaining conversation history and building contextual understanding over time.

Technologies Used

Node.jsTypeScriptOpenAI APIDALL-EReddit APIJSON DatabaseSentiment AnalysisCLI