Kmemo

A semantic cache with eleven lexical checks, because cosine similarity confuses 100 and 250 USD.

Gradleimplementation("io.github.nacode-studios:kmemo-core:2.1.0")
kmemo.kt
val cache = SemanticCache( embedder = Embedder { text -> openAi.embed(text) }, store = InMemoryStore(maxEntries = 10_000, ttl = 1.hours),)val answer = cache.getOrPut(prompt) { llm.complete(it) }// Every miss says why: threshold, or a check.when (val r = cache.lookup(prompt)) { is CacheLookup.Hit -> r.response is CacheLookup.Miss -> r.reason}
The product

A semantic cache for LLM calls. It recognises requests it has already answered even when they are worded differently, and rejects the ones where similarity is high but the right answer is not the same. The core compiles for the JVM, iOS, macOS, Linux, Windows, JS and WasmJS.

Semantic cacheWaiting
cachedConvert 100 USD to EUR
incomingConvert 250 USD to EUR
cosine similarity · threshold 0.950.000
Threshold-only cache
HIT

—

Kmemorunning lexical checks…
numbersunitsentitiestime referencesnegationsantonymsreversed comparisons
numbers · 100 ≠ 250
MISS· different numberscall the model

A wrong rejection costs one API call. A wrong acceptance costs a wrong answer.

the case that breaks semantic caches

An exact-match cache cannot tell that two wordings are the same question. A semantic cache embeds the request, finds the nearest one it has already handled and reuses that answer, which cuts model calls and latency. The known weakness of this approach is the false positive. «Convert 100 USD to EUR» and «Convert 250 USD to EUR» sit around 0.99 similarity on every major embedding model. No threshold separates them, because on that axis they are closer than plenty of legitimate rephrasings. A cache that trusts the threshold alone returns the wrong answer and raises nothing. Kmemo treats that case as the primary requirement. Similarity is only the first filter: candidates that clear it are then read as text by a chain of eleven lexical checks, which look for real differences in numbers, units, entities, time references, negations, antonyms, reversed comparisons and a clause one wording adds to narrow the question. An optional twelfth check reads the cached answer instead of the two questions alone, which is the only way to catch two honest rephrasings whose difference sits in the answer. The two errors do not cost the same. A wrong rejection costs one API call; a wrong acceptance costs a wrong answer. The defaults follow from that, and a check abstains when it is unsure. The similarity threshold has to be measured against the embedding model you actually use, so the library ships a calibrator for it. The numbers are measured on blind corpora, written before the checks and never used to tune them. A cache that trusts the threshold alone serves every false positive it is shown; Kmemo's checks cut that to roughly a third while keeping 88% of the legitimate rephrasings. The comparison against GPTCache is published together with its cost: that cross-encoder serves fewer of them, and refuses more than half the correct rephrasings to do it. Those three corpora share one weakness no process removes: the same person wrote the pairs and the checks, so they cover the edge cases somebody thought of rather than the ones that exist. Since version 2.1 there is a fourth corpus this project did not write. PAWS was built by Google Research in 2019 to measure whether a model can tell a rephrasing from a near-rephrasing when almost every word matches. On it the checks refuse 14% of the false positives and keep 79% of the rephrasings, far worse than the internal numbers. Two things pull in opposite directions and neither improves it: a corpus built to defeat lexical overlap is harder than real traffic, and its pairs are encyclopedia sentences where the checks read questions. It is still the only figure on this page nobody here could have tuned, which is why it is published beside the others. Since version 2.0 the core is no longer tied to the JVM. It compiles for iOS, macOS, Linux, Windows, JS and WasmJS without adding a single dependency. A mobile app pays for every call over a cellular network and a browser tool has no server to cache on, which are the deployments a JVM-only library never reaches. The Redis and PostgreSQL stores and the framework integrations stay on the JVM, where the drivers they wrap live.

Scope
Open sourceKotlinKotlin MultiplatformLLMCaching
Approach

What we did

01Design of the check chain and of the abstention model
02Migration of the core to Kotlin Multiplatform
03Threshold calibrator for the client's embedding model
04In-memory, Redis, PostgreSQL and HNSW stores
05Spring Boot, Spring AI, LangChain4j and Ktor integrations
Highlights
+Eleven lexical checks standing between the threshold and a false positive
+An optional check reads the cached answer, not only the two questions
+Every miss reports its cause: below threshold, or refused by a check
+A calibrator for the threshold on your own embedding model
+A multiplatform core: JVM, iOS, macOS, Linux, Windows, JS and WasmJS
+kmemo-core declares one dependency, kotlinx-coroutines-core, on every platform
Results
False positives cut from all of them to roughly a third, with 88% of rephrasings kept
Measured on PAWS too, an external 2019 corpus: 14% of false positives refused, 79% of rephrasings kept
The comparison against GPTCache published together with what it costs
Checks verified against blind corpora on every build
Twelve modules published to Maven Central
Public API stable under SemVer, with a migration guide from 1.x
Resources
Open on GitHubVisit the siteRepository

Like what we build?

Let us talk about your next project