本文へ移動
cccskills
無料GitHub で公開

hallucinating-labels

Assign items to a CLOSED label vocabulary that is too large to put in a prompt — product taxonomies, category hierarchies, tag vocabularies, routing tables, ICD/SIC-style code lists. A cheap model writes the label it thinks the vocabulary would use, and an embedder snaps that writing onto the nearest legal value, so the schema is never transmitted and the output is always in-vocabulary. Use for "classify these into our taxonomy", "tag these against the existing tag list", "map these queries to categories", "the enum is too big to send", or a Literal/enum that hits a provider cap. NOT for a vocabulary that fits in a prompt — structured output measured 0.701 acc@1 there against this pattern's 0.564. NOT for open-ended labelling with no fixed vocabulary, and not for ranked retrieval over documents (bm25).

インストール方法を見る

含まれるファイル(5)

  • SKILL.md11.0 KB
  • CHANGELOG.md1.8 KB
  • README.md1.9 KB
  • scripts/snap.py6.1 KB
  • tests/test_snap.py4.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

hallucinating-labels

Ask a cheap model to write a plausible label for the item. Snap that label onto the real vocabulary with an embedder. The model never sees the label set.

Doug Turnbull's pattern (softwaredoug.com, 2026-08-10), with the two prompt and boundary corrections that measurement produced.

Check the boundary first

If the whole vocabulary fits in a prompt, do not use this skill. Ship the label list and ask for a constrained choice. Measured on WANDS (860 labels, 468 queries, one gold label each, gemini-3.5-flash-lite):

approachacc@1acc@3input tokens/item
structured output, all 860 labels shipped0.7010.7445,265
this skill0.5640.6906
embed the item directly, no model0.4170.5640

Shipping the vocabulary is 14 points more accurate and 880× more expensive. Take the accuracy unless the tokens are the problem. The tokens are the problem when the vocabulary does not fit, when a provider enum cap rejects it, or when per-call cost at volume dominates — a 5,000-label vocabulary is roughly 30k tokens on every single call.

This skill still beats every model-free baseline by a wide margin, so it is the right tool whenever shipping the vocabulary is off the table.

Procedure

1. Write the vocabulary to a file, one label per line, and index it once.

python3 scripts/snap.py build --vocab categories.txt --out .snap-index.pkl

Default backend is tfidf — sklearn only, no download. Pass --backend minilm when sentence-transformers and a ~90 MB download are available and the items share no wording with the labels; it scored 0.564 to tfidf's 0.528 on WANDS. Where items literally contain their own label words, tfidf wins outright (0.416 vs 0.356 on a memory-tag corpus).

2. Write the labels yourself, in batches of 40, using the register prompt below. Write them to a file, one per line, in the same order as the items.

3. Snap.

python3 scripts/snap.py snap --index .snap-index.pkl --labels written.txt --k 3

Add --min-score 0.35 to get null instead of a bad snap, and --items items.txt --union for long items (see below). Output is JSON with the top-k legal labels per item.

4. Report the nulls and the low scores. A snap at cosine 0.18 is noise wearing a legal label. Never present one as a classification.

Anchor the prompt on REGISTER, not on novelty

This is the correction that matters most, and it is the opposite of what the source post's prompt says. Its prompt opens "create a novel, never-seen-before classification". That instruction is safe only with a model too weak to follow it.

Measured on the same 40 WANDS queries, MiniLM backend:

promptmodelacc@1acc@3
embed the query directly, no model—0.5000.650
"novel, never-seen-before"gemini-3.5-flash-lite0.5750.675
"novel, never-seen-before"Haiku 4.5 subagent0.1000.275
register-anchored (below)Haiku 4.5 subagent0.5250.750

Haiku obeyed. Asked for novelty it produced novelty — Hydraulic Styling Thrones, Weathered Branch-Frame Reflectors, Chromatic Comfort Accents — and scored a fifth of what doing nothing scores. Gemini flash-lite half-ignored the same instruction and wrote Salon & Styling Chairs, Rustic Wall Mirrors, which is what the snap needs. The pattern wants a novel instance in the vocabulary's register, and "never-seen-before" asks for novel wording. A better instruction-follower is worse at the badly-worded prompt.

