DEV Community

Cover image for A Meal Planner & Grocery Shopping Agent in Typescript with HazelJS
Nisa Fatima
Nisa Fatima

Posted on

A Meal Planner & Grocery Shopping Agent in Typescript with HazelJS

In this post, we'll build a practical AI-powered meal planning agent using HazelJS. This agent helps users create personalized meal plans, discover recipes, and generate shopping lists—all while demonstrating HazelJS's powerful agent orchestration, RAG capabilities, and production-ready patterns.

What We're Building

Our meal planning agent handles the complete meal planning workflow:

  • Dietary Intake: Extracts dietary restrictions, budget, cooking time, and preferences
  • Recipe Search: Finds recipes using RAG over a recipe database
  • Meal Planning: Creates day-by-day meal plans with balanced nutrition
  • Shopping Lists: Generates organized grocery lists with cost estimates
  • Nutrition Coaching: Orchestrates the entire workflow via supervisor routing

Why HazelJS?

HazelJS provides a production-ready framework for building AI-native applications. For this project, we leverage:

  • Multi-agent orchestration with @Agent, @Tool, and @Delegate decorators
  • RAG (Retrieval-Augmented Generation) for semantic recipe search
  • Supervisor routing to delegate tasks to specialized agents
  • Production resilience with retries, circuit breakers, and rate limiting
  • Built-in observability via the HazelJS Inspector
  • Guardrails for safer AI recommendations

Project Architecture

meal-planning-agent/
├── src/
│   ├── agents/              # Agent implementations
│   │   ├── dietary-intake.agent.ts
│   │   ├── recipe-search.agent.ts
│   │   ├── meal-plan.agent.ts
│   │   ├── shopping-list.agent.ts
│   │   └── nutrition-coach.agent.ts
│   ├── providers/           # LLM and embedding providers
│   │   ├── meal-planning-llm.provider.ts
│   │   └── local-embedding.provider.ts
│   ├── data/                # Recipe fixtures
│   │   └── recipe-fixtures.ts
│   ├── meal/                # Controllers and services
│   │   ├── meal.controller.ts
│   │   └── recipe-kb.service.ts
│   ├── app.module.ts
│   ├── health.controller.ts
│   └── index.ts
├── package.json
├── tsconfig.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

Agent Implementation

Dietary Intake Agent

The DietaryIntakeAgent extracts structured information from natural language:

@Agent({
  name: 'DietaryIntakeAgent',
  description: 'Extracts dietary restrictions, budget, cooking time, cuisine preferences, and nutritional goals.',
  systemPrompt: 'You are a dietary intake specialist. Extract the user profile before any meal plan is created.',
  maxSteps: 4,
  temperature: 0,
})
@Service()
export class DietaryIntakeAgent {
  @Tool({
    name: 'extractDietaryProfile',
    description: 'Extract structured dietary planning information from a user request.',
    parameters: [
      { name: 'message', type: 'string', description: 'The raw user request', required: true },
      { name: 'userId', type: 'string', description: 'User id when available', required: false },
    ],
  })
  async extractDietaryProfile(input: { message: string; userId?: string }) {
    const dietaryRestrictions = this.extractDietaryRestrictions(input.message);
    const budget = this.extractBudget(input.message) ?? 150;
    const cookingTime = this.extractCookingTime(input.message) ?? 30;

    return {
      userId: input.userId ?? 'user-demo',
      dietaryRestrictions,
      budget,
      cookingTime,
      // ... more fields
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Recipe Search Agent with RAG

The RecipeSearchAgent uses RAG to find recipes semantically:

@Agent({
  name: 'RecipeSearchAgent',
  description: 'Retrieves recipes from the knowledge base based on dietary restrictions, cuisine, and nutritional goals.',
  systemPrompt: 'You are a recipe search specialist. Use retrieved context only and cite source ids.',
  enableRAG: true,
  ragTopK: 3,
  maxSteps: 4,
  temperature: 0,
})
@Service()
export class RecipeSearchAgent {
  constructor(private readonly knowledgeBase: RecipeKnowledgeBaseService) {}

  @Tool({
    name: 'searchRecipes',
    description: 'Search recipes based on dietary preferences, cuisine, and nutritional goals.',
    parameters: [
      { name: 'query', type: 'string', description: 'The recipe search query', required: true },
      { name: 'topK', type: 'number', description: 'Number of recipes to retrieve', required: false },
    ],
  })
  async searchRecipes(input: { query: string; topK?: number }) {
    return this.knowledgeBase.answer(input.query, input.topK ?? 3);
  }
}
Enter fullscreen mode Exit fullscreen mode

Nutrition Coach with Supervisor

The NutritionCoachAgent orchestrates the workflow using delegation:

@Agent({
  name: 'NutritionCoachAgent',
  description: 'Coordinates dietary intake, recipe retrieval, meal planning, and shopping list creation.',
  systemPrompt: 'You are the nutrition coach orchestrator. Extract constraints, retrieve recipes, build meal plans, and create shopping lists.',
  maxSteps: 8,
  temperature: 0,
})
@Service()
export class NutritionCoachAgent {
  @Delegate({
    agent: 'DietaryIntakeAgent',
    description: 'Extract dietary restrictions, budget, cooking time, and preferences from a user request.',
    inputField: 'input',
  })
  async analyzeMealRequest(input: string): Promise<string> {
    return '';
  }

  @Delegate({
    agent: 'RecipeSearchAgent',
    description: 'Retrieve recipes based on dietary preferences and nutritional goals.',
    inputField: 'input',
  })
  async getRecipes(input: string): Promise<string> {
    return '';
  }
  // ... more delegates
}
Enter fullscreen mode Exit fullscreen mode

RAG Implementation

We use MemoryVectorStore and RAGPipeline for semantic recipe search:

@Service()
export class RecipeKnowledgeBaseService {
  private readonly embeddings = new LocalMealEmbeddingProvider();
  private readonly vectorStore = new MemoryVectorStore(this.embeddings);
  private readonly rag = new RAGPipeline({
    vectorStore: this.vectorStore,
    embeddingProvider: this.embeddings,
    topK: 3,
  });

  async answer(query: string, topK = 3) {
    const sources = await this.rag.retrieve(query, { topK }, RetrievalStrategy.HYBRID);
    return {
      answer: sources.map((source) => source.content).join('\n\n'),
      sources: sources.map((source) => ({
        id: source.id,
        score: Number(source.score.toFixed(3)),
        cuisine: source.metadata?.cuisine,
        dietary: source.metadata?.dietary,
        // ... more metadata
      })),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Production Configuration

The AgentModule is configured with production-ready features:

@HazelModule({
  imports: [
    ConfigModule.forRoot({ envFilePath: ['.env', '.env.local'], isGlobal: true }),
    CacheModule.forRoot({ strategy: 'memory', isGlobal: true }),
    InspectorModule.forRoot({ inspectorBasePath: '/__hazel', developmentOnly: true }),
    GuardrailsModule.forRoot({
      redactPIIByDefault: true,
      blockInjectionByDefault: true,
      blockToxicityByDefault: true,
    }),
    AIModule,
    RAGModule,
    AgentModule.forRoot({
      runtime: {
        llmProvider: new MealPlanningLocalLLMProvider(),
        defaultMaxSteps: 8,
        defaultTimeout: 15000,
        enableObservability: true,
        enableMetrics: true,
        enableRetry: true,
        enableCircuitBreaker: true,
        rateLimitPerMinute: 120,
      },
    }),
  ],
  controllers: [HealthController, MealController],
  providers: [
    RecipeKnowledgeBaseService,
    DietaryIntakeAgent,
    RecipeSearchAgent,
    MealPlanAgent,
    ShoppingListAgent,
    NutritionCoachAgent,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Running the Project

# Install dependencies
npm install --legacy-peer-deps

# Build the project
npm run build

# Run eval tests
npm run eval

# Start development server
npm run dev
Enter fullscreen mode Exit fullscreen mode

Testing the API

# Health check
curl http://localhost:3000/health

# Dietary intake
curl -s -X POST http://localhost:3000/meal/intake \
  -H 'content-type: application/json' \
  -d '{"message":"I am vegetarian, have a $150 weekly budget, can cook 30 mins per meal.","userId":"user-1"}'

# Recipe search with RAG
curl -s -X POST http://localhost:3000/meal/recipes \
  -H 'content-type: application/json' \
  -d '{"message":"Find high-protein vegetarian recipes"}'

# Supervisor orchestration
curl -s -X POST http://localhost:3000/meal/supervisor \
  -H 'content-type: application/json' \
  -d '{"message":"I am vegetarian, have a $150 weekly budget, can cook 30 mins per meal. Plan my week with high-protein options.","userId":"user-1"}'
Enter fullscreen mode Exit fullscreen mode

HazelJS Inspector

Access the built-in inspector at http://localhost:3000/__hazel for:

  • Real-time agent execution traces
  • Tool call logs
  • Performance metrics
  • Circuit breaker status
  • Rate limiter status

Key Takeaways

  1. Multi-agent architecture: Each agent has a focused responsibility, making the system maintainable and testable
  2. RAG for knowledge retrieval: Semantic search over recipe database provides accurate, context-aware results
  3. Supervisor routing: The orchestrator agent delegates tasks to specialists, enabling complex workflows
  4. Production resilience: Built-in retries, circuit breakers, and rate limiting ensure reliability
  5. Observability: The inspector provides deep insights into agent behavior and performance
  6. Guardrails: PII redaction and content safety protect users from inappropriate recommendations

What's Next?

For a production deployment, you would:

  • Replace the local LLM provider with OpenAI, Anthropic, or Google Generative AI
  • Add persistent storage with @hazeljs/memory for user preferences and meal history
  • Implement @hazeljs/flow for advanced workflow orchestration
  • Add @hazeljs/pubsub for async meal planning notifications
  • Connect to real recipe APIs for dynamic content

Conclusion

This meal planning agent demonstrates how HazelJS enables building sophisticated AI applications with production-ready patterns. The combination of multi-agent orchestration, RAG, and resilience features makes it easy to create reliable, observable, and scalable AI-native applications.

Complete project is available here: Meal Planning Agent
See you next time!

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.