finagent / tools /cache.py
emmanuelakbi's picture
Deploy FinAgent: multi-agent trading signals powered by Qwen on AMD MI300X
07ff2cb
raw
history blame
3.4 kB
"""
TTL Cache module for Agent Tools.
Provides an in-memory cache with time-to-live eviction to avoid
redundant API calls and rate limiting.
"""
import json
import threading
import time
from dataclasses import dataclass
from typing import Optional
@dataclass
class CacheEntry:
"""A single cache entry with its stored value and creation timestamp."""
value: str
timestamp: float
class TTLCache:
"""In-memory cache with time-to-live eviction.
Stores string results keyed by function name + parameters.
Thread-safe via threading.Lock on all read/write operations.
"""
def __init__(self, default_ttl: int = 300, max_age: int = 900):
"""Initialize the cache.
Args:
default_ttl: Time-to-live in seconds (default 300 = 5 minutes).
Entries older than this are considered expired on get().
max_age: Maximum entry age before forced eviction (default 900 = 15 minutes).
Entries older than this are removed during eviction sweeps.
"""
self.default_ttl = default_ttl
self.max_age = max_age
self._store: dict[str, CacheEntry] = {}
self._lock = threading.Lock()
def make_key(self, func_name: str, **kwargs) -> str:
"""Generate a deterministic cache key from function name and parameters.
Uses sorted JSON serialization to ensure the same parameters always
produce the same key regardless of argument order.
Args:
func_name: The name of the tool function.
**kwargs: The parameters passed to the function.
Returns:
A string key in the format "func_name:{sorted_params_json}".
"""
sorted_params_json = json.dumps(kwargs, sort_keys=True)
return f"{func_name}:{sorted_params_json}"
def get(self, key: str) -> Optional[str]:
"""Return cached value if it exists and is within TTL, else None.
Always triggers _evict_stale() to clean up old entries.
Args:
key: The cache key to look up.
Returns:
The cached string value if valid, or None if expired/missing.
"""
with self._lock:
self._evict_stale()
entry = self._store.get(key)
if entry is None:
return None
if time.time() - entry.timestamp > self.default_ttl:
return None
return entry.value
def set(self, key: str, value: str) -> None:
"""Store a value in the cache with the current timestamp.
Args:
key: The cache key.
value: The string value to cache.
"""
with self._lock:
self._store[key] = CacheEntry(value=value, timestamp=time.time())
def _evict_stale(self) -> None:
"""Remove entries older than max_age (15 minutes by default).
This method is called internally and assumes the lock is already held.
"""
current_time = time.time()
stale_keys = [
key
for key, entry in self._store.items()
if current_time - entry.timestamp > self.max_age
]
for key in stale_keys:
del self._store[key]
def clear(self) -> None:
"""Remove all entries from the cache. Useful for testing."""
with self._lock:
self._store.clear()