DocumentationReference

HazelJS AI Package

npm downloads

@hazeljs/ai provides LLM integration for HazelJS applications. The HazelJS AI package supports OpenAI, Anthropic, Gemini, Cohere, and Ollama through a unified API. @hazeljs/ai provides completions, streaming, embeddings, function calling, context management, token tracking, multi-provider fallback, and AI-powered validation.

Quick Reference

  • Purpose: @hazeljs/ai provides LLM integration for HazelJS applications with a provider-agnostic API for completions, streaming, embeddings, and AI-powered validation.
  • When to use: Use @hazeljs/ai when a HazelJS application needs to call LLMs (OpenAI, Anthropic, Gemini, Cohere, Ollama) for text generation, chat, embeddings, or AI-powered validation. Use @hazeljs/agent instead when building stateful agents with tools and memory.
  • Key concepts: AIModule, AIEnhancedService (fluent chat builder), HCEL (Fluent DSL), AssistantFacade (Conversational Assistants), AIService (Deprecated), AI Providers (OpenAI, Anthropic, Gemini, Cohere, Ollama), @AITask decorator, @AIFunction decorator, @AIPrompt decorator, @AIValidate decorator, @Trace decorator (Observability), Prompt Registry, AIContextManager, TokenTracker, VectorService.
  • Inputs: User messages, system prompts, prompt templates with {{variable}} placeholders, DTO properties for AI validation.
  • Outputs: Text completions (string), streaming chunks (async generator), embeddings (number arrays), validation results (boolean).
  • Dependencies: @hazeljs/core, an AI provider API key (e.g., OPENAI_API_KEY).
  • Common patterns: Inject AIEnhancedService → build chat with .chat(message).system(prompt).model(name).text(); Use @AITask decorator for declarative AI methods; Use AIContextManager for multi-turn conversation windows; Use TokenTracker for cost monitoring.
  • Common mistakes: Forgetting to register AIModule in the module's imports array; using deprecated AIService instead of AIEnhancedService; not handling AIError with try-catch; exceeding token limits without using AIContextManager; not setting OPENAI_API_KEY or equivalent environment variable.

Architecture Mental Model

graph TD
  A["Your Application<br/>(Controllers, Services, Decorators)"] --> B["AIEnhancedService<br/>(Unified Interface & Task Execution)"]
  B --> C["Provider Abstraction Layer<br/>(OpenAI, Anthropic, Gemini, Ollama)"]
  C --> D["External AI APIs"]
  
  style A fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style B fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style C fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style D fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
Controller → Service → AIEnhancedService → AI Provider (OpenAI/Anthropic/Gemini/Ollama) → LLM API → Response

For streaming:

Controller → Service → AIEnhancedService.chat().stream() → async generator → chunk by chunk to client

For AI validation:

DTO with @AIValidateProperty → ValidationPipeline → AI Provider evaluates property → pass or reject

The @hazeljs/ai provider abstraction layer:

Your Application (Controllers, Services)
    ↓
AIEnhancedService (Unified fluent API)
    ↓
Provider Abstraction Layer
    ↓                    ↓                  ↓                ↓
OpenAIProvider    AnthropicProvider    GeminiProvider    OllamaProvider
    ↓                    ↓                  ↓                ↓
OpenAI API        Anthropic API        Gemini API       Local Ollama

When to Use @hazeljs/ai vs Alternatives

NeedUsePackage
Call an LLM for text generation, chat, or completionsAIEnhancedService@hazeljs/ai
Stream LLM responses to a clientAIEnhancedService.chat().stream()@hazeljs/ai
Generate embeddings for textVectorService or AI Provider .embed()@hazeljs/ai
Validate DTO properties with AI@AIValidateProperty@hazeljs/ai
Build a stateful agent with tools and memory@Agent, @Tool@hazeljs/agent
Semantic search over documents@SemanticSearch, @HybridSearch@hazeljs/rag
Manage prompt templates and versionsPrompt registry@hazeljs/prompts
Filter unsafe LLM input/output@GuardrailInput, @GuardrailOutput@hazeljs/guardrails

