Skip to content

feat(cache): normalize default-valued params in cache key + hit-rate metric (#892) - #901

Open
suantea wants to merge 5 commits into
tashfeenahmed:mainfrom
suantea:feat/cache-hit-rate-892
Open

feat(cache): normalize default-valued params in cache key + hit-rate metric (#892)#901
suantea wants to merge 5 commits into
tashfeenahmed:mainfrom
suantea:feat/cache-hit-rate-892

Conversation

@suantea

@suantea suantea commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

中文说明

针对 issue #892(response cache 命中率提升)的 #1 参数规范化 + #4 命中率指标 两部分。

#1 参数规范化computeCacheKey 现在把「显式传入但等于 API 默认值」的采样参数(top_p:1presence_penalty:0frequency_penalty:0n:1)从缓存 key 中剔除——总是序列化完整参数的客户端(如 CLI/agent 默认带 top_p:1)与最小化请求命中同一缓存条目,这是提高命中率的核心修复。key 版本 v3→v4;非默认值仍参与 key,不会串答案。

#4 命中率指标CacheStats 新增 misseshitRate(hits/(hits+misses)),miss 在查询无有效条目(不存在或 TTL 过期)时计数;GET /api/cache/stats 自动带出新字段。前端设置对话框「高级」区新增 Response cache 命中率区块(命中/未命中/命中率/节省配额),i18n 新增 7 个 settings.cache* 键同步到全部 60 个 locale。

测试:cache 套件 30 + proxy-cache 套件 10 全绿(含 2 个新测试:显式默认值命中同一 key / 非默认值仍区分);client/server tsc 干净。

说明:#2(SQLite 持久化)与 #3(流式重放)将在后续 PR 中跟进。


English

Part 1 (param normalization) + part 4 (hit-rate metric) of issue #892.

#1 Param normalization: computeCacheKey now drops explicit-but-default-valued sampling params (top_p:1, presence_penalty:0, frequency_penalty:0, n:1) from the cache key, so clients that always serialize full params (e.g. CLIs/agents that send top_p:1 by default) hit the same entries as minimal requests — the core fix for the near-zero hit rate. Key version bumped 3→4; non-default values stay in the key, so answers can never collide.

#4 Hit-rate metric: CacheStats gains misses and hitRate (hits/(hits+misses)); a miss is counted on lookup with no valid entry (missing or TTL-expired). GET /api/cache/stats automatically includes the new fields. The settings dialog now shows a Response-cache stats block (hits/misses/hit-rate/quota saved) under Advanced, with 7 new settings.cache* i18n keys synced across all 60 locales.

Tests: cache suite 30 + proxy-cache suite 10 all green (incl. 2 new tests: explicit defaults share a key / non-defaults stay distinct); client & server tsc clean.

Note: #2 (SQLite persistence) and #3 (streaming replay) will follow in later PRs.


Closes #892

The server build writes server/tsconfig.tsbuildinfo into the repo root;
git status was showing it as untracked noise.
@suantea
suantea force-pushed the feat/cache-hit-rate-892 branch from 1f59aa6 to 1caf141 Compare August 19, 2026 06:34
suantea and others added 3 commits August 20, 2026 10:29
…metric (tashfeenahmed#892)

- computeCacheKey: explicit-but-default-valued sampling params (top_p:1,
  presence_penalty:0, frequency_penalty:0, n:1) are dropped from the key so
  clients that always serialize full params hit the same entries as minimal
  ones; key version bumped 3->4
- CacheStats: add misses and hitRate (hits/(hits+misses)); miss counted on
  lookup with no valid entry (missing or TTL-expired)
- settings dialog: response-cache stats block (hits/misses/hit-rate/quota
  saved) on the Advanced section, fetching /api/cache/stats
- i18n: 7 new settings.cache* keys across all 60 locales

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>
The cache now stores entries in the response_cache table (better-sqlite3)
in addition to the in-memory LRU, so answers survive restarts — free-tier
quota resets at UTC midnight, and the same tasks re-run after the reset
hit again instead of burning quota anew.

- getCachedResponse: on an in-memory miss, fall through to SQLite and
  re-warm the LRU; TTL expiry deletes from both views
- storeCachedResponse: INSERT OR REPLACE into SQLite + enforce the entry
  cap there too (LRU by last_hit_at_ms/created_at_ms)
- getCacheStats: aggregate from SQLite (source of truth), so stats match
  what a cold process would actually serve
- clearCache: clears memory + SQLite
- fail-safe via withDb() (mirrors ratelimit): DB uninitialized/query
  failure disables the cache in the proxy hot path instead of throwing

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>
…ashfeenahmed#892 part 3)

Streaming requests are now cacheable: the exact SSE chunk sequence written
to the client on a complete success is stored, and an identical later
streaming request is replayed from the cache without touching a provider.

- cache.ts: schema gains stream_frames column (ALTER for pre-existing DBs);
  CachedResponse/StoreInput/CacheEntry carry streamFrames; read/write both
  in-memory and SQLite views
- proxy.ts: cacheKey no longer requires non-streaming; a streamed hit
  replays frames + [DONE] with SSE headers; a streamed miss collects frames
  (preamble + chunks + usage) and stores them on success — truncated
  (finish 'length') turns are not cached, matching the non-stream path;
  a stream request hitting a non-stream entry falls through to the provider
- proxy-cache.test.ts: replaced the old 'streaming bypasses' assertion with
  populate-then-replay; added cache cleanup to keep stats/clear assertions
  isolated

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>
@suantea
suantea force-pushed the feat/cache-hit-rate-892 branch from 1caf141 to 090cb59 Compare August 20, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Increase response-cache hit rate: param normalization, SQLite persistence, streaming replay, hit-rate metric

1 participant