The register prompt also beat the novelty prompt on Gemini across all 468 queries (0.564 vs 0.489 acc@1), so it is strictly the better wording. Use this:

You are writing entries for a {DOMAIN} vocabulary.

For each item below, write the label that this vocabulary WOULD file that item under. Write it the way the vocabulary writes labels — match the examples' register, length and wording exactly.

Do not worry about whether the label already exists. Write the obvious one. Do not invent novel or creative wording, do not use marketing adjectives, do not hedge, do not explain.

Examples of the register: {6-8 REAL LABELS FROM THE VOCABULARY}

Output one line per item, in the same order, formatted exactly as: <n>. <label>

ITEMS: {NUMBERED ITEMS}

The examples are load-bearing — they are how the register gets communicated. Draw 6-8 real labels from the vocabulary. They are not the vocabulary; sending eight labels is not sending five thousand.

Batch 40 per call. Never one item per call

Batching is free: 0.496/0.641 batched ×40 against 0.489/0.613 unbatched, at 1/17 the input tokens and 1/9 the wall-clock.

Write the labels yourself when the items are already in context — you are the cheap model here, and it costs one short generation. Delegate to a Haiku subagent only in batches, and only when the item list is long enough to be worth it. A subagent invocation carries a measured floor of ~32,500 tokens before it reads your prompt: a general-purpose Haiku subagent asked to output the single word ok, with zero tool calls, spent 32,539. Per item that floor is 813 tokens at batch 40 and 32,500 at batch 1.

Parse the model's numbered reply back by index, not by zipping positionally. When the model drops item 2 of 40, zipping shifts every later item onto its neighbour's label and nothing signals it. A dropped item is an empty label and then a null.

Long items want --union, not a different pattern

The written label replaces direct embedding cleanly when item and label are the same kind of string — a WANDS query and a WANDS category are both short noun phrases. When the item is a 1,500-character document and the label is one word, the written label throws away most of the document, and the direct embedding still has it. The two are complementary.

Memory store, 1,273 tags, 250 documents of 300-2000 characters, mean 4.8 gold tags, tfidf:

arm@1@3@5
embed the document directly0.4160.6280.712
write 5 tags, novelty prompt0.2080.3520.424
write 5 tags, register prompt0.5080.7000.792
both, interleaved (--union)0.6720.8520.888

Note the middle two rows. With the wrong prompt this corpus says the pattern loses to doing nothing by 2x; with the right one it wins, and the union wins by a lot more. Long items amplify the register error rather than causing a separate problem — a distinctive vocabulary is exactly where novel wording lands furthest from anything legal.

Rule: item and label share a register → write labels and snap them. Item is a long document → do that and pass --items ... --union. Neither case is a reason to reach for the novelty prompt.

Picking the encoder, and why there is no local-LLM version

The encoder is the whole system when you cannot reach an API. Snapping the raw query, no model call anywhere, full WANDS set:

encoderint8 ONNXacc@1acc@3
all-MiniLM-L6-v223 MB0.4170.564
bge-small-en-v1.533 MB0.4270.583
gte-small33 MB0.4550.594
bge-base-en-v1.5109 MB0.4620.630

gte-small is the knee. bge-base buys +0.007 acc@1 for 3.3x the download.

Do not substitute a tiny local model for the label-writing half. Pleias Monad (57M) and Baguettotron (321M) both have onnx-community builds — 35 MB and 236 MB at q4f16 — so a wholly client-side pipeline packages fine. Neither earns its bytes. As writers they score 0.425 and 0.400 acc@1 against a 0.500 no-model control on the same 40 queries: they echo the query (smart coffee table → Smart coffee table) and bleed from the few-shot exemplars (chair and a half recliner → Chair & Recycling Bins). As likelihood rerankers over the encoder's top-10 — which asks them for no format compliance at all — they score 0.325 and 0.350 against the same 0.500, with the gold label present in that top-10 for 82.5% of queries.

The reason is the same one that makes the register prompt matter: what the cheap model contributes here is not reasoning but a prior over how taxonomies name things, learned from web-scale pretraining. A small model trained for reasoning has no retail-taxonomy prior, and reasoning does not substitute for one. In a browser, ship the encoder alone.