Decision guide:

  • Use @hazeljs/ai when you need direct LLM calls (completions, streaming, embeddings) without agent orchestration.
  • Use @hazeljs/agent when you need stateful multi-turn agents with tools, memory, and delegation.
  • Use @hazeljs/rag when you need document retrieval and context injection before LLM calls.
  • Use @hazeljs/ai + @hazeljs/rag together when building RAG pipelines that retrieve context and generate responses.

Installation

npm install @hazeljs/ai

Required: an AI provider API key set as an environment variable (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY).

HazelJS AIModule Registration

Register AIModule in your root or feature module to make AIEnhancedService available for injection:

// File: src/app.module.ts
import { HazelModule } from '@hazeljs/core';
import { AIModule } from '@hazeljs/ai';

@HazelModule({
  imports: [AIModule.register({
    provider: 'openai',
    model: 'gpt-4-turbo-preview',
    apiKey: process.env.OPENAI_API_KEY,
  })],
})
export class AppModule {}

AIModule.register() accepts:

OptionTypeDescription
providerstringAI provider name: 'openai', 'anthropic', 'gemini', 'cohere', 'ollama'
modelstringDefault model name (e.g., 'gpt-4-turbo-preview', 'claude-3-opus-20240229')
apiKeystringAPI key for the provider

AIService is deprecated. Use AIEnhancedService for all new HazelJS projects.

AIEnhancedService is the primary service in @hazeljs/ai for making LLM calls. AIEnhancedService provides a fluent chat builder API.

AIEnhancedService Fluent API

// File: src/chat/chat.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';

@Service()
export class ChatService {
  constructor(private readonly ai: AIEnhancedService) {}

  async generateResponse(userMessage: string): Promise<string> {
    const response = await this.ai
      .chat(userMessage)          // Set user message
      .system('You are a helpful assistant')  // Set system prompt
      .model('gpt-4')            // Select model
      .temperature(0.7)          // Set creativity (0-1)
      .maxTokens(500)            // Limit response length
      .text();                   // Execute and return string

    return response;
  }
}

AIEnhancedService Streaming

// File: src/chat/chat.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';

@Service()
export class ChatService {
  constructor(private readonly ai: AIEnhancedService) {}

  async streamResponse(userMessage: string) {
    const stream = this.ai
      .chat(userMessage)
      .model('gpt-4')
      .stream();                 // Returns async generator

    for await (const chunk of stream) {
      process.stdout.write(chunk.delta);
    }
  }
}

AIEnhancedService Fluent Methods

MethodPurpose
.chat(message)Set the user message
.system(prompt)Set the system prompt
.model(name)Select the LLM model
.temperature(value)Set response creativity (0-1)
.maxTokens(count)Limit response token count
.text()Execute and return the full response as a string
.stream()Execute and return an async generator of chunks
.hazelAccess the HCEL (HazelJS Composable Expression Language) builder

HCEL (HazelJS Composable Expression Language)

For complex, multi-step AI pipelines, HazelAI provides the .hazel property, which returns an HCEL builder. HCEL allows you to compose prompts, RAG operations, agents, multi-agent orchestration, and @hazeljs/memory operations into a single fluent chain.

const ai = HazelAI.create({
  defaultProvider: 'openai',
  providers: { openai: { apiKey: process.env.OPENAI_API_KEY || '' } },
});

const result = await ai.hazel
  .prompt('Summarize: {{input}}')
  .rag('docs')
  .agent('AnalysisAgent')
  .execute('Long text...');

HCEL also supports:

  • .agentPipeline({ pipelineId, agents })
  • .agentSupervisor({ name, workers, ... })
  • .agentGraphCompiled(graphId, compiledGraph, options?)
  • .memory(memoryService), .memoryRecall(...), .memorySave(...), .memorySearch(...)

See the HCEL Guide for more details.

AssistantFacade

AssistantFacade provides high-level conversational assistants with built-in memory management. It allows you to create assistant instances that "remember" previous turns and optionally persist them to Redis or Postgres via @hazeljs/memory.

Creating an Assistant

// File: src/chat/chat.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService, AssistantFacade } from '@hazeljs/ai';

@Service()
export class ChatService {
  constructor(
    private readonly ai: AIEnhancedService,
    private readonly assistantFacade: AssistantFacade
  ) {}

