HazelJS AI Package
@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/aiprovides LLM integration for HazelJS applications with a provider-agnostic API for completions, streaming, embeddings, and AI-powered validation. - When to use: Use
@hazeljs/aiwhen a HazelJS application needs to call LLMs (OpenAI, Anthropic, Gemini, Cohere, Ollama) for text generation, chat, embeddings, or AI-powered validation. Use@hazeljs/agentinstead 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@AITaskdecorator for declarative AI methods; UseAIContextManagerfor multi-turn conversation windows; UseTokenTrackerfor cost monitoring. - Common mistakes: Forgetting to register
AIModulein the module's imports array; using deprecatedAIServiceinstead ofAIEnhancedService; not handlingAIErrorwith try-catch; exceeding token limits without usingAIContextManager; not settingOPENAI_API_KEYor 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
| Need | Use | Package |
|---|---|---|
| Call an LLM for text generation, chat, or completions | AIEnhancedService | @hazeljs/ai |
| Stream LLM responses to a client | AIEnhancedService.chat().stream() | @hazeljs/ai |
| Generate embeddings for text | VectorService 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 versions | Prompt registry | @hazeljs/prompts |
| Filter unsafe LLM input/output | @GuardrailInput, @GuardrailOutput | @hazeljs/guardrails |
Decision guide:
- Use
@hazeljs/aiwhen you need direct LLM calls (completions, streaming, embeddings) without agent orchestration. - Use
@hazeljs/agentwhen you need stateful multi-turn agents with tools, memory, and delegation. - Use
@hazeljs/ragwhen you need document retrieval and context injection before LLM calls. - Use
@hazeljs/ai+@hazeljs/ragtogether 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:
| Option | Type | Description |
|---|---|---|
provider | string | AI provider name: 'openai', 'anthropic', 'gemini', 'cohere', 'ollama' |
model | string | Default model name (e.g., 'gpt-4-turbo-preview', 'claude-3-opus-20240229') |
apiKey | string | API key for the provider |
HazelJS AIEnhancedService (Recommended)
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
| Method | Purpose |
|---|---|
.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 |
.hazel | Access 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/memoryfor 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
- The
@AITaskdecorator replaces the method body with AI execution logic - The first parameter passed to the method becomes the
{{input}}variable in the prompt template - The decorator sends the formatted prompt to the configured AI provider
- The AI provider returns a completion
- The decorator parses the response according to
outputTypeand returns the result - 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
AIServicein the constructor aspublic aiService(notprivate) - Writing logic in the method body (the
@AITaskdecorator replaces the method body entirely) - Not registering
AIModulein 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 body | Replaced entirely | Preserved (custom logic allowed) |
| Prompt source | Prompt template in decorator config | @AIPrompt() parameter decorator |
| Flexibility | Simple declarative tasks | Complex scenarios with custom processing |
| AI Service injection | Requires public aiService in constructor | Automatic |
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
@AIValidatePropertyfor 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
| Provider | Best for | Models |
|---|---|---|
| OpenAI | General-purpose chat, embeddings, function calling | GPT-4, GPT-4o, GPT-3.5, text-embedding-3-small |
| Anthropic | Long context, careful reasoning, safety-focused tasks | Claude 3 Opus, Sonnet, Haiku |
| Gemini | Multimodal tasks, Google ecosystem integration | Gemini Pro, Gemini Ultra |
| Cohere | Embeddings, search, enterprise features | Command, Embed |
| Ollama | Local development, privacy-sensitive workloads, offline use | Llama 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 Code | Description |
|---|---|
PROVIDER_NOT_FOUND | AI provider not registered in AIModule |
PROVIDER_NOT_CONFIGURED | Missing API key or configuration |
COMPLETION_FAILED | LLM completion request failed |
STREAMING_FAILED | Streaming response failed |
EMBEDDING_FAILED | Embedding generation failed |
RATE_LIMIT | Rate limited by the AI provider |
TOKEN_LIMIT_EXCEEDED | Request exceeds model token limit |
INVALID_REQUEST | Malformed request to AI provider |
AUTHENTICATION_FAILED | Invalid 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();
}
}
Related Pages
- Agent Package — Build stateful AI agents with tools and memory (uses
@hazeljs/aiinternally) - 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/coreand@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
- Agent Package — Stateful AI agents with tools and memory
- RAG Package — Document retrieval for grounded LLM responses
- Memory Guide — Conversation and entity memory systems
- RAG Patterns Guide — Advanced RAG strategies and best practices