p3: blind titles, cap snippets, equalise k; abort cached dumps
Exempt title from the vendor-token check. Truncate snippets to 160 chars and clip all four packs to the same per-query k. run.py fails cached replies with a rerun message instead of a config mismatch.
This commit is contained in:
+59
-16
@@ -24,7 +24,9 @@ CONFIGS = (
|
||||
)
|
||||
HIT_KEYS = ("rank", "url", "title", "snippet")
|
||||
LEAK_RE = re.compile(r"searxng|brave|tavily|hermes", re.I)
|
||||
EXEMPT_FIELDS = frozenset({"url", "snippet"})
|
||||
EXEMPT_FIELDS = frozenset({"url", "title", "snippet"})
|
||||
SNIPPET_MAX = 160
|
||||
DEFAULT_K = 8
|
||||
|
||||
|
||||
def load_queries(path: Path) -> list[str]:
|
||||
@@ -96,20 +98,44 @@ def normalize(raw: object, n: int) -> list[list[dict]]:
|
||||
rank_i = int(rank)
|
||||
except (TypeError, ValueError):
|
||||
rank_i = i
|
||||
snip = str(h.get("snippet") or h.get("description") or h.get("content") or "")
|
||||
if len(snip) > SNIPPET_MAX:
|
||||
snip = snip[:SNIPPET_MAX]
|
||||
hits.append(
|
||||
{
|
||||
"rank": rank_i,
|
||||
"url": str(h.get("url") or ""),
|
||||
"title": str(h.get("title") or ""),
|
||||
"snippet": str(
|
||||
h.get("snippet") or h.get("description") or h.get("content") or ""
|
||||
),
|
||||
"snippet": snip,
|
||||
}
|
||||
)
|
||||
out.append(hits)
|
||||
return out
|
||||
|
||||
|
||||
def equalise(packs: dict[str, list], k: int) -> None:
|
||||
n = None
|
||||
for cfg in CONFIGS:
|
||||
if cfg not in packs:
|
||||
raise ValueError(f"missing pack {cfg}")
|
||||
got = len(packs[cfg])
|
||||
if n is None:
|
||||
n = got
|
||||
elif got != n:
|
||||
raise ValueError(f"{cfg}: {got} queries, want {n}")
|
||||
assert n is not None
|
||||
for cfg in CONFIGS:
|
||||
for i in range(n):
|
||||
packs[cfg][i] = packs[cfg][i][:k]
|
||||
for i in range(n):
|
||||
m = min(len(packs[cfg][i]) for cfg in CONFIGS)
|
||||
for cfg in CONFIGS:
|
||||
hits = packs[cfg][i][:m]
|
||||
for j, h in enumerate(hits, 1):
|
||||
h["rank"] = j
|
||||
packs[cfg][i] = hits
|
||||
|
||||
|
||||
def leak_hits(obj: object, path: str = "$"):
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
@@ -140,10 +166,11 @@ def assert_blind(pack: object) -> None:
|
||||
found = list(leak_hits(pack))
|
||||
if found:
|
||||
bits = "; ".join(f"{p}={v!r}" for p, v in found[:8])
|
||||
raise ValueError(f"vendor token outside url/snippet: {bits}")
|
||||
raise ValueError(f"vendor token outside url/title/snippet: {bits}")
|
||||
|
||||
|
||||
def write_packs(packs: dict[str, list], out_dir: Path, n: int, seed: int | None) -> Path:
|
||||
def write_packs(packs: dict[str, list], out_dir: Path, n: int, seed: int | None, k: int = DEFAULT_K) -> Path:
|
||||
equalise(packs, k)
|
||||
rng = random.Random(seed)
|
||||
order = list(CONFIGS)
|
||||
rng.shuffle(order)
|
||||
@@ -231,19 +258,34 @@ def self_test() -> int:
|
||||
walk(blob)
|
||||
if keys != {"rank", "url", "title", "snippet"}:
|
||||
raise AssertionError(f"{lab} keys {keys}")
|
||||
# snippet/url may name vendors; title must not
|
||||
bad = [[{"rank": 1, "url": "https://x.example", "title": "tavily guide", "snippet": "ok"}]]
|
||||
try:
|
||||
assert_blind(bad)
|
||||
except ValueError as e:
|
||||
if "vendor token" not in str(e):
|
||||
raise
|
||||
else:
|
||||
raise AssertionError("title leak not caught")
|
||||
# titles of correct hits name vendors; url/title/snippet are exempt
|
||||
assert_blind(
|
||||
[[{"rank": 1, "url": "https://x.example", "title": "tavily guide", "snippet": "ok"}]]
|
||||
)
|
||||
ok_snip = [
|
||||
[{"rank": 1, "url": "https://api.tavily.com/x", "title": "Docs", "snippet": "uses tavily"}]
|
||||
]
|
||||
assert_blind(ok_snip)
|
||||
long = "L" * (SNIPPET_MAX + 40)
|
||||
fat = {
|
||||
"results": [
|
||||
{"url": "https://a.example/" + str(i), "title": "T", "snippet": long}
|
||||
for i in range(5)
|
||||
]
|
||||
}
|
||||
thin = {"results": [{"url": "https://b.example/0", "title": "T", "snippet": "short"}]}
|
||||
packs2 = {
|
||||
"searxng": normalize([fat, fat], 2),
|
||||
"searxng_brave": normalize([thin, thin], 2),
|
||||
"searxng_brave_tavily": normalize([fat, fat], 2),
|
||||
"hermes_tavily": normalize([thin, thin], 2),
|
||||
}
|
||||
equalise(packs2, k=8)
|
||||
for cfg, pack in packs2.items():
|
||||
if len(pack[0]) != 1:
|
||||
raise AssertionError(f"{cfg} k={len(pack[0])} after equalise")
|
||||
if any(len(h["snippet"]) > SNIPPET_MAX for h in pack[0]):
|
||||
raise AssertionError("snippet not truncated")
|
||||
print("self-test ok", key_path)
|
||||
return 0
|
||||
|
||||
@@ -272,6 +314,7 @@ def main() -> int:
|
||||
default=Path("tests/testdata/search_targets.txt"),
|
||||
)
|
||||
p.add_argument("--seed", type=int, default=None)
|
||||
p.add_argument("--k", type=int, default=DEFAULT_K)
|
||||
args = p.parse_args()
|
||||
if args.self_test:
|
||||
return self_test()
|
||||
@@ -293,7 +336,7 @@ def main() -> int:
|
||||
packs[name] = normalize(json.loads(f.read_text()), len(queries))
|
||||
|
||||
try:
|
||||
key_path = write_packs(packs, args.out_dir, len(queries), args.seed)
|
||||
key_path = write_packs(packs, args.out_dir, len(queries), args.seed, args.k)
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user