feat(llm): theme filter pushed into the vector store — theme_1..theme_N rank keys beside themes, $or equality filter; stamp threshold 0.60
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / notebooks-smoke (push) Has been cancelled
Deploy / notebooks (push) Has been cancelled
Deploy / zotero (push) Has been cancelled
Deploy / docs (push) Has been cancelled
Deploy / api (push) Has been cancelled
Deploy / llm (push) Has been cancelled
Deploy / mc (push) Has been cancelled
Deploy / report (push) Has been cancelled
Infra CI / notebooks (push) Has been cancelled
Infra CI / zotero (push) Has been cancelled
Infra CI / docs (push) Has been cancelled
Infra CI / api (push) Has been cancelled
Infra CI / llm (push) Has been cancelled
Infra CI / mc (push) Has been cancelled
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / notebooks-smoke (push) Has been cancelled
Deploy / notebooks (push) Has been cancelled
Deploy / zotero (push) Has been cancelled
Deploy / docs (push) Has been cancelled
Deploy / api (push) Has been cancelled
Deploy / llm (push) Has been cancelled
Deploy / mc (push) Has been cancelled
Deploy / report (push) Has been cancelled
Infra CI / notebooks (push) Has been cancelled
Infra CI / zotero (push) Has been cancelled
Infra CI / docs (push) Has been cancelled
Infra CI / api (push) Has been cancelled
Infra CI / llm (push) Has been cancelled
Infra CI / mc (push) Has been cancelled
/search?theme= used to filter in Python after the k-limited fetch, so a narrow theme on a broad doctype came back empty. llm.themes now also writes one scalar key per rank (theme_1, theme_2, …) and _store_filter turns a theme into an $or of equalities over them, so the store's overfetch is spent on on-theme chunks. Default min score raised to 0.60 (the knee the tagging sweep found); 24,555 manual chunks re-stamped: top first themes enrollment-program-integrity 7,081, evaluation-management 2,142, coverage-policy 2,064, telehealth 960.
This commit is contained in:
@@ -21,7 +21,7 @@ Usage: stack llm stamp-themes [OPTIONS]
|
|||||||
│ --key TEXT Only this item key. │
|
│ --key TEXT Only this item key. │
|
||||||
│ --top INTEGER Themes kept per chunk. [default: 2] │
|
│ --top INTEGER Themes kept per chunk. [default: 2] │
|
||||||
│ --min-score FLOAT Cosine similarity a theme must reach. │
|
│ --min-score FLOAT Cosine similarity a theme must reach. │
|
||||||
│ [default: 0.55] │
|
│ [default: 0.6] │
|
||||||
│ --force Re-stamp chunks already on the current │
|
│ --force Re-stamp chunks already on the current │
|
||||||
│ vocabulary version. │
|
│ vocabulary version. │
|
||||||
│ --help Show this message and exit. │
|
│ --help Show this message and exit. │
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ def stamp_themes(
|
|||||||
key: str = typer.Option("", "--key", help="Only this item key."),
|
key: str = typer.Option("", "--key", help="Only this item key."),
|
||||||
top: int = typer.Option(2, "--top", help="Themes kept per chunk."),
|
top: int = typer.Option(2, "--top", help="Themes kept per chunk."),
|
||||||
min_score: float = typer.Option(
|
min_score: float = typer.Option(
|
||||||
0.55, "--min-score", help="Cosine similarity a theme must reach."
|
0.60, "--min-score", help="Cosine similarity a theme must reach."
|
||||||
),
|
),
|
||||||
force: bool = typer.Option(
|
force: bool = typer.Option(
|
||||||
False,
|
False,
|
||||||
|
|||||||
@@ -54,13 +54,16 @@ def _collections_for(collection: str) -> tuple[str, ...]:
|
|||||||
return (collection,)
|
return (collection,)
|
||||||
|
|
||||||
|
|
||||||
def _store_filter(filters: dict[str, str]) -> dict[str, str]:
|
def _store_filter(filters: dict[str, str]) -> dict[str, Any]:
|
||||||
"""The langchain PGVector ``filter=`` metadata dict for *filters* —
|
"""The langchain PGVector ``filter=`` metadata dict for *filters* —
|
||||||
docket/item_key/kind and the manual coordinates (doctype, manual,
|
docket/item_key/kind and the manual coordinates (doctype, manual,
|
||||||
chapter, iom_section) are pushed down to the store; ``year`` is
|
chapter, iom_section) are pushed down to the store; ``year`` is
|
||||||
applied in Python (:func:`_year_ok`) since it's a prefix of the
|
applied in Python (:func:`_year_ok`) since it's a prefix of the
|
||||||
``date`` field, not its own metadata key."""
|
``date`` field, not its own metadata key. A ``theme`` becomes an
|
||||||
return {
|
``$or`` of equalities over the ``theme_1..theme_3`` rank keys
|
||||||
|
``llm.themes`` writes, so the overfetch is not spent on off-theme
|
||||||
|
chunks (the comma-joined ``themes`` is re-checked in Python)."""
|
||||||
|
out: dict[str, Any] = {
|
||||||
key: filters[key]
|
key: filters[key]
|
||||||
for key in (
|
for key in (
|
||||||
"docket",
|
"docket",
|
||||||
@@ -73,6 +76,10 @@ def _store_filter(filters: dict[str, str]) -> dict[str, str]:
|
|||||||
)
|
)
|
||||||
if filters.get(key)
|
if filters.get(key)
|
||||||
}
|
}
|
||||||
|
theme = str(filters.get("theme") or "")
|
||||||
|
if theme:
|
||||||
|
out["$or"] = [{f"theme_{i}": theme} for i in range(1, 4)]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _year_ok(md: dict[str, Any], year: str) -> bool:
|
def _year_ok(md: dict[str, Any], year: str) -> bool:
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def stamp(
|
|||||||
doctype: str = "manual",
|
doctype: str = "manual",
|
||||||
item_key: str = "",
|
item_key: str = "",
|
||||||
top: int = 2,
|
top: int = 2,
|
||||||
min_score: float = 0.55,
|
min_score: float = 0.60,
|
||||||
batch: int = 1000,
|
batch: int = 1000,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
) -> dict[str, int]:
|
) -> dict[str, int]:
|
||||||
@@ -136,6 +136,12 @@ def stamp(
|
|||||||
"themes": ",".join(s for s, _ in chosen),
|
"themes": ",".join(s for s, _ in chosen),
|
||||||
"theme_scores": ",".join(f"{s}:{sc}" for s, sc in chosen),
|
"theme_scores": ",".join(f"{s}:{sc}" for s, sc in chosen),
|
||||||
"themes_vocab": version,
|
"themes_vocab": version,
|
||||||
|
# one scalar key per rank so the store can filter with an
|
||||||
|
# equality ($or over theme_1..theme_N) instead of a LIKE
|
||||||
|
**{
|
||||||
|
f"theme_{i}": (chosen[i - 1][0] if i <= len(chosen) else "")
|
||||||
|
for i in range(1, top + 1)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
patches.append({"id": cid, "patch": json.dumps(patch)})
|
patches.append({"id": cid, "patch": json.dumps(patch)})
|
||||||
if patches:
|
if patches:
|
||||||
|
|||||||
@@ -391,3 +391,12 @@ class TestThemeFilter:
|
|||||||
assert not _theme_ok({"themes": "telehealth,supervision"}, "drugs")
|
assert not _theme_ok({"themes": "telehealth,supervision"}, "drugs")
|
||||||
assert not _theme_ok({}, "drugs")
|
assert not _theme_ok({}, "drugs")
|
||||||
assert _theme_ok({}, "")
|
assert _theme_ok({}, "")
|
||||||
|
|
||||||
|
def test_theme_is_pushed_down_as_an_or_of_rank_keys(self):
|
||||||
|
out = _store_filter({"theme": "telehealth", "doctype": "manual"})
|
||||||
|
assert out["doctype"] == "manual"
|
||||||
|
assert out["$or"] == [
|
||||||
|
{"theme_1": "telehealth"},
|
||||||
|
{"theme_2": "telehealth"},
|
||||||
|
{"theme_3": "telehealth"},
|
||||||
|
]
|
||||||
|
|||||||
@@ -64,7 +64,14 @@ class TestStamp:
|
|||||||
by["a"]["themes"] == "telehealth,mixed" and by["a"]["themes_vocab"] == "2"
|
by["a"]["themes"] == "telehealth,mixed" and by["a"]["themes_vocab"] == "2"
|
||||||
)
|
)
|
||||||
assert by["a"]["theme_scores"].startswith("telehealth:1.0,mixed:0.7071")
|
assert by["a"]["theme_scores"].startswith("telehealth:1.0,mixed:0.7071")
|
||||||
assert by["c"] == {"themes": "", "theme_scores": "", "themes_vocab": "2"}
|
assert by["a"]["theme_1"] == "telehealth" and by["a"]["theme_2"] == "mixed"
|
||||||
|
assert by["c"] == {
|
||||||
|
"themes": "",
|
||||||
|
"theme_scores": "",
|
||||||
|
"themes_vocab": "2",
|
||||||
|
"theme_1": "",
|
||||||
|
"theme_2": "",
|
||||||
|
}
|
||||||
assert "b" not in by
|
assert "b" not in by
|
||||||
|
|
||||||
def test_force_restamps_everything_and_pages_by_id(self):
|
def test_force_restamps_everything_and_pages_by_id(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user