Infrastructure

Local LLM Setup

Run AI models locally with Ollama for zero-cost inference, with automatic fallback to Claude API when needed.

Ollama Installation

Ollama runs LLMs locally on your Mac Mini. Install it and pull the recommended models:

# Install Ollama
brew install ollama

# Start Ollama service
ollama serve

# Pull recommended models
ollama pull mistral:7b    # Intent classification (~4GB)
ollama pull llama3:8b      # Text generation (~4.7GB)

# Verify installation
ollama list

Health Check

# Check Ollama health
curl http://localhost:11434/api/tags

# Expected response:
# {
#   "models": [
#     { "name": "mistral:7b", "size": 4109865472 },
#     { "name": "llama3:8b", "size": 4661211648 }
#   ]
# }

# Test a generation
curl http://localhost:11434/api/generate -d '{
  "model": "mistral:7b",
  "prompt": "Classify this message intent: I want to create a 20% off deal",
  "stream": false
}'

Claude API Setup

Get your API key from console.anthropic.com and add it to your environment. Claude serves as the fallback LLM when Ollama is unavailable.

# Get your API key from https://console.anthropic.com
# Add to your .env file:
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here

# The system uses Claude as a fallback when Ollama is unavailable
# or for complex tasks that require stronger reasoning.
# Recommended model: claude-sonnet-4-20250514

Auto-Fallback System

If Ollama fails or is unavailable, the system automatically falls back to Claude API. This ensures your agents are always responsive, even during Ollama maintenance or high load.

// Auto-fallback: Ollama → Claude API
const generateResponse = async (prompt, options = {}) => {
  // Try Ollama first (free, local)
  try {
    const ollamaHealth = await fetch('http://localhost:11434/api/tags');
    if (ollamaHealth.ok) {
      const response = await fetch('http://localhost:11434/api/generate', {
        method: 'POST',
        body: JSON.stringify({
          model: options.model || 'mistral:7b',
          prompt,
          stream: false
        })
      });
      if (response.ok) {
        const data = await response.json();
        return { text: data.response, provider: 'ollama' };
      }
    }
  } catch (err) {
    console.warn('Ollama unavailable, falling back to Claude API');
  }

  // Fallback to Claude API
  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': process.env.ANTHROPIC_API_KEY,
      'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 1024,
      messages: [{ role: 'user', content: prompt }]
    })
  });
  const data = await response.json();
  return { text: data.content[0].text, provider: 'claude' };
};

Intent Classification System Prompt

The INTENT_SYSTEM_PROMPT is used to classify incoming messages into actionable intents. This is the core of the messaging-to-action pipeline:

// INTENT_SYSTEM_PROMPT used for message classification
const INTENT_SYSTEM_PROMPT = `You are an intent classifier for a commerce platform.
Classify the user's message into one of these intents:

INTENTS:
- create_deal: User wants to create a discount or promotion
- check_inventory: User wants to check stock levels
- update_price: User wants to change a product price
- view_analytics: User wants to see sales/performance data
- manage_agent: User wants to configure AI agent settings
- approve_action: User wants to approve a pending agent action
- reject_action: User wants to reject a pending agent action
- general_query: General question about the platform
- unknown: Cannot determine intent

Respond with ONLY the intent name, nothing else.`;

// Usage with Ollama
const classifyIntent = async (message) => {
  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    body: JSON.stringify({
      model: 'mistral:7b',
      prompt: `${INTENT_SYSTEM_PROMPT}\n\nUser: ${message}\nIntent:`,
      stream: false
    })
  });
  const data = await response.json();
  return data.response.trim().toLowerCase();
};

Model Selection Guide

ModelSizeUse CaseSpeed
mistral:7b
Recommended
~4 GBIntent classification, quick responsesFast (~50 tok/s on M2)
llama3:8b
Recommended
~4.7 GBText generation, complex reasoningMedium (~35 tok/s on M2)
llama3:70b~40 GBAdvanced reasoning (requires 64GB+ RAM)Slow (~5 tok/s on M4 Pro)
phi3:3.8b~2.3 GBLightweight tasks, low-memory devicesVery fast (~80 tok/s on M2)

Cost Comparison

ProviderMonthly CostLatencyPrivacyLimits
Ollama (Local)$0~100-300msFull (on-device)Hardware only
Claude API~$5-15~500-2000msAnthropic serversRate limits apply

Health Check Endpoint

Monitor the LLM subsystem health via the dedicated health endpoint:

// LLM Health Check endpoint
// GET /v1/messaging/llm/health

curl http://localhost:3000/v1/messaging/llm/health

// Response:
{
  "status": "ok",
  "ollama": {
    "status": "connected",
    "url": "http://localhost:11434",
    "models": ["mistral:7b", "llama3:8b"],
    "gpu_available": true
  },
  "claude": {
    "status": "configured",
    "model": "claude-sonnet-4-20250514",
    "api_key_set": true
  },
  "active_provider": "ollama",
  "fallback_enabled": true
}

What's Next?