Skip to main content

Tool Caching

RadarOS supports result caching for tools. When a tool is called with the same arguments within the TTL window, the cached result is returned instantly — no re-execution.

Quick Start

import { Agent, openai, defineTool } from "@radaros/core";
import { z } from "zod";

const weatherTool = defineTool({
  name: "getWeather",
  description: "Get current weather for a city",
  parameters: z.object({
    city: z.string().describe("City name"),
  }),
  execute: async ({ city }) => {
    const res = await fetch(`https://api.weather.com/${city}`);
    return await res.text();
  },
  cache: { ttl: 60_000 }, // Cache for 1 minute
});

const agent = new Agent({
  name: "WeatherBot",
  model: openai("gpt-4o"),
  tools: [weatherTool],
});
If the agent calls getWeather("Mumbai") twice within 60 seconds, the second call returns the cached result without hitting the API.

Configuration

Add a cache property to any tool definition:
cache.ttl
number
required
Time-to-live in milliseconds. Cached results expire after this duration. Example: 60_000 for 1 minute, 300_000 for 5 minutes.

How It Works

  1. When a tool is called, the ToolExecutor generates a cache key from the tool name and a stable serialization of the arguments.
  2. If a valid (non-expired) cache entry exists for that key, the result is returned immediately.
  3. If no cache entry exists, the tool executes normally and the result is stored with an expiry timestamp.
Tool Call → Cache Lookup
  ├─ HIT  → Return cached result (skip execution)
  └─ MISS → Execute tool → Store result → Return

Cache Key Strategy

Cache keys are generated as toolName:stableStringify(args) — arguments are sorted by key to ensure consistent hashing regardless of property order.
// These produce the same cache key:
getWeather({ city: "Mumbai", unit: "celsius" })
getWeather({ unit: "celsius", city: "Mumbai" })

Clearing the Cache

Tool result caches are scoped to each ToolExecutor instance, which is recreated per request when using toolRouter. For agents without a tool router, the cache persists across runs on the same agent instance. Cache entries expire automatically based on ttl. To manually clear all cached results, create a fresh agent or call setTools() to rebuild the tool executor.

When to Use

Good Candidates

  • External API calls (weather, exchange rates)
  • Database lookups with stable results
  • Expensive computations
  • Rate-limited APIs

Poor Candidates

  • Tools with side effects (send email, write file)
  • Real-time data that changes every second
  • Tools where arguments include timestamps

ToolCacheConfig Type

interface ToolCacheConfig {
  ttl: number; // milliseconds
}

Logging

When logLevel is "info" or higher, cached tool results are prefixed with [cached] in the logs, making it easy to verify caching behavior during development.

Cache Behavior Example

When the same tool is called with identical arguments within the TTL window, the cached result is returned instantly without re-executing:
const weatherTool = defineTool({
  name: "getWeather",
  description: "Get current weather",
  parameters: z.object({ city: z.string() }),
  execute: async ({ city }) => {
    console.log(`[API CALL] Fetching weather for ${city}`);
    const data = await weatherAPI.get(city);
    return `${city}: ${data.temp}°C, ${data.condition}`;
  },
  cache: { ttl: 60_000 }, // 1 minute
});

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  tools: [weatherTool],
});

// First call — executes the tool, logs "[API CALL]"
await agent.run("What's the weather in Tokyo?");

// Second call within 60s — returns cached result, no API call
await agent.run("Tell me Tokyo's weather again");

// After 60s — cache expired, executes again

Different TTLs per Tool

Each tool can have its own cache duration based on how frequently the data changes:
const tools = [
  defineTool({
    name: "getExchangeRate",
    description: "Get currency exchange rate",
    parameters: z.object({ from: z.string(), to: z.string() }),
    execute: async ({ from, to }) => { /* ... */ },
    cache: { ttl: 300_000 }, // 5 minutes — rates change slowly
  }),
  defineTool({
    name: "getStockPrice",
    description: "Get current stock price",
    parameters: z.object({ symbol: z.string() }),
    execute: async ({ symbol }) => { /* ... */ },
    cache: { ttl: 30_000 }, // 30 seconds — prices change fast
  }),
  defineTool({
    name: "getCurrentTime",
    description: "Get the current date and time",
    parameters: z.object({}),
    execute: async () => new Date().toISOString(),
    // No cache — always fresh
  }),
];