How to Use AI in GoGrab Space

Complete guide to using AI services anywhere in the monorepo

🤖 How to Use AI in GoGrab Space

This guide explains how to use AI services anywhere in the monorepo - whether in components, services, libraries, or apps.


⚡ Quick Start

1. Inject HttpClient and API_BASE_URL

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_BASE_URL } from '@libs-foundational/core-angular';

@Injectable({ providedIn: 'root' })
export class MyAIService {
  private readonly http = inject(HttpClient);
  private readonly apiBaseUrl = inject(API_BASE_URL, { optional: true }) ?? 'http://localhost:3000/api';

  async askAI(prompt: string): Promise<string> {
    const response = await this.http.post<{ text: string }>(
      `${this.apiBaseUrl}/ai/anthropic/messages`,
      {
        messages: [
          { role: 'user', content: prompt }
        ],
        maxTokens: 1000
      }
    ).toPromise();
    
    return response?.text ?? '';
  }
}

2. That's it!

The API_BASE_URL is already provided at app level, and the AuthInterceptor automatically adds the JWT token to all requests.


🏗️ Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        Your Component/Service                    │
│                                                                  │
│   const response = await http.post(`${apiBaseUrl}/ai/...`)      │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                        AuthInterceptor                           │
│                                                                  │
│   Automatically adds: Authorization: Bearer <jwt-token>          │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Backend API (port 3000)                      │
│                                                                  │
│   /api/ai/anthropic/messages  → Claude (Anthropic)               │
│   /api/ai/openai/chat         → GPT-4 (OpenAI)                   │
│   /api/ai/google/generate     → Gemini (Google)                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                     AI Provider APIs                             │
│                                                                  │
│   API keys stored securely in backend .env.local                 │
└─────────────────────────────────────────────────────────────────┘

📡 Available AI Endpoints

All endpoints require authentication (JWT token auto-added by interceptor).

Anthropic (Claude) - Recommended for reasoning tasks

POST ${apiBaseUrl}/ai/anthropic/messages
{
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'What is 2+2?' }
  ],
  temperature?: 0.7,      // 0-1, lower = more deterministic
  maxTokens?: 1000        // Max response length
}

Response:

{
  text: "2+2 equals 4.",
  usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15 },
  model: "claude-3-5-sonnet-20241022",
  provider: "anthropic",
  cost: 0.0001,
  requestId: "uuid"
}

OpenAI (GPT-4) - Great for code generation

POST ${apiBaseUrl}/ai/openai/chat

Same request/response format as Anthropic.

Google (Gemini) - Good for multimodal tasks

POST ${apiBaseUrl}/ai/google/generate

Same request/response format as Anthropic.


🧩 Reusable AI Service Pattern

Create a reusable AI service in your library:

// libs/your-lib/src/lib/services/your-ai.service.ts

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, catchError, map, of, timeout } from 'rxjs';
import { API_BASE_URL } from '@libs-foundational/core-angular';
import { LoggerService } from '@libs-core/log-system';

export interface AIResponse<T> {
  data: T;
  source: 'ai' | 'fallback';
  processingTime: number;
}

@Injectable({ providedIn: 'root' })
export class YourAIService {
  private readonly http = inject(HttpClient);
  private readonly logger = inject(LoggerService);
  private readonly apiBaseUrl = inject(API_BASE_URL, { optional: true }) ?? 'http://localhost:3000/api';
  
  private readonly AI_TIMEOUT = 15000; // 15 seconds

  /**
   * Ask AI with automatic fallback
   */
  ask<T>(
    prompt: string,
    systemPrompt?: string,
    fallbackFn?: () => T
  ): Observable<AIResponse<T>> {
    const startTime = Date.now();
    
    return this.http.post<{ text: string }>(
      `${this.apiBaseUrl}/ai/anthropic/messages`,
      {
        messages: [
          ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
          { role: 'user', content: prompt }
        ],
        temperature: 0.3,
        maxTokens: 2000
      }
    ).pipe(
      timeout(this.AI_TIMEOUT),
      map(response => ({
        data: this.parseResponse<T>(response.text),
        source: 'ai' as const,
        processingTime: Date.now() - startTime
      })),
      catchError(error => {
        this.logger.warn('AI request failed, using fallback', { error: error.message });
        return of({
          data: fallbackFn ? fallbackFn() : (null as T),
          source: 'fallback' as const,
          processingTime: Date.now() - startTime
        });
      })
    );
  }

  private parseResponse<T>(text: string): T {
    // Try to parse as JSON if it looks like JSON
    const jsonMatch = text.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
    if (jsonMatch) {
      try {
        return JSON.parse(jsonMatch[0]) as T;
      } catch {
        // Not valid JSON, return as string
      }
    }
    return text as unknown as T;
  }
}

Usage in Components

@Component({ ... })
export class MyComponent {
  private readonly aiService = inject(YourAIService);

  analyzeText(text: string) {
    this.aiService.ask<{ sentiment: string; confidence: number }>(
      `Analyze the sentiment of: "${text}"`,
      'You are a sentiment analyzer. Return JSON: { "sentiment": "positive|negative|neutral", "confidence": 0-1 }',
      () => ({ sentiment: 'neutral', confidence: 0 }) // Fallback
    ).subscribe(result => {
      console.log('Sentiment:', result.data.sentiment);
      console.log('Source:', result.source); // 'ai' or 'fallback'
    });
  }
}

🔧 Configuration

Environment Setup

The API_BASE_URL is configured in apps/gograb-space/src/environments/:

// environment.ts (development)
import { BACKEND_URLS } from './environment.shared';