  async startConversation() {
    const assistant = await this.assistantFacade.create({
      model: 'gpt-4',
      systemPrompt: 'You are a professional research assistant.',
      memory: true, // Enable persistence
    });

    const response1 = await assistant.chat('Explain quantum computing.');
    console.log(response1.content);

    const response2 = await assistant.chat('How does it relate to AI?'); // Remembers context
    console.log(response2.content);

    return assistant.sessionId;
  }
}

Assistant Features

  • Built-in Memory: Automatically tracks message history.
  • Persistence: Integrates with @hazeljs/memory for cross-request session persistence.
  • Streaming Support: Easily stream conversational responses.
  • History Management: Access and clear conversation history per session.

HazelJS @AITask Decorator

Purpose

The @AITask decorator is a HazelJS method decorator that replaces a method's implementation with AI execution logic. When the decorated method is called, @AITask sends the configured prompt to the specified AI provider and returns the LLM response.

Signature

function AITask(options: AITaskConfig): MethodDecorator

Parameters

interface AITaskConfig {
  name: string;           // Unique task identifier
  provider: string;       // AI provider: 'openai', 'anthropic', 'ollama', etc.
  model: string;          // Model name (e.g., 'gpt-4-turbo-preview')
  prompt: string;         // Prompt template with {{variable}} placeholders
  outputType: string;     // Expected output: 'string', 'json', 'number', 'boolean'
  temperature?: number;   // Creativity level (0-1, default: 0.7)
  maxTokens?: number;     // Maximum tokens in response
  stream?: boolean;       // Enable streaming responses (default: false)
}

Lifecycle / Behavior

  1. The @AITask decorator replaces the method body with AI execution logic
  2. The first parameter passed to the method becomes the {{input}} variable in the prompt template
  3. The decorator sends the formatted prompt to the configured AI provider
  4. The AI provider returns a completion
  5. The decorator parses the response according to outputType and returns the result
  6. For stream: true, the method returns an async generator instead of a value

Example

// File: src/content/content.service.ts
import { Service } from '@hazeljs/core';
import { AIService, AITask } from '@hazeljs/ai';

@Service()
export class ContentService {
  constructor(public aiService: AIService) {}

  @AITask({
    name: 'summarize',
    provider: 'openai',
    model: 'gpt-4-turbo-preview',
    prompt: 'Summarize the following text in 3 sentences: {{input}}',
    outputType: 'string',
    temperature: 0.7,
    maxTokens: 500,
    stream: false,
  })
  async summarize(text: string) {
    // Method body is replaced by @AITask decorator
  }
}
// Usage: const summary = await contentService.summarize("Long article text...");

Common Mistakes

  • Forgetting to inject AIService in the constructor as public aiService (not private)
  • Writing logic in the method body (the @AITask decorator replaces the method body entirely)
  • Not registering AIModule in the module's imports array

HazelJS @AIFunction Decorator

Purpose

The @AIFunction decorator is a HazelJS method decorator that marks a method for AI processing without replacing the method body. @AIFunction is more flexible than @AITask because the method body can contain custom logic.

Signature

function AIFunction(options: AIFunctionOptions): MethodDecorator

Parameters

interface AIFunctionOptions {
  provider: string;       // AI provider to use
  model: string;          // Model identifier
  streaming?: boolean;    // Enable streaming (default: false)
  temperature?: number;   // Response creativity (default: 0.7)
  maxTokens?: number;     // Maximum response tokens
}

Example

// File: src/text/text.service.ts
import { Service } from '@hazeljs/core';
import { AIFunction, AIPrompt } from '@hazeljs/ai';

@Service()
export class TextService {
  @AIFunction({
    provider: 'openai',
    model: 'gpt-4-turbo-preview',
    streaming: true,
    temperature: 0.7,
  })
  async generateText(@AIPrompt() prompt: string) {
    // The framework intercepts this call, extracts the @AIPrompt parameter,
    // sends the prompt to the AI provider, and returns the result.
    // For streaming: returns an async generator.
    // For non-streaming: returns the complete response string.
  }
}

@AITask vs @AIFunction

Feature@AITask@AIFunction
Method bodyReplaced entirelyPreserved (custom logic allowed)
Prompt sourcePrompt template in decorator config@AIPrompt() parameter decorator
FlexibilitySimple declarative tasksComplex scenarios with custom processing
AI Service injectionRequires public aiService in constructorAutomatic

