Get Name — addressee/given-name extraction for cactus-needle

get-name is a small LoRA fine-tune of the cactus-needle base model (Cactus-Compute/needle2) that extracts the given (first) name of the intended person in a piece of text.

It was trained to answer one question: "which single person is this text about / addressed to, and what is that person's first name?" — and to call a tool only when such a person exists.

Input Output
Nice to meet you, Alex Alex
By the way, Alice, if you don't know, how old am I? Alice
The birth certificate was issued to John Snow John
Right now, there are me, Carl, Stephanie, and you, Greg, in the room. Greg
Please let Maria know about the meeting. Maria
Give this package to Lucas. Lucas
Hi everyone, thanks for coming. (no call)
My brother Carl lives in Boston. (no call — mention, not target)

Behaviour

  • Given name only. Full names are collapsed to the first name on purpose (John SnowJohn, James MillerJames).
  • Single target. In enumerations the person directly addressed (you, Greg) is preferred over names merely listed (the team was Carl, Stephanie, and Greg → no call).
  • No call when there is no target: group greetings, place names, brands, assistants (Siri/Alexa), generic salutations, and passive mentions.

How to use

The archive is a needle .cact weights file for cactus-needle.

# pull the archive
needle download Qrzysztof/get-name
from needle import Needle

tool = {
    "name": "extract_name",
    "parameters": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "The given (first) name of the intended person; never the surname.",
            }
        },
        "required": ["name"],
    },
}
system = (
    "You extract the given (first) name of the intended person. "
    "If the text has no intended person, do not call the tool."
)

agent = Needle(tools=[tool], weights="tuned.cact", system=system)
print(agent.complete("Nice to meet you, Alex"))
# {"name": "Alex"}

Note: the system prompt above is mandatory for best results — the model was trained with it in every example and the same string must be passed at inference.

Running in the browser (no server)

The needle engine ships an official WebAssembly build, so this model runs entirely in the browser — no backend, no Python. Only three assets are needed:

Asset Source
needle.js (62 KB, Emscripten glue) Cactus-Compute/needle2/wasm/needle.js
needle.wasm (325 KB, engine) Cactus-Compute/needle2/wasm/needle.wasm
tuned.cact (13.7 MB, this model) this repo

Everything is fetchable from the Hugging Face CDN (CORS-enabled), so a purely static page works. Open the included browser-demo.html to try it, or use the live Space: https://huggingface.co/spaces/Qrzysztof/get-name-demo (direct: qrzysztof-get-name-demo.static.hf.space), or wire up the same calls yourself:

<script src="https://huggingface.co/Cactus-Compute/needle2/resolve/main/wasm/needle.js"></script>
<script>
const SYSTEM = "You extract the given (first) name of the intended person. If the text has no intended person, do not call the tool.";
const TOOLS  = [{ name: "extract_name", parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } }];

let mod;
const enc = s => { const b = new TextEncoder().encode(s + "\0"); const p = mod._malloc(b.length); mod.HEAPU8.set(b, p); return p; };

async function init() {
  const [wasm, cact] = await Promise.all([
    fetch("https://huggingface.co/Cactus-Compute/needle2/resolve/main/wasm/needle.wasm").then(r => r.arrayBuffer()),
    fetch("https://huggingface.co/Qrzysztof/get-name/resolve/main/tuned.cact").then(r => r.arrayBuffer()),
  ]);
  mod = await createNeedle({ wasmBinary: wasm });

  const p = mod._malloc(cact.byteLength);
  mod.HEAPU8.set(new Uint8Array(cact), p);
  if (mod._needle_load(p, BigInt(cact.byteLength)) !== 0) throw new Error("needle_load failed");
  mod._free(p);

  if (mod._needle_init(enc(SYSTEM), enc(JSON.stringify(TOOLS)), 0) < 0) throw new Error("needle_init failed");
}

