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
- Open DevTools → Network
- Filter by "ai"
- Check request/response payloads
- 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"
- ✅ Ensure backend-api is running:
yarn serve:backend - ✅ Check API_BASE_URL is correctly injected
- ✅ Verify endpoint path:
/ai/anthropic/messages(not/api/ai/...)
"401 Unauthorized"
- ✅ Ensure user is logged in
- ✅ Check AuthInterceptor is adding token
- ✅ Verify JWT is not expired
"AI unavailable" / Timeout
- ✅ Check backend has valid API keys in
.env.local - ✅ Increase timeout for complex prompts
- ✅ Check Anthropic/OpenAI service status
"Rate limited"
- ✅ Implement request caching
- ✅ Add debounce to user inputs
- ✅ Consider upgrading API tier
📊 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