export const environment = {
  backendApiUrl: BACKEND_URLS.local,  // http://localhost:3000/api
  // ...
};

// environment.shared.ts
export const BACKEND_URLS = {
  railway: 'https://backend-api-production-97c0.up.railway.app/api',
  local: 'http://localhost:3000/api',
  mock: '',
};

App Module Provider

The token is provided at app level in app.config.ts:

import { API_BASE_URL } from '@libs-foundational/core-angular';
import { environment } from '../environments/environment';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: API_BASE_URL, useValue: environment.backendApiUrl },
    // ... other providers
  ]
};

Backend API Keys

API keys are stored securely in apis/backend-api/.env.local:

OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=AI...

⚠️ NEVER commit API keys to git! The .env.local file is gitignored.


🎯 Best Practices

1. Always Use Fallbacks

// ✅ Good - has fallback
this.aiService.ask(prompt, systemPrompt, () => defaultValue)

// ❌ Bad - no fallback, will fail if AI unavailable
this.aiService.ask(prompt, systemPrompt)

2. Set Appropriate Timeouts

// For quick operations (entity extraction, classification)
private readonly AI_TIMEOUT = 10000; // 10s

// For complex generation (code, long text)
private readonly AI_TIMEOUT = 30000; // 30s

3. Use System Prompts for Structured Output

const systemPrompt = `You are a JSON generator. 
Always respond with valid JSON only, no markdown.
Schema: { "field1": "string", "field2": number }`;

4. Handle Errors Gracefully

.pipe(
  catchError(error => {
    // Log the error
    this.logger.warn('AI failed', { error: error.message });
    
    // Return fallback value
    return of({ data: fallbackValue, source: 'fallback' });
  })
)

5. Cache AI Responses When Appropriate

import { autoFlashState } from '@storage-system';

// Cache AI responses for repeated queries
private readonly aiCache = autoFlashState<Record<string, any>>('ai-cache', {}, {
  database: 'ai-system',
  collection: 'cache',
  ttl: 60 * 60 * 1000 // 1 hour
});

📚 Existing AI Services

The monorepo has several AI services you can use or reference:

Service Location Purpose
EntityExtractionService @libs-structural/modularity-lib Extract data entities from descriptions
IntelligenceCoordinatorService @libs-ai/gograb-ai-lib Complex AI orchestration
AIOrchestratorService @libs-ai/gograb-ai-lib AI provider selection
AIFactoryService @libs-ai/gograb-ai-lib AI service factory

Using EntityExtractionService

import { EntityExtractionService } from '@libs-structural/modularity-lib';

@Component({ ... })
export class MyComponent {
  private readonly entityExtractor = inject(EntityExtractionService);

  extractEntities(description: string) {
    this.entityExtractor.extractEntities(description).subscribe(result => {
      console.log('Entities:', result.entities);
      console.log('Source:', result.source); // 'ai' or 'local'
    });
  }
}

🚀 Quick Copy-Paste Templates

Template 1: Simple AI Call

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_BASE_URL } from '@libs-foundational/core-angular';
import { firstValueFrom } from 'rxjs';

// In your component/service
private readonly http = inject(HttpClient);
private readonly apiBaseUrl = inject(API_BASE_URL, { optional: true }) ?? 'http://localhost:3000/api';

async askAI(prompt: string): Promise<string> {
  try {
    const response = await firstValueFrom(
      this.http.post<{ text: string }>(`${this.apiBaseUrl}/ai/anthropic/messages`, {
        messages: [{ role: 'user', content: prompt }],
        maxTokens: 1000
      })
    );
    return response.text;
  } catch (error) {
    console.error('AI failed:', error);
    return 'AI unavailable';
  }
}

Template 2: Observable-Based with Fallback

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_BASE_URL } from '@libs-foundational/core-angular';
import { Observable, of, catchError, map, timeout } from 'rxjs';

private readonly http = inject(HttpClient);
private readonly apiBaseUrl = inject(API_BASE_URL, { optional: true }) ?? 'http://localhost:3000/api';

analyzeWithAI(text: string): Observable<{ result: string; isAI: boolean }> {
  return this.http.post<{ text: string }>(`${this.apiBaseUrl}/ai/anthropic/messages`, {
    messages: [
      { role: 'system', content: 'Analyze the following text and provide insights.' },
      { role: 'user', content: text }
    ],
    maxTokens: 1000
  }).pipe(
    timeout(15000),
    map(response => ({ result: response.text, isAI: true })),
    catchError(() => of({ result: 'Analysis unavailable', isAI: false }))
  );
}

🔍 Debugging AI Requests

Check Browser Console

Look for these logs:

🔐 Auth Interceptor: Added JWT token to request: http://localhost:3000/api/ai/anthropic/messages
[INFO] Entity extraction complete { source: "ai", entityCount: 2, processingTime: 11591 }

Check Network Tab

  1. Open DevTools → Network
  2. Filter by "ai"
  3. Check request/response payloads
  4. Verify JWT token in Authorization header

Check Backend Logs

# In terminal running backend-api
[AIService] Processing Anthropic request for user xxx with model claude-3-5-sonnet-20241022
[AIService] Anthropic request completed. Tokens: 150, Cost: $0.0045

❓ Troubleshooting

"404 Not Found"

"401 Unauthorized"

"AI unavailable" / Timeout

"Rate limited"


📊 Cost Tracking

The backend automatically tracks AI usage per user:

// Get user's AI usage
GET /api/ai/usage?days=30

// Get usage by provider
GET /api/ai/usage/providers?days=30

Last Updated: December 3, 2025 | Session: 4