function complete(text) {
  const qP = enc(text), cap = 1 << 20, outP = mod._malloc(cap);
  const rc = mod._needle_complete(qP, 512, outP, cap);
  const bytes = mod.HEAPU8.subarray(outP, outP + Math.min(rc, cap));
  let end = bytes.indexOf(0); if (end < 0) end = bytes.length;
  const response = JSON.parse(new TextDecoder().decode(bytes.subarray(0, end)));
  mod._free(qP); mod._free(outP);
  return response;                       // { type, function_calls: [{ name, arguments: { name } }], … }
}

await init();
console.log(complete("Nice to meet you, Alex"));           // function_calls[0].arguments.name === "Alex"
console.log(complete("Hi everyone, thanks for coming."));  // function_calls === []
</script>

Browser specifics (verified against the WASM build):

  • API is identical to the native engineneedle_load / needle_init / needle_complete / needle_reset. Return codes follow the Python binding: needle_load returns 0 on success; needle_init and needle_complete return negative on error.
  • needle_load takes the cact length as a 64-bit BigInt, and tools must be a JSON array (like the Python binding sends).
  • Hosting: serve statically (GitHub Pages, Vercel, or just python3 -m http.server) — HF's CDN allows the cross-origin fetches.
  • Footprint: ~13.7 MB download, ~325 KB WASM, and ~150–200 MB process memory (measured via Node RSS while loading weights + running a completion; not the engine's peak_ram_mb field, which proved unreliable in this build). Roughly ~50 tokens/s on recent Apple Silicon / desktop Chrome (2-bit/4-bit quantized weights).
  • One call per complete(); for batch processing run a fresh engine instance per text (same guidance as the Python API in Known limitations).

Training

  • Method: LoRA adapters (rank 16, alpha 32) on the five attention projections of every layer of a frozen Cactus-Compute/needle2 base; engine, tokenizer and confidence head untouched. Adapter merged into the weights at export.
  • Data: ~1,100 hand-curated, template-augmented examples in needle's tool-calling JSONL format (data.jsonl in this repo), 88 % positive / 12 % negative, covering:
    • greetings & welcomes,
    • vocative / direct address (incl. titled forms, Dr. John Snow, …),
    • documents issued to a person (birth certificates, passports, licences…),
    • enumerations with an addressed member (…and you, Greg,…),
    • message targets and relays (Tell Maria…, Pass this note to Owen…),
    • off-topic no-call examples (groups, places, brands, assistants, generic salutations).
  • Hyper-parameters: batch 16, lr 1e-4 with warmup + cosine decay, grad clip 1.0, epochs chosen so the held-out loss lands ~0.5 (see Known limitations), validation split 0.1, seq len 256.
  • Recipe: needle finetune data.jsonl --epochs 9 --lora-rank 16 then needle build checkpoints/needle2.pkl --lora adapter.pkl --out tuned.cact.

Training ran on a single Colab T4 and took a few minutes per run.

Known limitations

  • False-positive rejection is the weak point. The tuning budget between never calls the tool and calls a tool on everything is narrow for this "is it really addressed to a person?" discrimination. This release is tuned on the extraction side (strong recall on intended targets). If you need stricter rejection, retrain with a higher negative share (25 %+) and stop a little earlier on the validation curve.
  • Sequential calls in one process: repeated complete() calls in the same long-lived process showed degraded output on later calls. For batch work, run one fresh process per text (one query each) or recreate the Needle object between batches.
  • Confidence head is not tuned: tuned weights report confidence as None and a warning is emitted at construction (expected with needle LoRA blends).
  • Non-English input: like the base model, out-of-domain text (and Spanish in particular) is not well calibrated.

Files

  • tuned.cact — merged, exported weights (13.7 MB) for Needle(weights=...).
  • data.jsonl — the training dataset (browser-search friendly, one JSON object per line).
  • tool.json — the extract_name tool schema.
  • browser-demo.html — self-contained, zero-server browser demo (WASM engine + tuned weights fetched straight from Hugging Face).
  • README.md — this card.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Qrzysztof/get-name

Adapter
(2)
this model

Space using Qrzysztof/get-name 1