Failure modes

signalcausefix
Snapped labels are wrong but confident; written labels read like ad copynovelty-anchored prompt, obeyedswitch to the register prompt; read the written labels before blaming the snap — Hydraulic Styling Thrones is a prompt bug, not an embedder bug
Everything snaps to the same one or two labelsthe register examples are unrepresentative, or the vocabulary has one dominant stringdraw examples spanning the vocabulary's breadth
Scores cluster near 0.15items and labels share no surface wording--backend minilm, and --union if items are long
Item n onward all shifted by onepositional zipping of a reply with a dropped lineparse by the emitted index; verify counts match before snapping
Accuracy below the no-model controlnovelty-anchored prompt, or a long item without --unionfix the prompt first — it cost 30 points on one corpus and 7.5 on another; then add --union

Verify it beat doing nothing

Run the no-model control before shipping this anywhere. Snap the items directly (--labels items.txt, no written labels) and compare. On one of the two corpora measured here the control won by 2x under the wrong prompt. If you have no gold labels to score against, hand-check 20 items both ways — the control is one command and its absence is how this pattern gets adopted where it loses.

Related

  • bm25 — ranked retrieval over documents. Different problem: no closed label set.
  • agent-routing — routing to a small named set, which fits in a prompt. Ship it instead.
  • muninn_utils.hypothetical_classifier — the same pattern as a Python API with Gemini wired in, for Muninn sessions.

Experiment, arms and artifacts: oaustegard/experiments/hypothetical-classification.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

GitHub repository access in containerized environments using REST API and credential detection. Use when git clone fails, or when accessing private repos/writing files via API.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

Decide which model, effort level, and cascade shape each subagent gets, and how to keep improvement loops safe (evaluator-as-selector, stop on regression). Routes on measured cost-per-completed-task rather than per-token price, because a tier's token count varies more by task shape than price varies across tiers. Covers per-model effort semantics, the concision lever, cascade preconditions, context handoff, and watching a subagent fan-out live. Use when choosing a subagent's model or effort, when choosing how to escalate a failed attempt, or when asked which model or effort a task should get. Grounded in a 2026-07 calibration, a 2026-08 coding-cost study, a 2026-09 seeded-bug repair battery, a 2026-10 Haiku 5.5 ladder priced per spawn, and a 2026-10 retry-versus-escalation ladder on 295 SWE-bench Verified tasks; Managed Agents API specifics are operational, not calibrated.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

Securely manages API credentials for multiple providers (Anthropic Claude, Google Gemini, GitHub). Use when skills need to access stored API keys for external service invocations.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

Guidance for asking clarifying questions when user requests are ambiguous, have multiple valid approaches, or require critical decisions. Use when implementation choices exist that could significantly affect outcomes.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

Pre-change blast-radius report for a symbol or file. Walks tree-sitting references, augments with a plain-text scan over non-parsed files (configs, plain docs), and clusters affected sites by feature (`_FEATURES.md`) or top-level package. Use when about to refactor, rename, or delete something in a repo you don't own — "what breaks if I change `validateUser`", "who calls this", "is this safe to remove", "where is this used", "blast radius", "impact analysis". This is the CONVERGENT pre-change risk skill — for "what is this repo?" use exploring-codebases; for "where is X?" use searching-codebases.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

Read Bluesky/ATProto without depending on Bluesky's AppView — interactions on a user's posts, thread replies, any repo's records, network-wide backlinks across every lexicon, an account's handle and PDS history, which collections are active network-wide, and layer-by-layer outage diagnosis. Use when bsky.app or the AppView is down or slow, when a Bluesky read returns timeouts or 5xx, when asked who liked/replied/quoted/reposted something, when pulling live Bluesky context cheaply, when reading records straight from a PDS, when asked who references a record or account anywhere on the network, whether an account has changed handle or migrated servers, or which lexicons and non-Bluesky apps are active on atproto. Complements browsing-bluesky, which routes everything through the AppView and fails when it does.

日本語の概要は準備中です。原文の説明を表示しています。

oaustegard/claude-skills1512026年10月12日 更新

oaustegard のスキルをすべて見る

このスキルの問題を報告する