HazelJS @AIPrompt Parameter Decorator

Purpose

@AIPrompt() is a HazelJS parameter decorator that marks a method parameter as the input prompt for AI processing. @AIPrompt works with @AIFunction.

Example

// Single prompt parameter
async generateText(@AIPrompt() prompt: string) { }

// Multiple parameters — only the @AIPrompt parameter is sent to AI
async processData(
  @AIPrompt() userInput: string,  // Sent to AI provider
  context: any                     // Not processed by AI
) { }

HazelJS AI Validation Decorators

@AIValidate Decorator

@AIValidate is a HazelJS class or property decorator for AI-powered validation. The AI model evaluates data against validation criteria specified in the prompt.

@AIValidateProperty Decorator

@AIValidateProperty is a HazelJS property decorator for per-field AI validation. More explicit than @AIValidate for property-level validation.

AI Validation Configuration

interface AIValidationOptions {
  provider: string;       // AI provider to use
  instruction: string;    // Validation criteria instruction
  model?: string;         // Optional model override
  failOnInvalid?: boolean; // Throw error if validation fails (default: true)
}

AI Validation Example

// File: src/post/create-post.dto.ts
import { IsString, MinLength } from 'class-validator';
import { AIValidateProperty } from '@hazeljs/ai';

export class CreatePostDto {
  @IsString()
  @MinLength(10)
  @AIValidateProperty({
    provider: 'openai',
    instruction: 'Check if this title is SEO-friendly, appropriate, and engaging. Respond VALID or INVALID with reason.',
  })
  title: string;

  @AIValidateProperty({
    provider: 'openai',
    instruction: 'Validate content quality: professional, accurate, free of harmful content. Respond VALID or INVALID with reason.',
  })
  content: string;
}
// Validation order:
// 1. Traditional validators (@IsString, @MinLength) run first
// 2. AI validators (@AIValidateProperty) run second
// 3. All validators must pass for the DTO to be accepted

AI Validation Decision Guide

  • Use @AIValidateProperty for content moderation, quality checks, SEO validation, and complex semantic validation that rule-based validators cannot handle.
  • Use traditional validators (class-validator) for type checks, length limits, format validation, and simple constraints.
  • Combine both for defense-in-depth: traditional validators catch obvious issues cheaply; AI validators catch subtle issues.
  • Tradeoff: AI validation adds latency and API cost per request. Use selectively on user-facing content, not on every field.

HazelJS AI Providers

OpenAI Provider

// File: src/providers/openai-example.ts
import { OpenAIProvider } from '@hazeljs/ai';

const provider = new OpenAIProvider(process.env.OPENAI_API_KEY, {
  defaultModel: 'gpt-4-turbo-preview',
});

// Generate completion
const response = await provider.complete({
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Hello!' },
  ],
  model: 'gpt-4-turbo-preview',
  temperature: 0.7,
});

// Streaming completion
for await (const chunk of provider.streamComplete({
  messages: [{ role: 'user', content: 'Tell me a story' }],
})) {
  process.stdout.write(chunk.delta);
}

// Generate embeddings
const embeddings = await provider.embed({
  input: 'Hello, world!',
  model: 'text-embedding-3-small',
});

Anthropic Provider

import { AnthropicProvider } from '@hazeljs/ai';

const provider = new AnthropicProvider(process.env.ANTHROPIC_API_KEY);

const response = await provider.complete({
  messages: [{ role: 'user', content: 'Explain quantum computing' }],
  model: 'claude-3-opus-20240229',
});

Gemini Provider

import { GeminiProvider } from '@hazeljs/ai';

const provider = new GeminiProvider(process.env.GEMINI_API_KEY);

const response = await provider.complete({
  messages: [{ role: 'user', content: 'What is machine learning?' }],
  model: 'gemini-pro',
});

Ollama Provider (Local Models)

import { OllamaProvider } from '@hazeljs/ai';

const provider = new OllamaProvider({
  baseURL: 'http://localhost:11434',
});

const response = await provider.complete({
  messages: [{ role: 'user', content: 'Hello!' }],
  model: 'llama2',
});

Prompt Registry (New)

Manage your prompts outside of code. Version, test, and inject prompts dynamically:

