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), 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; not injecting AIEnhancedService for @AITask methods; 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

Provider SDKs are bundled. Set the API key for your provider:

export OPENAI_API_KEY=sk-...          # OpenAI (default)
export ANTHROPIC_API_KEY=sk-ant-...   # Anthropic
export GEMINI_API_KEY=...             # Google Gemini
export COHERE_API_KEY=...             # Cohere
export OLLAMA_ENABLED=true            # Local Ollama (HTTP API, no npm package)
export OLLAMA_BASE_URL=http://localhost:11434

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

HazelJS AIEnhancedService

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 { AIEnhancedService, AITask } from '@hazeljs/​ai';

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

  @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 AIEnhancedService 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 { AIEnhancedService } from '@hazeljs/​ai';

const ai = new AIEnhancedService();

async function completeWithFallback(messages: { role: string; content: string }[]) {
  const providers = ['openai', 'anthropic', 'ollama'] as const;
  for (const provider of providers) {
    try {
      return await ai.complete({ messages, provider });
    } catch {
      /​/​ try next provider
    }
  }
  throw new Error('All providers failed');
}

/​/​ If OpenAI is rate-limited, the loop retries with Anthropic, then Ollama.
const result = await completeWithFallback([{ 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 { AIEnhancedService, AITask } from '@hazeljs/​ai';

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

  @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