@@ -2130,7 +2130,7 @@ def cache_put(
21302130 ttl_seconds : int = 0
21312131 ) -> bool :
21322132 """
2133- Store a value in the semantic cache with its embedding (Embedded FFI mode) .
2133+ Store a value in the semantic cache with its embedding.
21342134
21352135 Args:
21362136 cache_name: Name of the cache
@@ -2153,25 +2153,47 @@ def cache_put(
21532153 """
21542154 self ._check_open ()
21552155
2156- import ctypes
2157- import numpy as np
2158-
2159- # Convert embedding to float32 array
2160- emb_array = np .array (embedding , dtype = np .float32 )
2161- emb_ptr = emb_array .ctypes .data_as (ctypes .POINTER (ctypes .c_float ))
2162-
2163- result = _FFI .lib .toondb_cache_put (
2164- self ._ptr ,
2165- cache_name .encode ("utf-8" ),
2166- key .encode ("utf-8" ),
2167- value .encode ("utf-8" ),
2168- emb_ptr ,
2169- len (embedding ),
2170- ttl_seconds
2171- )
2156+ # Try FFI first if available
2157+ try :
2158+ if hasattr (_FFI , 'lib' ) and _FFI .lib is not None and hasattr (self , '_ptr' ) and self ._ptr is not None :
2159+ import ctypes
2160+ import numpy as np
2161+
2162+ emb_array = np .array (embedding , dtype = np .float32 )
2163+ emb_ptr = emb_array .ctypes .data_as (ctypes .POINTER (ctypes .c_float ))
2164+
2165+ result = _FFI .lib .toondb_cache_put (
2166+ self ._ptr ,
2167+ cache_name .encode ("utf-8" ),
2168+ key .encode ("utf-8" ),
2169+ value .encode ("utf-8" ),
2170+ emb_ptr ,
2171+ len (embedding ),
2172+ ttl_seconds
2173+ )
2174+
2175+ if result == 0 :
2176+ return True
2177+ except (AttributeError , OSError , TypeError ):
2178+ pass # Fall through to KV fallback
21722179
2173- if result != 0 :
2174- raise DatabaseError (f"Failed to cache put: error code { result } " )
2180+ # KV fallback - store as JSON
2181+ import json
2182+ import time
2183+ import hashlib
2184+
2185+ # Create unique cache entry key
2186+ key_hash = hashlib .md5 (key .encode ()).hexdigest ()[:12 ]
2187+ cache_key = f"_cache/{ cache_name } /{ key_hash } " .encode ()
2188+
2189+ entry = {
2190+ "key" : key ,
2191+ "value" : value ,
2192+ "embedding" : embedding ,
2193+ "ttl" : ttl_seconds ,
2194+ "created" : time .time ()
2195+ }
2196+ self .put (cache_key , json .dumps (entry ).encode ())
21752197 return True
21762198
21772199 def cache_get (
@@ -2181,7 +2203,7 @@ def cache_get(
21812203 threshold : float = 0.85
21822204 ) -> Optional [str ]:
21832205 """
2184- Look up a value in the semantic cache by embedding similarity (Embedded FFI mode) .
2206+ Look up a value in the semantic cache by embedding similarity.
21852207
21862208 Args:
21872209 cache_name: Name of the cache
@@ -2202,30 +2224,77 @@ def cache_get(
22022224 """
22032225 self ._check_open ()
22042226
2205- import ctypes
2206- import numpy as np
2207-
2208- # Convert embedding to float32 array
2209- emb_array = np .array (query_embedding , dtype = np .float32 )
2210- emb_ptr = emb_array .ctypes .data_as (ctypes .POINTER (ctypes .c_float ))
2211- out_len = ctypes .c_size_t ()
2212-
2213- result_ptr = _FFI .lib .toondb_cache_get (
2214- self ._ptr ,
2215- cache_name .encode ("utf-8" ),
2216- emb_ptr ,
2217- len (query_embedding ),
2218- threshold ,
2219- ctypes .byref (out_len )
2220- )
2227+ # Try FFI first if available
2228+ try :
2229+ if hasattr (_FFI , 'lib' ) and _FFI .lib is not None and hasattr (self , '_ptr' ) and self ._ptr is not None :
2230+ import ctypes
2231+ import numpy as np
2232+
2233+ emb_array = np .array (query_embedding , dtype = np .float32 )
2234+ emb_ptr = emb_array .ctypes .data_as (ctypes .POINTER (ctypes .c_float ))
2235+ out_len = ctypes .c_size_t ()
2236+
2237+ result_ptr = _FFI .lib .toondb_cache_get (
2238+ self ._ptr ,
2239+ cache_name .encode ("utf-8" ),
2240+ emb_ptr ,
2241+ len (query_embedding ),
2242+ threshold ,
2243+ ctypes .byref (out_len )
2244+ )
2245+
2246+ if result_ptr is not None :
2247+ try :
2248+ return ctypes .c_char_p (result_ptr ).value .decode ("utf-8" )
2249+ finally :
2250+ _FFI .lib .toondb_free_string (result_ptr )
2251+ except (AttributeError , OSError , TypeError ):
2252+ pass # Fall through to KV fallback
2253+
2254+ # KV fallback - scan and compute similarity
2255+ import json
2256+ import math
2257+ import time
22212258
2222- if result_ptr is None :
2223- return None # Cache miss
2259+ prefix = f"_cache/{ cache_name } /" .encode ()
2260+ best_match = None
2261+ best_score = 0.0
22242262
22252263 try :
2226- return ctypes .c_char_p (result_ptr ).value .decode ("utf-8" )
2227- finally :
2228- _FFI .lib .toondb_free_string (result_ptr )
2264+ with self .transaction () as txn :
2265+ for k , v in txn .scan_prefix (prefix ):
2266+ try :
2267+ entry = json .loads (v .decode ())
2268+
2269+ # Check TTL
2270+ if entry .get ("ttl" , 0 ) > 0 :
2271+ if time .time () - entry .get ("created" , 0 ) > entry ["ttl" ]:
2272+ continue # Expired
2273+
2274+ # Compute cosine similarity
2275+ cached_emb = entry .get ("embedding" , [])
2276+ if len (cached_emb ) != len (query_embedding ):
2277+ continue
2278+
2279+ # Cosine similarity
2280+ dot_product = sum (q * c for q , c in zip (query_embedding , cached_emb ))
2281+ query_norm = math .sqrt (sum (x * x for x in query_embedding ))
2282+ cached_norm = math .sqrt (sum (x * x for x in cached_emb ))
2283+
2284+ if query_norm > 0 and cached_norm > 0 :
2285+ score = dot_product / (query_norm * cached_norm )
2286+ else :
2287+ score = 0.0
2288+
2289+ if score >= threshold and score > best_score :
2290+ best_match = entry .get ("value" )
2291+ best_score = score
2292+ except (json .JSONDecodeError , KeyError ):
2293+ continue
2294+ except Exception :
2295+ pass # Return None on any error
2296+
2297+ return best_match
22292298
22302299 # =========================================================================
22312300 # Trace Operations (FFI) - Observability
0 commit comments