import { PromptRegistry } from '@hazeljs/prompts';

const prompts = new PromptRegistry();
prompts.register('customer_greeting', 'Hello {{name}}, welcome to {{company}}!');

const formatted = prompts.get('customer_greeting', { name: 'Arslan', company: 'HazelJS' });

Provider Selection Guide

ProviderBest forModels
OpenAIGeneral-purpose chat, embeddings, function callingGPT-4, GPT-4o, GPT-3.5, text-embedding-3-small
AnthropicLong context, careful reasoning, safety-focused tasksClaude 3 Opus, Sonnet, Haiku
GeminiMultimodal tasks, Google ecosystem integrationGemini Pro, Gemini Ultra
CohereEmbeddings, search, enterprise featuresCommand, Embed
OllamaLocal development, privacy-sensitive workloads, offline useLlama 2, Mistral, CodeLlama, any GGUF model

HazelJS AIContextManager

Purpose

AIContextManager maintains a token-aware sliding conversation window for multi-turn conversations. AIContextManager automatically trims old messages when the token budget is exceeded.

Example

// File: src/chat/context-example.ts
import { AIContextManager } from '@hazeljs/ai';

const manager = new AIContextManager({
  maxTokens: 4000,       // Total token budget (prompt + completion)
  model: 'gpt-4o-mini',  // Used to estimate token counts
  reserveTokens: 500,    // Tokens reserved for the completion
});

// Add messages over multiple turns
manager.addSystemMessage('You are a helpful TypeScript assistant.');
manager.addUserMessage('What is dependency injection?');
manager.addAssistantMessage('Dependency injection is a design pattern where...');
manager.addUserMessage('How does HazelJS implement it?');

// Get the current window — old messages are automatically pruned if over budget
const messages = manager.getMessages();

// Inspect window state
console.log(manager.getTokenCount());   // Current estimated tokens
console.log(manager.getMessageCount()); // Messages in window

AIContextManager Behavior

  • System messages are always preserved (never pruned)
  • Oldest user/assistant turns are dropped first when the token budget is exceeded
  • Token estimates use a fast character-ratio approximation (no external tokenizer dependency)

HazelJS TokenTracker

Purpose

TokenTracker records prompt and completion token usage per model and computes estimated API costs.

Example

// File: src/tracking/token-example.ts
import { TokenTracker } from '@hazeljs/ai';

const tracker = new TokenTracker();

// After each completion, record the usage
tracker.track(response.usage, 'gpt-4o-mini');

// After embedding calls
tracker.trackEmbedding(text.length, 'text-embedding-3-small');

// Get a full breakdown
const summary = tracker.summary();
// {
//   totalPromptTokens: 12480,
//   totalCompletionTokens: 3210,
//   totalEmbeddingTokens: 4800,
//   estimatedCostUSD: 0.0048,
//   byModel: {
//     'gpt-4o-mini': { prompt: 12480, completion: 3210, costUSD: 0.0046 },
//     'text-embedding-3-small': { tokens: 4800, costUSD: 0.0002 },
//   },
// }

// Reset counters for the next request or session
tracker.reset();

HazelJS Multi-Provider Fallback

Configure a priority-ordered fallback chain so requests automatically retry on the next AI provider if the primary provider fails (rate limit, outage, or context-length error):

// File: src/ai/fallback-example.ts
import { AIService } from '@hazeljs/ai';

const ai = new AIService({
  providers: [
    { name: 'openai',    apiKey: process.env.OPENAI_API_KEY },
    { name: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY },
    { name: 'ollama',    baseURL: 'http://localhost:11434' },
  ],
  fallbackOrder: ['openai', 'anthropic', 'ollama'],
});

// If OpenAI is rate-limited, the request automatically retries with Anthropic.
// If Anthropic fails too, Ollama (local) is used as the final fallback.
const result = await ai.complete({
  messages: [{ role: 'user', content: 'Hello!' }],
});

Multi-Provider Fallback Decision Guide

  • Use this when your application requires high availability (99.9%+ uptime) for AI features
  • Use this when mixing local and cloud models in development environments
  • Use this when optimizing cost by routing to cheaper providers during off-peak hours
  • Tradeoff: Adds complexity and requires API keys for multiple providers

HazelJS VectorService

