@@ -56,6 +56,7 @@ SQLITE_EXTENSION_INIT1
5656#define DBMEM_SETTINGS_KEY_TEXT_WEIGHT "text_weight"
5757#define DBMEM_SETTINGS_KEY_MIN_SCORE "min_score"
5858#define DBMEM_SETTINGS_KEY_UPDATE_ACCESS "update_access"
59+ #define DBMEM_SETTINGS_KEY_EMBEDDING_CACHE "embedding_cache"
5960
6061// default values from https://docs.openclaw.ai/concepts/memory
6162#define DEFAULT_CHARS_PER_TOKEN 4 // Approximate number of characters per token (GPT ≈ 4, Claude ≈ 3.5)
@@ -104,7 +105,12 @@ struct dbmem_context {
104105 double text_weight ; // Weight of the FTS results during the merge of the result
105106 double min_score ; // Minimum score threshold to filter irrelevant results
106107 bool update_access ; // Whether to update last_accessed on search
107-
108+ bool embedding_cache ; // Enable/disable embedding cache (default: true)
109+
110+ // Cache
111+ float * cache_buffer ; // Reusable buffer for cache hits
112+ int cache_buffer_size ; // Allocated size in floats
113+
108114 // Runtime state
109115 int64_t counter ; // Chunk counter during file processing
110116 uint64_t hash ; // Hash of the current text
@@ -242,6 +248,12 @@ static int dbmem_settings_sync (dbmem_context *ctx, const char *key, sqlite3_val
242248 return 0 ;
243249 }
244250
251+ if (strcasecmp (key , DBMEM_SETTINGS_KEY_EMBEDDING_CACHE ) == 0 ) {
252+ int n = sqlite3_value_int (value );
253+ ctx -> embedding_cache = (n > 0 ) ? 1 : 0 ;
254+ return 0 ;
255+ }
256+
245257 if (strcasecmp (key , DBMEM_SETTINGS_KEY_PROVIDER ) == 0 ) {
246258 char * provider = dbmem_strdup ((const char * )sqlite3_value_text (value ));
247259 if (provider ) {
@@ -308,6 +320,10 @@ static int dbmem_database_init (sqlite3 *db) {
308320 rc = sqlite3_exec (db , sql , NULL , NULL , NULL );
309321 if (rc != SQLITE_OK ) return rc ;
310322
323+ sql = "CREATE TABLE IF NOT EXISTS dbmem_cache (text_hash INTEGER NOT NULL, provider TEXT NOT NULL, model TEXT NOT NULL, embedding BLOB NOT NULL, dimension INTEGER NOT NULL, PRIMARY KEY (text_hash, provider, model));" ;
324+ rc = sqlite3_exec (db , sql , NULL , NULL , NULL );
325+ if (rc != SQLITE_OK ) return rc ;
326+
311327 sql = "CREATE VIRTUAL TABLE IF NOT EXISTS dbmem_vault_fts USING fts5 (content, hash UNINDEXED, seq UNINDEXED, context UNINDEXED);" ;
312328 rc = sqlite3_exec (db , sql , NULL , NULL , NULL );
313329 if (rc != SQLITE_OK ) {
@@ -514,6 +530,7 @@ static void *dbmem_context_create (sqlite3 *db) {
514530 ctx -> text_weight = DEFAULT_TEXT_WEIGHT ;
515531 ctx -> min_score = DEFAULT_MIN_SCORE ;
516532 ctx -> update_access = true;
533+ ctx -> embedding_cache = true;
517534
518535 return (void * )ctx ;
519536}
@@ -526,6 +543,7 @@ static void dbmem_context_free (void *ptr) {
526543 if (ctx -> model ) dbmem_free (ctx -> model );
527544 if (ctx -> api_key ) dbmem_free (ctx -> api_key );
528545 if (ctx -> extensions ) dbmem_free (ctx -> extensions );
546+ if (ctx -> cache_buffer ) dbmem_free (ctx -> cache_buffer );
529547
530548 #ifndef DBMEM_OMIT_LOCAL_ENGINE
531549 if (ctx -> l_engine ) dbmem_local_engine_free (ctx -> l_engine );
@@ -776,6 +794,44 @@ static void dbmem_clear (sqlite3_context *context, int argc, sqlite3_value **arg
776794 sqlite3_result_error (context , sqlite3_errmsg (db ), -1 );
777795}
778796
797+ // MARK: - Cache Clear -
798+
799+ static void dbmem_cache_clear (sqlite3_context * context , int argc , sqlite3_value * * argv ) {
800+ sqlite3 * db = sqlite3_context_db_handle (context );
801+ int rc ;
802+
803+ if (argc == 0 ) {
804+ rc = sqlite3_exec (db , "DELETE FROM dbmem_cache;" , NULL , NULL , NULL );
805+ } else if (argc == 2 ) {
806+ if (sqlite3_value_type (argv [0 ]) != SQLITE_TEXT || sqlite3_value_type (argv [1 ]) != SQLITE_TEXT ) {
807+ sqlite3_result_error (context , "The function memory_cache_clear expects two arguments of type TEXT (provider, model)" , SQLITE_ERROR );
808+ return ;
809+ }
810+ const char * provider = (const char * )sqlite3_value_text (argv [0 ]);
811+ const char * model = (const char * )sqlite3_value_text (argv [1 ]);
812+
813+ sqlite3_stmt * vm = NULL ;
814+ rc = sqlite3_prepare_v2 (db , "DELETE FROM dbmem_cache WHERE provider=?1 AND model=?2;" , -1 , & vm , NULL );
815+ if (rc == SQLITE_OK ) {
816+ sqlite3_bind_text (vm , 1 , provider , -1 , SQLITE_STATIC );
817+ sqlite3_bind_text (vm , 2 , model , -1 , SQLITE_STATIC );
818+ rc = sqlite3_step (vm );
819+ if (rc == SQLITE_DONE ) rc = SQLITE_OK ;
820+ }
821+ if (vm ) sqlite3_finalize (vm );
822+ } else {
823+ sqlite3_result_error (context , "The function memory_cache_clear expects 0 or 2 arguments" , SQLITE_ERROR );
824+ return ;
825+ }
826+
827+ if (rc != SQLITE_OK ) {
828+ sqlite3_result_error (context , sqlite3_errmsg (db ), -1 );
829+ return ;
830+ }
831+
832+ sqlite3_result_int (context , sqlite3_changes (db ));
833+ }
834+
779835// MARK: - General -
780836
781837static void dbmem_version (sqlite3_context * context , int argc , sqlite3_value * * argv ) {
@@ -970,40 +1026,123 @@ static void dbmem_dump_embeding (const embedding_result_t *result) {
9701026}
9711027#endif
9721028
1029+ // MARK: - Embedding Cache -
1030+
1031+ static bool dbmem_cache_lookup (dbmem_context * ctx , uint64_t text_hash , embedding_result_t * result ) {
1032+ static const char * sql = "SELECT embedding, dimension FROM dbmem_cache WHERE text_hash=?1 AND provider=?2 AND model=?3 LIMIT 1;" ;
1033+
1034+ if (!ctx -> provider || !ctx -> model ) return false;
1035+
1036+ bool found = false;
1037+ sqlite3_stmt * vm = NULL ;
1038+ int rc = sqlite3_prepare_v2 (ctx -> db , sql , -1 , & vm , NULL );
1039+ if (rc != SQLITE_OK ) goto cleanup ;
1040+
1041+ sqlite3_bind_int64 (vm , 1 , (sqlite3_int64 )text_hash );
1042+ sqlite3_bind_text (vm , 2 , ctx -> provider , -1 , SQLITE_STATIC );
1043+ sqlite3_bind_text (vm , 3 , ctx -> model , -1 , SQLITE_STATIC );
1044+
1045+ rc = sqlite3_step (vm );
1046+ if (rc != SQLITE_ROW ) goto cleanup ;
1047+
1048+ int dimension = sqlite3_column_int (vm , 1 );
1049+ int blob_bytes = sqlite3_column_bytes (vm , 0 );
1050+ const void * blob = sqlite3_column_blob (vm , 0 );
1051+
1052+ if (blob_bytes != dimension * (int )sizeof (float )) goto cleanup ;
1053+
1054+ // ensure cache_buffer is large enough
1055+ if (ctx -> cache_buffer_size < dimension ) {
1056+ float * new_buf = (float * )dbmem_realloc (ctx -> cache_buffer , dimension * sizeof (float ));
1057+ if (!new_buf ) goto cleanup ;
1058+ ctx -> cache_buffer = new_buf ;
1059+ ctx -> cache_buffer_size = dimension ;
1060+ }
1061+
1062+ memcpy (ctx -> cache_buffer , blob , blob_bytes );
1063+ result -> embedding = ctx -> cache_buffer ;
1064+ result -> n_embd = dimension ;
1065+ result -> n_tokens = 0 ;
1066+ result -> n_tokens_truncated = 0 ;
1067+ found = true;
1068+
1069+ cleanup :
1070+ if (vm ) sqlite3_finalize (vm );
1071+ return found ;
1072+ }
1073+
1074+ static void dbmem_cache_store (dbmem_context * ctx , uint64_t text_hash , const embedding_result_t * result ) {
1075+ static const char * sql = "INSERT OR REPLACE INTO dbmem_cache (text_hash, provider, model, embedding, dimension) VALUES (?1, ?2, ?3, ?4, ?5);" ;
1076+
1077+ if (!ctx -> provider || !ctx -> model ) return ;
1078+
1079+ sqlite3_stmt * vm = NULL ;
1080+ int rc = sqlite3_prepare_v2 (ctx -> db , sql , -1 , & vm , NULL );
1081+ if (rc != SQLITE_OK ) goto cleanup ;
1082+
1083+ sqlite3_bind_int64 (vm , 1 , (sqlite3_int64 )text_hash );
1084+ sqlite3_bind_text (vm , 2 , ctx -> provider , -1 , SQLITE_STATIC );
1085+ sqlite3_bind_text (vm , 3 , ctx -> model , -1 , SQLITE_STATIC );
1086+ sqlite3_bind_blob (vm , 4 , result -> embedding , result -> n_embd * (int )sizeof (float ), SQLITE_STATIC );
1087+ sqlite3_bind_int (vm , 5 , result -> n_embd );
1088+
1089+ sqlite3_step (vm );
1090+
1091+ cleanup :
1092+ if (vm ) sqlite3_finalize (vm );
1093+ }
1094+
1095+ // MARK: -
1096+
9731097static int dbmem_process_callback (const char * text , size_t len , size_t offset , size_t length , void * xdata , size_t index ) {
9741098 dbmem_context * ctx = (dbmem_context * )xdata ;
9751099 embedding_result_t result = {0 };
9761100 int rc = SQLITE_OK ;
977-
978- // compute embedding
979- if (ctx -> is_local ) {
980- #ifndef DBMEM_OMIT_LOCAL_ENGINE
981- rc = dbmem_local_compute_embedding (ctx -> l_engine , text , (int )len , & result );
982- if (rc != 0 ) {
983- const char * err = dbmem_local_errmsg (ctx -> l_engine );
1101+
1102+ // check embedding cache
1103+ uint64_t chunk_hash = 0 ;
1104+ bool cache_hit = false;
1105+ if (ctx -> embedding_cache ) {
1106+ chunk_hash = dbmem_hash_compute (text , len );
1107+ cache_hit = dbmem_cache_lookup (ctx , chunk_hash , & result );
1108+ }
1109+
1110+ if (!cache_hit ) {
1111+ // compute embedding
1112+ if (ctx -> is_local ) {
1113+ #ifndef DBMEM_OMIT_LOCAL_ENGINE
1114+ rc = dbmem_local_compute_embedding (ctx -> l_engine , text , (int )len , & result );
1115+ if (rc != 0 ) {
1116+ const char * err = dbmem_local_errmsg (ctx -> l_engine );
1117+ memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
1118+ return rc ;
1119+ }
1120+ #else
1121+ const char * err = "Local embedding cannot be computed because extension was compiled without local engine support" ;
9841122 memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
985- return rc ;
1123+ return 1 ;
1124+ #endif
9861125 }
987- #else
988- const char * err = "Local embedding cannot be computed because extension was compiled without local engine support" ;
989- memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
990- return 1 ;
991- #endif
992- }
993-
994- if (!ctx -> is_local ) {
995- #ifndef DBMEM_OMIT_REMOTE_ENGINE
996- rc = dbmem_remote_compute_embedding (ctx -> r_engine , text , (int )len , & result );
997- if (rc != 0 ) {
998- const char * err = dbmem_remote_errmsg (ctx -> r_engine );
1126+
1127+ if (!ctx -> is_local ) {
1128+ #ifndef DBMEM_OMIT_REMOTE_ENGINE
1129+ rc = dbmem_remote_compute_embedding (ctx -> r_engine , text , (int )len , & result );
1130+ if (rc != 0 ) {
1131+ const char * err = dbmem_remote_errmsg (ctx -> r_engine );
1132+ memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
1133+ return rc ;
1134+ }
1135+ #else
1136+ const char * err = "Remote embedding cannot be computed because extension was compiled without remote engine support" ;
9991137 memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
1000- return rc ;
1138+ return 1 ;
1139+ #endif
1140+ }
1141+
1142+ // store in cache on miss
1143+ if (ctx -> embedding_cache ) {
1144+ dbmem_cache_store (ctx , chunk_hash , & result );
10011145 }
1002- #else
1003- const char * err = "Remote embedding cannot be computed because extension was compiled without remote engine support" ;
1004- memcpy (ctx -> error_msg , err , strlen (err ) + 1 );
1005- return 1 ;
1006- #endif
10071146 }
10081147
10091148 // make sure dimension is the same
@@ -1283,6 +1422,12 @@ SQLITE_DBMEMORY_API int sqlite3_memory_init (sqlite3 *db, char **pzErrMsg, const
12831422 rc = sqlite3_create_function_v2 (db , "memory_clear" , 0 , SQLITE_UTF8 , ctx , dbmem_clear , NULL , NULL , NULL );
12841423 if (rc != SQLITE_OK ) return rc ;
12851424
1425+ rc = sqlite3_create_function_v2 (db , "memory_cache_clear" , 0 , SQLITE_UTF8 , ctx , dbmem_cache_clear , NULL , NULL , NULL );
1426+ if (rc != SQLITE_OK ) return rc ;
1427+
1428+ rc = sqlite3_create_function_v2 (db , "memory_cache_clear" , 2 , SQLITE_UTF8 , ctx , dbmem_cache_clear , NULL , NULL , NULL );
1429+ if (rc != SQLITE_OK ) return rc ;
1430+
12861431 rc = dbmem_register_search (db , ctx , pzErrMsg );
12871432 if (rc != SQLITE_OK ) return rc ;
12881433
0 commit comments