@reaatech/structured-function-calling-ai-engine
Execution engine with retry, circuit breakers, and telemetry hooks wrapping a tool registry.
Installation
pnpm add @reaatech/structured-function-calling-ai-engineAPI
ToolExecutor
class ToolExecutor {
constructor(registry: ToolRegistry, middlewares?: Middleware[]);
execute(
name: string,
input: Record<string, unknown>,
opts?: ExecuteOptions,
): Promise<ExecutionResult>;
}Resolves a tool by name from the registry, parses input against its Zod schema, runs the middleware pipeline (onBefore → execute → onAfter; onError on exception), and returns an ExecutionResult.
ExecuteOptions
interface ExecuteOptions {
signal?: AbortSignal;
}Currently accepts an AbortSignal; in-flight execution is not yet abortable.
Middleware
interface Middleware {
onBefore?(ctx: ExecutionContext): Promise<void>;
onAfter?(ctx: ExecutionContext, result: ExecutionResult): Promise<void>;
onError?(ctx: ExecutionContext, error: Error): Promise<void>;
}Composable hooks. All three methods are optional. onBefore runs before execution; onAfter runs after a successful result; onError runs when execution throws.
ExecutionContext
interface ExecutionContext {
toolName: string;
input: Record<string, unknown>;
startTime: number;
attempt: number;
}ExecutionResult
interface ExecutionResult {
success: boolean;
data?: unknown;
error?: Error;
attempts: number;
durationMs: number;
}createRetryMiddleware
function createRetryMiddleware(opts?: RetryOptions): Middleware;Tracks attempt count in the ExecutionContext. Used internally by withRetry. This middleware does not itself implement the retry loop — use withRetry for a retrying executor.
withRetry
function withRetry(executor: ToolExecutor, opts?: RetryOptions): ToolExecutor;Wraps a ToolExecutor in a retry loop. Uses exponential backoff (baseDelayMs * 2^attempt). Attempts to repair partial JSON responses using partial-json. Retries on non-success results whose error message matches partial-JSON patterns. Returns a Proxy of the original executor with execute replaced.
RetryOptions
interface RetryOptions {
maxAttempts?: number; // default: 3
baseDelayMs?: number; // default: 200
}CircuitBreaker
class CircuitBreaker {
constructor(opts?: { threshold?: number; resetTimeoutMs?: number });
async call<T>(toolName: string, fn: () => Promise<T>): Promise<T>;
recordSuccess(toolName: string): void;
recordFailure(toolName: string): void;
getState(toolName: string): 'closed' | 'open' | 'half-open';
}Per-tool in-memory circuit breaker. Default threshold: 5 failures. Default reset timeout: 30 s. Transitions: closed → open (after threshold failures) → half-open (after resetTimeoutMs) → closed (probe succeeds) or open (probe fails).
createCircuitBreakerMiddleware
function createCircuitBreakerMiddleware(opts?: CircuitBreakerOptions): Middleware;Throws CircuitBreakerOpenError when the circuit is open, records success on onAfter, records failure on onError.
CircuitBreakerOptions
interface CircuitBreakerOptions {
threshold?: number; // default: 5
resetTimeoutMs?: number; // default: 30000
}createTelemetryMiddleware
function createTelemetryMiddleware(logger?: pino.Logger): Middleware;Logs onBefore, onAfter, and onError lifecycle events via pino. Silently skips all logging when logger is undefined.
ExecutionError
class ExecutionError extends Error {
readonly toolName: string;
readonly cause: Error;
}CircuitBreakerOpenError
class CircuitBreakerOpenError extends Error;Usage example
import { z } from 'zod/v3';
import { ToolRegistry } from '@reaatech/structured-function-calling-ai-core';
import {
ToolExecutor,
withRetry,
createTelemetryMiddleware,
createCircuitBreakerMiddleware,
} from '@reaatech/structured-function-calling-ai-engine';
import pino from 'pino';
const schema = z.object({ city: z.string() });
const registry = new ToolRegistry();
registry.register({
name: 'get_weather',
description: 'Get weather for a city',
schema,
execute: async (input) => ({ temp: 22, conditions: 'sunny' }),
});
const logger = pino({ level: 'info' });
const baseExecutor = new ToolExecutor(registry, [
createTelemetryMiddleware(logger),
createCircuitBreakerMiddleware({ threshold: 3 }),
]);
const executor = withRetry(baseExecutor, { maxAttempts: 3, baseDelayMs: 200 });
const result = await executor.execute('get_weather', { city: 'NYC' });Dependencies
@reaatech/structured-function-calling-ai-core(workspace:*)partial-json0.1.7pino10.3.1