VectorService in @hazeljs/ai provides embedding generation and basic vector search. For advanced RAG features, use @hazeljs/rag instead.

// File: src/search/vector-example.ts
import { VectorService } from '@hazeljs/ai';

const vectorService = new VectorService({
  provider: 'openai',
  embeddingModel: 'text-embedding-3-small',
});

// Store documents with embeddings
await vectorService.addDocument({
  id: 'doc1',
  content: 'HazelJS is a modern Node.js framework',
  metadata: { category: 'framework' },
});

// Semantic search
const results = await vectorService.search({
  query: 'What is HazelJS?',
  limit: 5,
});

Recipe: Build an AI Chat Endpoint

// File: src/chat/chat.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';

@Service()
export class ChatService {
  constructor(private readonly ai: AIEnhancedService) {}

  async chat(message: string): Promise<string> {
    return this.ai
      .chat(message)
      .system('You are a helpful assistant')
      .model('gpt-4')
      .temperature(0.7)
      .text();
  }
}
// File: src/chat/chat.controller.ts
import { Controller, Post, Body } from '@hazeljs/core';
import { ChatService } from './chat.service';

@Controller('chat')
export class ChatController {
  constructor(private readonly chatService: ChatService) {}

  @Post()
  async sendMessage(@Body() body: { message: string }) {
    const response = await this.chatService.chat(body.message);
    return { response };
  }
}
// File: src/chat/chat.module.ts
import { HazelModule } from '@hazeljs/core';
import { AIModule } from '@hazeljs/ai';
import { ChatController } from './chat.controller';
import { ChatService } from './chat.service';

@HazelModule({
  imports: [AIModule.register({
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY,
  })],
  controllers: [ChatController],
  providers: [ChatService],
})
export class ChatModule {}

Recipe: Stream AI Responses

// File: src/stream/stream.controller.ts
import { Controller, Post, Body, Res } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';

@Controller('stream')
export class StreamController {
  constructor(private readonly ai: AIEnhancedService) {}

  @Post()
  async streamChat(@Body() body: { message: string }, @Res() res: any) {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');

    const stream = this.ai
      .chat(body.message)
      .model('gpt-4')
      .stream();

    for await (const chunk of stream) {
      res.write(`data: ${JSON.stringify({ delta: chunk.delta })}\n\n`);
    }

    res.end();
  }
}

Recipe: AI-Powered Content Summarization with @AITask

// File: src/content/content.service.ts
import { Service } from '@hazeljs/core';
import { AIService, AITask } from '@hazeljs/ai';

@Service()
export class ContentService {
  constructor(public aiService: AIService) {}

  @AITask({
    name: 'summarize',
    provider: 'openai',
    model: 'gpt-4-turbo-preview',
    prompt: 'Summarize the following text in 3 bullet points: {{input}}',
    outputType: 'string',
    temperature: 0.5,
    maxTokens: 300,
  })
  async summarize(text: string) {}

  @AITask({
    name: 'translate',
    provider: 'openai',
    model: 'gpt-4-turbo-preview',
    prompt: 'Translate the following text to Spanish: {{input}}',
    outputType: 'string',
  })
  async translateToSpanish(text: string) {}
}

HazelJS AI Error Handling

@hazeljs/ai provides typed error classes through AIError and AIErrorCode.

// File: src/chat/chat.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService, AIError, AIErrorCode } from '@hazeljs/ai';

@Service()
export class ChatService {
  constructor(private readonly ai: AIEnhancedService) {}

  async generateResponse(message: string) {
    try {
      return await this.ai.chat(message).model('gpt-4').text();
    } catch (error) {
      if (error instanceof AIError) {
        switch (error.code) {
          case AIErrorCode.RATE_LIMIT:
            return 'Service is busy. Please try again.';
          case AIErrorCode.AUTHENTICATION_FAILED:
            console.error('Invalid API key');
            throw error;
          case AIErrorCode.TOKEN_LIMIT_EXCEEDED:
            return await this.ai.chat(message.slice(0, 1000)).text();
          default:
            console.error('AI error:', error.message);
            return 'An error occurred.';
        }
      }
      throw error;
    }
  }
}

HazelJS AI Error Codes

