Introducing HCEL: The Most Fluent Way to Build AI Pipelines in TypeScript
A deep dive into HCEL (HazelJS Composable Expression Language), a fluent DSL for orchestrating prompts, RAG, and AI agents with minimal boilerplate and maximum type safety.
In the rapidly evolving landscape of AI development, orchestration is everything. As developers move from simple LLM calls to complex, multi-step agentic workflows, the need for a clean, expressive, and type-safe way to define these pipelines becomes critical.
Today, we are excited to introduce HCEL (HazelJS Composable Expression Language)—a fluent, TypeScript-native DSL designed to make AI orchestration as intuitive as a standard functional chain.
What is HCEL?
HCEL stands for HazelJS Composable Expression Language. It is not a separate language you need to learn, but a fluent API provided by the @hazeljs/ai package. It allows you to "chain" together different AI capabilities—prompts, RAG searches, agents, and machine learning models—into a single, executable pipeline.
Why HCEL?
Traditional AI pipelines often suffer from "pyramid of doom" callback structures or messy async/await boilerplate for passing state between steps. HCEL solves this by providing:
- Fluent Method Chaining: Build complex logic step-by-step.
- Implicit Context Passing: The output of operation
Aautomatically becomes the input for operationB. - Observability by Default: Every step in the chain is automatically traced and timed.
- Flow Integration: Easily convert any HCEL chain into a durable
@hazeljs/flownode.
Real-World Examples from Our Codebase
Let's explore actual working examples from our @hazeljs/ai/examples directory that demonstrate HCEL in action.
Example 1: Simple HCEL Chain
From hcel-demo.ts:
// Simple HCEL Chain
const simpleResult = await ai.hazel
.prompt('What is HazelJS?')
.execute() as string;
console.log(`Result: ${simpleResult.slice(0, 100)}...`);
What's happening here?
- We start with
ai.hazel- the entry point for HCEL chains .prompt()creates a text generation operation.execute()runs the entire chain and returns the result- The result is automatically typed as
stringthanks to TypeScript inference
Example 2: RAG + ML Chain
From hcel-demo.ts:
// RAG + ML Chain
const ragMlResult = await ai.hazel
.rag('docs') // Search documentation
.ml('sentiment') // Analyze sentiment
.execute() as SentimentResult;
console.log(`Sentiment: ${ragMlResult.sentiment} (${ragMlResult.score})`);
What's happening here?
.rag('docs')searches your documentation for relevant context- The RAG result is automatically passed to the next step
.ml('sentiment')runs sentiment analysis on the RAG output- The final result includes both sentiment and confidence score
Example 3: Streaming Chain
From hcel-demo.ts:
// Streaming Chain
console.log('Assistant: ');
for await (const chunk of ai.hazel
.prompt('Tell me a short story about AI and creativity')
.stream()) {
process.stdout.write(chunk as string);
}
What's happening here?
.stream()instead of.execute()enables real-time streaming- Perfect for chat interfaces or long-running generations
- Each chunk is processed as it arrives from the AI provider
Example 4: Chain with Context & Observability
From hcel-demo.ts:
const contextChain = ai.hazel
.prompt('Analyze this user feedback: {feedback}')
.ml('sentiment')
.context({ userId: 'user-123', sessionId: 'session-456' })
.observe((event) => {
console.log(`📡 Event: ${event.type} at ${new Date(event.timestamp).toISOString()}`);
});
const contextResult = await contextChain.execute() as SentimentResult;
What's happening here?
.context()adds metadata that flows through the entire chain.observe()hooks into every operation for logging and monitoring- Perfect for debugging and production observability
Production-Ready Features
HCEL isn't just for demos—it's built for production workloads. Let's look at our production example:
Example 5: Persistent HCEL Chains
From hcel-production-demo.ts:
// Production HazelAI with Persistence Configuration
const ai = HazelAI.create({
defaultProvider: 'openai',
model: 'gpt-4o',
temperature: 0.7,
// Production persistence configuration
persistence: {
memory: {
store: 'in-memory', // Change to 'postgres' or 'redis' for production
ttl: 3600, // 1 hour
},
rag: {
vectorStore: 'in-memory', // Change to 'pinecone', 'qdrant', etc. for production
options: {
topK: 5,
chunkSize: 1000,
chunkOverlap: 200,
}
},
chains: {
store: 'in-memory', // Change to 'postgres' or 'redis' for production
ttl: 7200, // 2 hours
}
}
});
// Create a persistent analysis chain
const analysisChain = ai.hazel
.prompt('Analyze user feedback: This product is amazing! It works perfectly.')
.persist('user-feedback-analysis') // Persist this chain
.cache(1800); // Cache results for 30 minutes
const result = await analysisChain.execute();
Production Features:
- Persistence: Chain state is automatically saved to Redis/Postgres
- Caching: Results are cached to avoid redundant API calls
- Configuration: Production-ready settings for stores and TTLs
Example 6: Parallel Operations
From hcel-demo.ts:
// Parallel Operations
const parallelChain = ai.hazel
.parallel(
ai.hazel.prompt('Summarize: "AI is transforming the world"'),
ai.hazel.ml('sentiment', { labels: ['positive', 'negative', 'neutral'] })
);
const parallelResult = await parallelChain.execute();
What's happening here?
.parallel()executes multiple operations simultaneously- Perfect for independent tasks that can run concurrently
- Results are collected and returned as an array
Advanced Orchestration Patterns
Example 7: Flow Engine Integration
From hcel-flow-demo.ts:
// HCEL-Flow Bridge - Wraps HCEL chains as Flow Engine nodes
class HCELFlowNode {
constructor(private chain: any) {}
async execute(input: unknown): Promise<NodeResult> {
try {
const result = await this.chain.execute(input);
return {
status: 'ok',
output: result
};
} catch (error) {
return {
status: 'error',
reason: error instanceof Error ? error.message : 'Unknown error'
};
}
}
}
// Convert HCEL chain to Flow node
const hcelNode = new HCELFlowNode(
ai.hazel
.prompt('Process user request: {{input}}')
.rag('knowledge-base')
.agent('support-specialist')
);
What's happening here?
- HCEL chains can be wrapped as Flow Engine nodes
- Enables durable, long-running workflows with AI steps
- Perfect for human-in-the-loop processes and complex business logic
How to Run These Examples
All examples are available in our packages/ai/examples directory:
Setup
# From the packages/ai directory
npm run build
# Set your API key
export OPENAI_API_KEY=your-key-here
Run the Examples
# Basic HCEL demo
node dist/examples/hcel-demo.js
# Production features demo
node dist/examples/hcel-production-demo.js
# Flow integration demo
node dist/examples/hcel-flow-demo.js
What Each Example Demonstrates
hcel-demo.ts- Basic HCEL operations, streaming, parallel executionhcel-production-demo.ts- Persistence, caching, memory managementhcel-flow-demo.ts- Integration with HazelJS Flow Enginesimple-demo.ts- Works without API keys for testingunified-platform-example.ts- Complete platform showcase
The Power of Implicit Context
The core magic of HCEL is the Implicit Context. Every step in the builder returns a new state that includes a pipe. The pipe is what transforms the output of Operation N into the input of Operation N+1.
Explicit vs Implicit Context
// ❌ Traditional approach - explicit context passing
const summary = await ai.chat('Summarize: ' + userQuery);
const context = await ai.rag('docs', summary);
const analysis = await ai.agent('analyst', context);
// ✅ HCEL approach - implicit context passing
const result = await ai.hazel
.prompt('Summarize: {{input}}')
.rag('docs')
.agent('analyst')
.execute(userQuery);
Context Transformation
// Each step transforms the context
const pipeline = ai.hazel
.prompt('Extract key topics: {{input}}') // string → string
.ml('classify', { labels: ['tech', 'business', 'other'] }) // string → ClassificationResult
.conditional((result) => result.label === 'tech') // ClassificationResult → boolean
.prompt('Explain this tech topic: {{input}}') // ClassificationResult → string
.execute('AI is revolutionizing software development');
Type Safety and IntelliSense
Because HCEL is built with TypeScript, you get full type safety and IDE support:
// TypeScript knows the return type based on the last operation
const sentimentResult = await ai.hazel
.prompt('Analyze: {{input}}')
.ml('sentiment')
.execute() as SentimentResult; // Type is inferred!
// Get full chain summary with types
const summary = ai.hazel
.prompt('Test')
.ml('sentiment')
.getSummary();
console.log(summary.operations); // ['prompt', 'ml']
console.log(summary.config); // Chain configuration
Getting Started with HCEL
Installation
npm install @hazeljs/ai @hazeljs/core
Basic Usage
import { HazelAI } from '@hazeljs/ai';
const ai = HazelAI.create({
defaultProvider: 'openai',
model: 'gpt-4o'
});
// Your first HCEL chain
const result = await ai.hazel
.prompt('What is the future of AI?')
.execute();
Advanced Usage
// Production-ready chain
const pipeline = ai.hazel
.persist('user-analysis')
.prompt('Analyze user feedback: {{input}}')
.rag('product-docs')
.ml('sentiment')
.cache(3600)
.observe((event) => console.log(event));
What's Next?
HCEL is just the beginning. We're working on:
- More ML Operations: Classification, extraction, translation
- Advanced Flow Patterns: Conditional branching, loops, retries
- Enhanced Observability: OpenTelemetry integration, custom metrics
- Visual Builder: Web-based HCEL chain designer
- Template Library: Pre-built chains for common use cases
Join the HCEL Community
- GitHub: hazeljs/hazeljs
- Documentation: hazeljs.com/docs
- Examples: packages/ai/examples
- Discord: Join our developer community
Conclusion
HCEL represents a fundamental shift in how we think about AI orchestration. By providing a fluent, type-safe, and production-ready API, we're making sophisticated AI workflows accessible to every TypeScript developer.
Whether you're building simple chatbots or complex multi-agent systems, HCEL provides the tools you need to compose, observe, and scale your AI operations with confidence.
Try HCEL today and experience the future of AI orchestration in TypeScript! 🚀 in .prompt() tells HCEL exactly where to inject the previous step's result.
- Auto-Injection: Operations like
.rag()or.agent()automatically use the current context as their search query or instruction if no specific input is provided.
This allows you to focus on the logic, not the data-shuffling boilerplate.
Real-World Case Study: Automated Support Triage
Here is a full implementation of an automated support triage system built with HCEL:
@Service()
export class SupportTriage {
constructor(private readonly ai: AIEnhancedService) {}
async handleTicket(ticketText: string) {
return this.ai.hazel
.persist('support-triage')
.ml('sentiment')
.parallel(
this.ai.hazel.ml('classify', { categories: ['billing', 'technical', 'sales'] }),
this.ai.hazel.prompt('Extract product names from: {{input}}')
)
.conditional((context) => context.sentiment === 'negative' && context.classification === 'technical')
.agent('SeniorTechnicalSupport')
.conditional((context) => context.classification === 'billing')
.rag('billing-faq')
.prompt('Answer billing query: {{input}}')
.execute(ticketText);
}
}
This single method replaces what would traditionally be dozens of lines of nested if statements, manual RAG lookups, and complex state management.
Try it Today
HCEL is available now in @hazeljs/ai version v0.7.0+.
Whether you are building a simple chat bot or a massively parallel research engine, HCEL provides the expressive power you need without the boilerplate.
Resources:
- HCEL Guide — Full API reference and advanced patterns.
- AI Package Documentation — Getting started with AI in HazelJS.
- Agentic RAG Guide — Learn how to pair HCEL with advanced retrieval strategies.
Happy coding with HazelJS!