feat(heads): surface system-tag suggestions at a flat 0.65 confidence floor
System tags (wip/banner/editor) already get heads (kind=general) and aren't filtered from suggestions, but they surfaced only at each head's precision-tuned suggest_threshold — high enough to hide the borderline/false-positive guesses the operator wants to SEE and REJECT (hard-negative mining: 'negatively reinforce what isn't a system tag'). score_image now uses a flat _SYSTEM_TAG_SUGGEST_FLOOR (0.65, operator-set) for system-tag heads instead of their auto threshold; content-tag heads keep their own, and the typed-dropdown threshold_override still overrides everything. _current_heads carries Tag.is_system into the head meta to drive it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -63,6 +63,17 @@ _HEAD_KINDS = (TagKind.general, TagKind.character)
|
||||
# tag.kind -> the suggestion category the rail groups under.
|
||||
_CATEGORY = {TagKind.general: "general", TagKind.character: "character"}
|
||||
|
||||
# System-tag (wip/banner/editor screenshot) heads surface as suggestions at
|
||||
# this FLAT confidence floor instead of their auto-derived (precision-tuned)
|
||||
# suggest threshold. The auto threshold is high, so it hides the borderline /
|
||||
# false-positive guesses — which are exactly the cases the operator wants to
|
||||
# SEE and REJECT to sharpen these heads (hard-negative mining: "negatively
|
||||
# reinforce what isn't a system tag"). Operator-set 0.65 (2026-07-03): high
|
||||
# enough not to spam near-zero scores, low enough to surface real mistakes.
|
||||
# Content-tag heads keep their own thresholds; the typed-dropdown's
|
||||
# threshold_override still overrides everything (show-all mode).
|
||||
_SYSTEM_TAG_SUGGEST_FLOOR = 0.65
|
||||
|
||||
|
||||
class HeadTrainingAlreadyRunning(Exception):
|
||||
"""Raised by start_head_training_run when a run is already in flight."""
|
||||
@@ -297,7 +308,7 @@ async def _current_heads(session: AsyncSession, embedding_version: str):
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(
|
||||
TagHead.tag_id, Tag.name, Tag.kind,
|
||||
TagHead.tag_id, Tag.name, Tag.kind, Tag.is_system,
|
||||
TagHead.weights, TagHead.bias,
|
||||
TagHead.suggest_threshold, TagHead.auto_apply_threshold,
|
||||
)
|
||||
@@ -317,6 +328,7 @@ async def _current_heads(session: AsyncSession, embedding_version: str):
|
||||
"name": r.name,
|
||||
"category": _CATEGORY.get(r.kind, "general"),
|
||||
"auto_apply_threshold": r.auto_apply_threshold,
|
||||
"is_system": bool(r.is_system),
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
@@ -333,7 +345,10 @@ async def score_image(
|
||||
category, score}], ranked. A concept surfaces when its score clears the
|
||||
head's own suggest_threshold — or, when threshold_override is given (the
|
||||
typed-dropdown "show everything" mode), that flat floor instead (0 → every
|
||||
head). Empty if the image has no embedding or no heads exist yet.
|
||||
head). System-tag heads (wip/banner/editor) instead use a flat
|
||||
_SYSTEM_TAG_SUGGEST_FLOOR so their false positives surface for rejection
|
||||
(still overridden by threshold_override). Empty if the image has no
|
||||
embedding or no heads exist yet.
|
||||
|
||||
MAX-OVER-BAG: the image is scored as a BAG of embeddings — the whole-image
|
||||
vector PLUS every concept-region crop the agent embedded (same model
|
||||
@@ -383,7 +398,14 @@ async def score_image(
|
||||
probs = (1.0 / (1.0 + np.exp(-Z))).max(axis=0) # (H,) best over the bag
|
||||
out = []
|
||||
for i, p in enumerate(probs):
|
||||
cut = threshold_override if threshold_override is not None else heads["thr"][i]
|
||||
if threshold_override is not None:
|
||||
cut = threshold_override
|
||||
elif heads["meta"][i]["is_system"]:
|
||||
# System tags surface at the flat floor (see _SYSTEM_TAG_SUGGEST_FLOOR)
|
||||
# so their false positives show up for the operator to reject.
|
||||
cut = _SYSTEM_TAG_SUGGEST_FLOOR
|
||||
else:
|
||||
cut = heads["thr"][i]
|
||||
if p >= cut:
|
||||
m = heads["meta"][i]
|
||||
out.append({
|
||||
|
||||
@@ -111,6 +111,49 @@ async def test_threshold_override_surfaces_below_cut(db):
|
||||
assert any(s.canonical_tag_id == tag.id for s in flooded.by_category["general"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_system_tag_surfaces_at_flat_floor_despite_high_threshold(db):
|
||||
# A system tag's head uses the flat _SYSTEM_TAG_SUGGEST_FLOOR (0.65), not its
|
||||
# precision-tuned suggest_threshold — so a matching image (~0.73) surfaces it
|
||||
# for rejection even though its 0.9 auto threshold would hide it.
|
||||
from backend.app.services.ml.heads import _SYSTEM_TAG_SUGGEST_FLOOR
|
||||
assert _SYSTEM_TAG_SUGGEST_FLOOR == 0.65
|
||||
tag = await TagService(db).find_or_create("wip sketchy", TagKind.general)
|
||||
tag.is_system = True
|
||||
img = await _img(db, "1" * 64, _emb(0)) # matches head → ~0.73
|
||||
await _head(db, tag.id, slot=0, suggest_threshold=0.9)
|
||||
await db.commit()
|
||||
sl = await SuggestionService(db).for_image(img.id)
|
||||
assert any(
|
||||
s.canonical_tag_id == tag.id for s in sl.by_category.get("general", [])
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_system_tag_below_floor_stays_hidden(db):
|
||||
# Below the 0.65 floor (~0.5 on an orthogonal image) the system tag is not
|
||||
# surfaced — the floor still gates, it isn't "always show".
|
||||
tag = await TagService(db).find_or_create("wip faint", TagKind.general)
|
||||
tag.is_system = True
|
||||
img = await _img(db, "2" * 64, _emb(1)) # orthogonal → 0.5
|
||||
await _head(db, tag.id, slot=0, suggest_threshold=0.9)
|
||||
await db.commit()
|
||||
sl = await SuggestionService(db).for_image(img.id)
|
||||
assert not sl.by_category.get("general")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_content_tag_ignores_system_floor(db):
|
||||
# A NON-system head with the same high threshold does NOT get the floor: the
|
||||
# ~0.73 match stays hidden below its 0.9 auto threshold (floor is system-only).
|
||||
tag = await TagService(db).find_or_create("glasses hi", TagKind.general)
|
||||
img = await _img(db, "3" * 64, _emb(0)) # matches head → ~0.73
|
||||
await _head(db, tag.id, slot=0, suggest_threshold=0.9)
|
||||
await db.commit()
|
||||
sl = await SuggestionService(db).for_image(img.id)
|
||||
assert not sl.by_category.get("general")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concept_region_surfaces_via_max_over_bag(db):
|
||||
# Max-over-bag: the whole-image vector is orthogonal to the head (scores the
|
||||
|
||||
Reference in New Issue
Block a user