Error CodeDescription
PROVIDER_NOT_FOUNDAI provider not registered in AIModule
PROVIDER_NOT_CONFIGUREDMissing API key or configuration
COMPLETION_FAILEDLLM completion request failed
STREAMING_FAILEDStreaming response failed
EMBEDDING_FAILEDEmbedding generation failed
RATE_LIMITRate limited by the AI provider
TOKEN_LIMIT_EXCEEDEDRequest exceeds model token limit
INVALID_REQUESTMalformed request to AI provider
AUTHENTICATION_FAILEDInvalid or expired API key

HazelJS AI Debugging

Enable debug logging for @hazeljs/ai:

HAZELJS_DEBUG=true npm start

Debug output includes model and provider selection, token usage estimates, cache hits/misses, request duration, and rate limiting status:

2024-03-23T12:00:00.000Z [hazeljs:ai] complete start model=gpt-4 provider=openai
2024-03-23T12:00:00.100Z [hazeljs:ai] complete tokens estimated=150 user=anonymous
2024-03-23T12:00:01.500Z [hazeljs:ai] complete success duration=1400ms tokens=245

AI-Native Observability (Tracing)

Monitor LLM performance, latency, and costs natively with OpenTelemetry. Use the @Trace() decorator from @hazeljs/observability to automatically capture spans and usage metrics.

import { Trace } from '@hazeljs/observability';

class MyService {
  @Trace('ai_completion')
  async generateContent(prompt: string) {
    return this.ai.chat(prompt).text();
  }
}
  • Agent Package — Build stateful AI agents with tools and memory (uses @hazeljs/ai internally)
  • RAG Package — Retrieval-augmented generation and advanced vector search
  • Prompts Package — Prompt template management and versioning
  • Memory Package — Conversation and entity memory for agents and RAG
  • Guardrails Package — Content safety: PII redaction, prompt injection detection
  • Eval Package — Golden datasets and CI metrics for RAG and LLM outputs
  • Cache Package — Cache AI responses to reduce latency and cost

Prerequisites

  • Installation — Install @hazeljs/core and @hazeljs/ai
  • Core Package — Understand modules, DI, and the request pipeline

Recipes

Recipe: Chat Endpoint with Streaming

// File: src/chat/chat.controller.ts
import { Controller, Post, Body, Res } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';
import { Response } from 'express';

@Controller('chat')
export class ChatController {
  constructor(private readonly ai: AIEnhancedService) {}

  @Post('stream')
  async stream(@Body('message') message: string, @Res() res: Response) {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');

    const stream = await this.ai
      .chat(message)
      .system('You are a helpful assistant.')
      .model('gpt-4-turbo-preview')
      .stream();

    for await (const chunk of stream) {
      res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
    }
    res.end();
  }
}

Recipe: Multi-Provider Fallback

// File: src/ai/resilient-ai.service.ts
import { Service } from '@hazeljs/core';
import { AIEnhancedService } from '@hazeljs/ai';

@Service()
export class ResilientAIService {
  constructor(private readonly ai: AIEnhancedService) {}

  async generate(prompt: string): Promise<string> {
    try {
      return await this.ai
        .chat(prompt)
        .model('gpt-4-turbo-preview')
        .text();
    } catch {
      // Fallback to Anthropic if OpenAI fails
      return await this.ai
        .chat(prompt)
        .provider('anthropic')
        .model('claude-3-sonnet-20240229')
        .text();
    }
  }
}

Recipe: AI-Powered DTO Validation

// File: src/moderation/moderation.dto.ts
import { AIValidateProperty } from '@hazeljs/ai';

export class CreatePostDto {
  title: string;

  @AIValidateProperty({
    provider: 'openai',
    instruction: 'Content must not contain hate speech, violence, or personal attacks',
    model: 'gpt-4-turbo-preview',
  })
  content: string;
}
// File: src/moderation/moderation.controller.ts
import { Controller, Post, Body, UsePipes } from '@hazeljs/core';
import { AIValidationPipe } from '@hazeljs/ai';
import { CreatePostDto } from './moderation.dto';

@Controller('posts')
export class PostController {
  @Post()
  @UsePipes(AIValidationPipe)
  async create(@Body() dto: CreatePostDto) {
    return { message: 'Post created', title: dto.title };
  }
}

Next Concepts to Learn