SMBs running AI customer support agents experience cascading failures when one downstream API (like order lookup, return portal) becomes slow or unavailable, leading to lost customer interactions and manual intervention.
A complete, working implementation of this recipe — downloadable as a zip or browsable file by file. Generated by our build pipeline; tested with full coverage before publishing.
SMB customer support agents powered by Mistral AI can fail hard when a downstream API — your CRM, order lookup, return portal, or refund system — goes slow or starts returning errors. A single outage cascades: the AI can’t complete its reasoning loop, tools time out, and customers get no response. This tutorial builds a Mistral AI Agent Reliability Suite — a Next.js App Router project that wraps every Mistral tool call in a circuit breaker and idempotency middleware, exposes a health endpoint with auto-generated Kubernetes probes, and hooks a Trigger.dev job that creates incident workflows and runbooks when a breaker trips. By the end you’ll have a working reliability layer that keeps your support agent running regardless of which downstream service is having a bad day.
p-limit goes in devDependencies because it’s bundled by Next.js at build time — it doesn’t need to be a runtime dependency for a deployed app.
Expected output: A package.json with all manually added packages pinned to exact versions (no ^ or ~). The test script should read "test": "vitest run --coverage --reporter=json --outputFile=vitest-report.json".
Step 2: Define shared types and Zod schemas
Create src/lib/types.ts. This file holds every type your reliability layer needs: tool names, result shapes, circuit events, health statuses, and the Zod schemas that validate incoming requests.
Expected output: A file with 7 interfaces/types and 2 Zod schemas. The SupportToolName union appears in both toolRequestSchema’s enum and circuitTripEventSchema’s enum — keep them in sync.
Step 3: Create the Mistral AI client
Create src/lib/mistral-client.ts. This wraps the official Mistral SDK with Langfuse tracing, automatic retry via p-retry, and timeouts via p-timeout.
Expected output: A MistralClient class with chat(), chatStream(), and executeToolCall() methods. The singleton mistralClient is ready for import. Errors from chat() are not caught here — they propagate up so the circuit breaker and orchestrator can handle them.
Step 4: Wrap tools with circuit breakers and idempotency
Create src/lib/tools.ts. This is the heart of the reliability suite. It creates one CircuitBreaker per support tool, wraps every call in idempotency middleware, maps tool names to environment-variable URLs, and applies p-retry + p-timeout to the downstream HTTP call.
ts
import { CircuitBreaker, CircuitOpenError, InMemoryAdapter } from "@reaatech/circuit-breaker-agents";import { MemoryAdapter, IdempotencyMiddleware, IdempotencyError } from "@reaatech/idempotency-middleware";import { SupportToolName, ToolParameters, SupportToolResult } from "./types.js";import pRetry from "p-retry";import pTimeout from "p-timeout";const circuitAdapter = new InMemoryAdapter();const idempotencyStorage = new MemoryAdapter();const idempotencyMw = new IdempotencyMiddleware(idempotencyStorage, { ttl: 86_400_000, lockTimeout: 30_000,});const
Expected output: A module that exports initTools(), executeTool(), and getCircuitStatuses(). Call initTools() once at startup (from instrumentation) to connect the adapters and seed the breaker map. After that, executeTool() routes each tool name to the correct URL with a 10-second timeout and 2 retries, while the circuit breaker isolates failures — 5 consecutive failures open the circuit for 30 seconds.
Step 5: Build the support orchestrator
Create src/services/support-orchestrator.ts. The orchestrator maintains per-session conversation history, defines all four tool definitions for Mistral function calling, and implements the tool-call loop: send a message, process any tool calls the model requests, then return the final reply.
ts
import { MistralClient, mistralClient } from "../lib/mistral-client.js";import { executeTool } from "../lib/tools.js";import type { ChatMessage, ToolDefinition, ServiceHealth, SupportToolName } from "../lib/types.js";import pLimit from "p-limit";class SupportOrchestrator { private history = new Map<string, ChatMessage[]>(); private limiter: ReturnType<typeof pLimit>; constructor( private client: MistralClient, private toolExec: typeof executeTool, maxConcurrency
Expected output: A SupportOrchestrator with a concurrency limiter (pLimit(10)) that processes messages in a tool-call loop. When Mistral returns toolCalls, the orchestrator executes each one via executeTool(). If the breaker is open, it returns a user-friendly fallback message instead of a raw error.
Step 6: Create the health API endpoint
Create app/api/health/route.ts. This endpoint reads circuit breaker states, generates Kubernetes probe YAML and health check endpoints using @reaatech/agent-runbook-health-checks, and returns a comprehensive status report.
Expected output: A GET /api/health route that returns overall system status and circuit breaker states. When all breakers are CLOSED, status is "healthy". When at least one is OPEN, status is "degraded". When all are OPEN, status is "unhealthy". Any exception returns a 500 with "unhealthy".
Step 7: Create the tool execution API endpoint
Create app/api/tools/route.ts. This is the public POST endpoint that external callers hit to execute support tools through the reliability layer.
Expected output: A POST /api/tools route that validates input with Zod, calls executeTool(), and maps responses to proper HTTP status codes: 200 on success, 400 on invalid input, 503 when the circuit is open (with a Retry-After header), and 409 on idempotency conflicts.
Step 8: Add instrumentation for startup initialization
Create src/instrumentation.ts. Next.js’s experimental.instrumentationHook feature calls register() once at server startup. You’ll use it to initialize Langfuse and connect the circuit breaker adapters.
Expected output: A register() function that only runs in the Node.js runtime (not Edge). It dynamically imports langfuse and ./lib/tools.js inside the guard so Edge bundles don’t include Node-only dependencies. The SIGTERM handler ensures Langfuse flushes pending traces before shutdown.
Now enable the instrumentation hook in next.config.ts — replace its contents with:
ts
import type { NextConfig } from "next";const nextConfig: NextConfig = { experimental: { instrumentationHook: true, },};export default nextConfig;
Expected output: The exact key experimental.instrumentationHook: true — without it, register() is dead code and the circuit breakers never initialize.
Step 9: Create the Trigger.dev trip handler
Create src/triggers/trip-handler.ts. When a circuit breaker transitions to OPEN, this Trigger.dev task generates incident workflows, creates escalation policies, applies communication templates, assembles a full runbook, and performs a fallback model smoke test.
Expected output: A Trigger.dev task registered with id: "circuit-trip-handler". The runCircuitTripHandler function validates the payload with Zod, then calls all five REAA packages — incident workflows, escalation policies, communication templates, runbook artifacts, and completeness validation — plus a Mistral chat call to smoke-test the fallback model.
Step 10: Set up the barrel exports
Replace the scaffolded src/index.ts with focused re-exports so consumers can import the whole reliability suite from a single entry point:
ts
export { MistralClient, mistralClient } from "./lib/mistral-client.js";export { executeTool, getCircuitStatuses, initTools } from "./lib/tools.js";export { SupportOrchestrator, orchestrator } from "./services/support-orchestrator.js";export { generateHealthChecks, identifyHealthChecks, generateKubernetesProbeYaml, generateHealthCheckEndpoint,} from "@reaatech/agent-runbook-health-checks";export type { SupportToolName, SupportToolResult, CircuitTripEvent, HealthCheckStatus, ServiceHealth,} from "./lib/types.js";
Expected output: A barrel module that re-exports 4 internal modules and the health-checks package. Every consumer can now import { mistralClient, executeTool, orchestrator } from "./src/index.js".
Step 11: Configure environment variables
Create .env.example with all required variables:
env
# Env vars used by mistral-ai-agent-reliability-suite-for-smb-customer-support.# The builder adds entries here as it wires up each integration.# Keep placeholders only — never commit real values.NODE_ENV=development# Mistral AIMISTRAL_API_KEY=<your-mistral-key># Trigger.devTRIGGER_API_KEY=<your-trigger-key>TRIGGER_PROJECT_ID=<your-project-id># Langfuse observabilityLANGFUSE_PUBLIC_KEY=<your-langfuse-public-key>LANGFUSE_SECRET_KEY=<your-langfuse-secret-key>LANGFUSE_HOST=https://cloud.langfuse.com# Downstream support tool APIsCRM_API_URL=<your-crm-api-base-url>ORDER_API_URL=<your-order-api-base-url>RETURN_PORTAL_URL=<your-return-portal-url>REFUND_API_URL=<your-refund-api-url># Fallback model for degraded modeFALLBACK_MODEL=mistral-medium-latest
Copy to .env and fill in real values:
terminal
cp .env.example .env
Expected output: An .env.example with 11 placeholder entries. The FALLBACK_MODEL defaults to mistral-medium-latest — this is what the trip handler uses to smoke-test the fallback path.
Step 12: Run the test suite
The project includes a comprehensive test suite using Vitest + MSW. Global mocks live in tests/setup.ts — they mock all four external tool URLs and the Mistral API endpoint. Each test file covers happy paths, error paths, and boundaries.
Verify everything compiles and passes:
terminal
pnpm typecheckpnpm lintpnpm test
Expected output:pnpm typecheck exits 0 with no TypeScript errors. pnpm lint exits 0. pnpm test runs Vitest with coverage — all tests pass, with thresholds at 95% lines, 85% branches, 90% functions.
Next steps
Add Redis-backed persistence — swap InMemoryAdapter for RedisAdapter from @reaatech/circuit-breaker-persistence and MemoryAdapter for the Redis idempotency adapter so circuit states survive process restarts and scale across multiple instances
Wire the Trigger.dev job to real events — emit the circuit/trip custom event from executeTool() when the breaker opens, then start the Trigger.dev dev server with npx trigger.dev dev to see incident workflows generate on a real trip
Extend the tool set — add more support tools (inventory lookup, shipping tracker, billing history) by adding them to the SupportToolName union, the URL_MAP, and the orchestrator’s toolDefinitions array