cacheverifier 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/CHANGELOG.md +8 -0
  2. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/PKG-INFO +15 -1
  3. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/README.md +14 -0
  4. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/__init__.py +1 -1
  5. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/client.py +26 -2
  6. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/local.py +2 -0
  7. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/pyproject.toml +1 -1
  8. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_client.py +24 -0
  9. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.github/workflows/ci.yml +0 -0
  10. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.github/workflows/publish.yml +0 -0
  11. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.gitignore +0 -0
  12. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/LICENSE +0 -0
  13. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/__main__.py +0 -0
  14. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/__init__.py +0 -0
  15. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/_base_models.py +0 -0
  16. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/_finetune.py +0 -0
  17. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/cli.py +0 -0
  18. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_inference.py +0 -0
  19. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/__init__.py +0 -0
  20. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/gptcache.py +0 -0
  21. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/langchain.py +0 -0
  22. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/py.typed +0 -0
  23. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/examples/gptcache_example.py +0 -0
  24. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/examples/quickstart.py +0 -0
  25. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_gptcache_integration.py +0 -0
  26. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_healthcheck.py +0 -0
  27. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_langchain_integration.py +0 -0
  28. {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_local.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ `entries_to_refresh()` lists the cache entries your feedback reported stale,
6
+ or wrong more often than right, and `resolve_entry(key)` marks one refreshed
7
+ (`GET /v1/monitor/entries-to-refresh`, every plan). `feedback()` (on both
8
+ `CacheVerifier` and `LocalVerifier`) takes `cache_entry_id`, your own key for
9
+ the entry, which the list hands back so you can refresh or drop it directly.
10
+
3
11
  ## 0.6.0
4
12
 
5
13
  New `LocalVerifier` (`pip install "cacheverifier[local]"`, Managed plans):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cacheverifier
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Python client for the CacheVerifier semantic-cache verification API, hosted or scored locally
5
5
  Project-URL: Homepage, https://www.cacheverifier.com
6
6
  Project-URL: Documentation, https://www.cacheverifier.com/docs
@@ -185,6 +185,19 @@ set_llm_cache(
185
185
  )
186
186
  ```
187
187
 
188
+ ## Entries to refresh
189
+
190
+ Send your cache's own key with feedback, and the service tells you which
191
+ entries to fix: ones reported stale, or wrong more often than right.
192
+
193
+ ```python
194
+ cv.feedback(query, cached_answer, was_correct=False, stale=True, cache_entry_id="faq:shipping")
195
+
196
+ for e in cv.entries_to_refresh():
197
+ my_cache.delete(e["cache_entry_id"]) # or regenerate the answer
198
+ cv.resolve_entry(e["key"])
199
+ ```
200
+
188
201
  ## Fine-tuning
189
202
 
190
203
  Once you have ~20+ feedback rows (the service found fine-tuning is often a net negative
@@ -306,6 +319,7 @@ verdict
306
319
  | `get_finetune_job(id)` / `list_finetune_jobs()` | `GET /v1/finetune/jobs[/id]` |
307
320
  | `activate_model_version(id)` | `POST /v1/finetune/model-versions/{id}/activate` |
308
321
  | `drift_status()` | `GET /v1/monitor/drift-status` |
322
+ | `entries_to_refresh()` / `resolve_entry(key)` | `GET /v1/monitor/entries-to-refresh` / `POST .../{key}/resolve` |
309
323
  | `gray_zone_threshold()` | `GET /v1/monitor/gray-zone-threshold` |
310
324
  | `usage()` / `savings()` | `GET /v1/usage/status` / `/savings` |
311
325
  | `LocalVerifier.verify(...)` / `verify_batch(...)` | local; decisions to `POST /v1/verify/decisions` |
@@ -146,6 +146,19 @@ set_llm_cache(
146
146
  )
147
147
  ```
148
148
 
149
+ ## Entries to refresh
150
+
151
+ Send your cache's own key with feedback, and the service tells you which
152
+ entries to fix: ones reported stale, or wrong more often than right.
153
+
154
+ ```python
155
+ cv.feedback(query, cached_answer, was_correct=False, stale=True, cache_entry_id="faq:shipping")
156
+
157
+ for e in cv.entries_to_refresh():
158
+ my_cache.delete(e["cache_entry_id"]) # or regenerate the answer
159
+ cv.resolve_entry(e["key"])
160
+ ```
161
+
149
162
  ## Fine-tuning
150
163
 
151
164
  Once you have ~20+ feedback rows (the service found fine-tuning is often a net negative
@@ -267,6 +280,7 @@ verdict
267
280
  | `get_finetune_job(id)` / `list_finetune_jobs()` | `GET /v1/finetune/jobs[/id]` |
268
281
  | `activate_model_version(id)` | `POST /v1/finetune/model-versions/{id}/activate` |
269
282
  | `drift_status()` | `GET /v1/monitor/drift-status` |
283
+ | `entries_to_refresh()` / `resolve_entry(key)` | `GET /v1/monitor/entries-to-refresh` / `POST .../{key}/resolve` |
270
284
  | `gray_zone_threshold()` | `GET /v1/monitor/gray-zone-threshold` |
271
285
  | `usage()` / `savings()` | `GET /v1/usage/status` / `/savings` |
272
286
  | `LocalVerifier.verify(...)` / `verify_batch(...)` | local; decisions to `POST /v1/verify/decisions` |
@@ -19,7 +19,7 @@ Research behind it: https://github.com/imxinchengyou/CacheVerifier
19
19
  from cacheverifier.client import CacheVerifier, CacheVerifierError, ShadowVerdict, VerifyResult
20
20
  from cacheverifier.local import LocalVerifier, ModelCheckFailed
21
21
 
22
- __version__ = "0.6.0"
22
+ __version__ = "0.7.0"
23
23
  __all__ = [
24
24
  "CacheVerifier",
25
25
  "CacheVerifierError",
@@ -312,6 +312,7 @@ class CacheVerifier:
312
312
  similarity_score: float | None = None,
313
313
  cached_query: str | None = None,
314
314
  verification_id: int | None = None,
315
+ cache_entry_id: str | None = None,
315
316
  stale: bool = False,
316
317
  idempotency_key: str | None = None,
317
318
  ) -> int:
@@ -328,6 +329,8 @@ class CacheVerifier:
328
329
  - `cached_query`: the cached entry's own original query, if you have it.
329
330
  - `verification_id`: from the `VerifyResult` this feedback is about
330
331
  (always for an `audit` result) -- it ties the label to that verdict.
332
+ - `cache_entry_id`: your own key for the cache entry the answer came
333
+ from; `entries_to_refresh()` hands it back.
331
334
  - `stale`: set instead of a bare `was_correct=False` when the answer
332
335
  is wrong ONLY because a fact changed (price, date, ...), not a
333
336
  semantic mismatch. Stale rows are excluded from training and drift.
@@ -340,7 +343,12 @@ class CacheVerifier:
340
343
  "stale": stale,
341
344
  }
342
345
  payload.update(
343
- _drop_none(similarity_score=similarity_score, cached_query=cached_query, verification_id=verification_id)
346
+ _drop_none(
347
+ similarity_score=similarity_score,
348
+ cached_query=cached_query,
349
+ verification_id=verification_id,
350
+ cache_entry_id=cache_entry_id,
351
+ )
344
352
  )
345
353
  headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
346
354
  return int(self._post("/v1/feedback", json=payload, headers=headers)["id"])
@@ -348,7 +356,8 @@ class CacheVerifier:
348
356
  def feedback_batch(self, items: Iterable[dict[str, Any]]) -> list[int]:
349
357
  """Bulk-upload feedback rows (max 500). Each item is a dict with
350
358
  `query`, `candidate_answer`, `was_correct`, and optionally
351
- `similarity_score` / `stale`. Returns the created row ids.
359
+ `similarity_score` / `cached_query` / `cache_entry_id` / `stale`.
360
+ Returns the created row ids.
352
361
  `POST /v1/feedback/batch`.
353
362
  """
354
363
  data = self._post("/v1/feedback/batch", json={"items": list(items)})
@@ -412,6 +421,21 @@ class CacheVerifier:
412
421
  """
413
422
  return self._get("/v1/monitor/gray-zone-threshold")
414
423
 
424
+ def entries_to_refresh(self) -> list[dict[str, Any]]:
425
+ """Cache entries your feedback reported stale, or wrong more often
426
+ than right -- refresh or drop them, then `resolve_entry(e["key"])`.
427
+ Each has `key`, `cache_entry_id` (if you sent one), `reason`
428
+ (`"stale"` / `"often_wrong"`), the answer, report counts and sample
429
+ queries. `GET /v1/monitor/entries-to-refresh`.
430
+ """
431
+ return list(self._get("/v1/monitor/entries-to-refresh")["entries"])
432
+
433
+ def resolve_entry(self, key: str) -> dict[str, Any]:
434
+ """Mark an entry refreshed: earlier feedback stops counting toward it.
435
+ `POST /v1/monitor/entries-to-refresh/{key}/resolve`.
436
+ """
437
+ return self._post(f"/v1/monitor/entries-to-refresh/{key}/resolve")
438
+
415
439
  def usage(self) -> dict[str, Any]:
416
440
  """`GET /v1/usage/status`."""
417
441
  return self._get("/v1/usage/status")
@@ -475,6 +475,7 @@ class LocalVerifier:
475
475
  result: VerifyResult | None = None,
476
476
  similarity_score: float | None = None,
477
477
  cached_query: str | None = None,
478
+ cache_entry_id: str | None = None,
478
479
  stale: bool = False,
479
480
  idempotency_key: str | None = None,
480
481
  ) -> int:
@@ -488,6 +489,7 @@ class LocalVerifier:
488
489
  similarity_score=similarity_score,
489
490
  cached_query=cached_query,
490
491
  verification_id=verification_id,
492
+ cache_entry_id=cache_entry_id,
491
493
  stale=stale,
492
494
  idempotency_key=idempotency_key,
493
495
  )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cacheverifier"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "Python client for the CacheVerifier semantic-cache verification API, hosted or scored locally"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -133,6 +133,7 @@ def test_feedback_returns_id_and_forwards_idempotency_key():
133
133
  def test_feedback_omits_optional_fields_when_unset():
134
134
  def handler(request: httpx.Request) -> httpx.Response:
135
135
  assert "similarity_score" not in json.loads(request.content)
136
+ assert "cache_entry_id" not in json.loads(request.content)
136
137
  assert request.headers.get("Idempotency-Key") is None
137
138
  return httpx.Response(200, json={"id": 1})
138
139
 
@@ -140,6 +141,29 @@ def test_feedback_omits_optional_fields_when_unset():
140
141
  assert cv.feedback("q", "a", was_correct=True) == 1
141
142
 
142
143
 
144
+ def test_entries_to_refresh_round_trip():
145
+ seen = []
146
+
147
+ def handler(request: httpx.Request) -> httpx.Response:
148
+ seen.append((request.method, request.url.path, request.content))
149
+ if request.url.path == "/v1/feedback":
150
+ return httpx.Response(200, json={"id": 7})
151
+ if request.method == "GET":
152
+ return httpx.Response(200, json={"entries": [{"key": "k" * 32, "cache_entry_id": "faq:shipping", "reason": "stale"}]})
153
+ return httpx.Response(200, json={"key": "k" * 32, "resolved_at": "2026-10-31T00:00:00Z"})
154
+
155
+ with make_client(handler) as cv:
156
+ cv.feedback("q", "a", was_correct=False, stale=True, cache_entry_id="faq:shipping")
157
+ [entry] = cv.entries_to_refresh()
158
+ assert entry["cache_entry_id"] == "faq:shipping"
159
+ assert cv.resolve_entry(entry["key"])["key"] == entry["key"]
160
+ assert json.loads(seen[0][2])["cache_entry_id"] == "faq:shipping"
161
+ assert [s[:2] for s in seen[1:]] == [
162
+ ("GET", "/v1/monitor/entries-to-refresh"),
163
+ ("POST", f"/v1/monitor/entries-to-refresh/{'k' * 32}/resolve"),
164
+ ]
165
+
166
+
143
167
  def test_finetune_passes_query_params():
144
168
  def handler(request: httpx.Request) -> httpx.Response:
145
169
  assert request.url.path == "/v1/finetune/jobs"
File without changes
File without changes