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.
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/CHANGELOG.md +8 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/PKG-INFO +15 -1
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/README.md +14 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/__init__.py +1 -1
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/client.py +26 -2
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/local.py +2 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/pyproject.toml +1 -1
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_client.py +24 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.github/workflows/ci.yml +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.github/workflows/publish.yml +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/.gitignore +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/LICENSE +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/__main__.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/__init__.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/_base_models.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/_finetune.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_healthcheck/cli.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/_inference.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/__init__.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/gptcache.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/integrations/langchain.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/cacheverifier/py.typed +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/examples/gptcache_example.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/examples/quickstart.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_gptcache_integration.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_healthcheck.py +0 -0
- {cacheverifier-0.6.0 → cacheverifier-0.7.0}/tests/test_langchain_integration.py +0 -0
- {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.
|
|
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.
|
|
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(
|
|
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` / `
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|