Files
echo-core/memory/kb/projects/cactus-needle-function-calling-eval.md

11 KiB

Cactus Needle — evaluare model de function calling pentru pre-routing local (2026-07-12)

Spike de testare: se poate folosi un model mic de function calling (rulat local pe CPU) ca pre-router înaintea claude_session.py, ca să interceptăm intenții simple (weather, add task, note, query roa2web) fără să consumăm din rate limit-ul Claude?

Context — de ce s-a pornit testul

router.py rutează doar pe prefix (/, text-keywords) — orice mesaj care nu e slash-command pornește un turn Claude CLI complet, inclusiv întrebări triviale ("ce vreme e", "cât e în casă"). Asta consumă rate limit pe Anthropic subscription (nu cost — vezi user_anthropic_subscription) chiar și pe intenții simple, repetitive. Video sursă care a declanșat testul: Cactus Needle — The 26M Function Calling Model (notă: 2026-07-12_cactus-needle-26m-function-calling-model).

Ce s-a testat

Model: Cactus-Compute/needle — 26M parametri, "Simple Attention Network" (attention+gating, fără FFN/MLP), distilat din Gemini 3.1 Flash Lite. Repo: cactus-compute/needle.

  • Instalare izolată în /tmp/needle-test (venv separat, nu a atins .venv de producție din echo-core). JAX/Flax/Optax, CPU-only.
  • Checkpoint needle.pkl (~50MB fp32) descărcat direct de pe HF, fără antrenare/fine-tuning suplimentar.
  • Tools JSON definite manual, mimând funcții reale Echo: get_weather, add_task, create_note, get_treasury_balance (analog roa2web), set_timer.
  • 10 query-uri de test, engleză + română, incluzând și mesaje conversaționale banale (fără tool corespunzător).

Rezultate

  • Recunoaștere corectă pe intenții clare, inclusiv română (deși modelul e antrenat mai ales pe engleză): "Ce vreme e la Cluj?" → get_weather(location=Cluj), "Cât e în casă?" → get_treasury_balance(account=casa), "Pune un timer de 10 minute" → set_timer(duration="10 minutes").
  • Fals pozitiv critic pe conversație casual: "Bună, ce mai faci?" a declanșat get_treasury_balance(account="bun") — tool call inventat pe un salut banal. Traficul Echo e majoritar conversație, nu comenzi, deci acest failure mode lovește direct fluxul principal.
  • Query ambiguu ("Cât e în bancă?") a generat două tool calls duplicate cu argumente diferite, nu un răspuns curat.
  • Extragere de conținut incompletă pe note mai lungi (a păstrat doar titlul, a pierdut restul textului).
  • Pe query clar conversațional/tehnic ("Explică-mi recursivitatea în Python") a răspuns corect cu listă goală — fără tool call inventat.
  • Latență reală pe CPU: 3-9s per query (nu ms-urile din marketing) — setup-ul default din repo nu are encoder JIT-cache-uit/warm, ar necesita inginerie suplimentară (padding fix + server ținut pornit) ca să se apropie de cifrele oficiale (1200 tok/s pe device).

Comparație cu alternative

  • Qwen3-0.6B (benchmark extern, heyneo.com, 4-core CPU, 50 query-uri / 5 nivele de dificultate, netestat local): acuratețe mai bună decât Needle cu prompt bun (84% vs 72% overall), dar 4-6x mai lent (~40-56s/query vs ~7-17s Needle) și cu exact același failure mode — pe query "What's 2+2?" a inventat un tool call run_command.

Hammer2.1-0.5b — testat local (2026-07-12)

MadeAgents/Hammer2.1-0.5b0.5 miliarde parametri, ~19x mai mare decât Needle (26M). Fine-tune pe Qwen2.5-Coder-0.5B-Instruct, folosește "function masking" pentru robustețe la tool calling (arxiv 2410.04587). Rulat local pe CPU (torch+transformers, venv izolat /tmp/hammer-test), exact aceleași 10 query-uri și tool-uri ca la testul Needle, pentru comparație directă.

  • Fixează problema critică de la Needle: pe ambele mesaje conversaționale ("Bună, ce mai faci?" și "Explică-mi recursivitatea") a răspuns corect cu listă goală — zero fals-pozitive pe chit-chat, spre deosebire de Needle care a inventat un tool call pe salut.
  • Dar are propriile probleme:
    • Fals-negativ pe RO: "Ce vreme e la Cluj?" (română) → listă goală, deloc recunoscut — deși Needle a rezolvat corect exact acest query. Query-ul englezesc echivalent a mers corect.
    • Halucinație de conținut, nu doar omisiune: la create_note pentru "intalnirea de azi cu Andrei", a generat content: "Please check the weather report tomorrow." — text complet inventat, fără nicio legătură cu query-ul. Mai grav decât Needle, care măcar a păstrat conținut relevant (incomplet, dar corect tematic).
    • Nu extrage argumentul account la get_treasury_balance — "Cât e în casă?" și "Cât e în bancă?" au produs exact același output gol (arguments: {}), pierzând distincția casă/bancă pe care Needle o prindea corect.
  • Dimensiune pe disc: checkpoint ~950MB (fp32) vs ~50MB la Needle — proporțional cu raportul de parametri.
  • Latență: 2.4-5.6s/query (comparabil cu Needle, 3-9s), plus ~24s load inițial al modelului (Needle: 2.4s).

Qwen2.5-0.5B-Instruct — testat local (2026-07-12)

Qwen/Qwen2.5-0.5B-Instruct — 0.5B parametri, model general-purpose foarte popular (nu specializat pe function calling), cu suport tool calling nativ prin chat template. Testat cu același set de 10 query-uri.

  • Corect pe weather RO și EN (mai bine ca Hammer, care rata varianta RO).
  • Fals-negativ pe task RO: "Adaugă un task să sun clientul mâine" → nu a emis niciun tool call, a răspuns cu o întrebare de clarificare în engleză. Varianta EN a mers corect.
  • create_note RO: nume+titlu corecte, dar a omis complet argumentul content (obligatoriu) — tool call incomplet.
  • Cel mai prost rezultat din tot testul: "Cât e în casă?" → refuz ("I can't assist with that"); "Cât e în bancă?" → răspuns complet halucinat și incoerent, "NO, CAT IS NOT IN THE BANK" — a confundat "cât" (românește) cu "cat" (pisică, engleză) și a ieșit din orice logică de tool calling.
  • Pe conversație casual, nu a inventat tool calls (bine), dar calitatea răspunsului conversațional în română a fost slabă/agramat pe salut, deși explicația tehnică despre recursivitate (tot RO) a ieșit surprinzător de coerentă.
  • Concluzie: sigur pe fals-pozitive, dar robustețea pe română e inconsistentă și imprevizibilă — de la perfect (weather) la incoerent total (treasury).

FunctionGemma-270M — testat local (2026-07-12)

google/functiongemma-270m-it (testat via mirror-ul unsloth/functiongemma-270m-it, ~520MB) — bazat pe Gemma 3 270M, model Google specializat explicit pe function calling, cel menționat și în videoclipul sursă ca fiind depășit de Needle pe single-shot function calling. README-ul modelului avertizează clar: "not intended for use as a direct dialogue model... designed to be highly performant after further fine-tuning" — adică e gândit ca bază de fine-tuning per-domeniu, nu ca model gata de folosit zero-shot.

  • Cel mai rapid dintre toate: 1-2.7s/query (comparabil cu Needle, mai rapid decât Hammer/Qwen).
  • Folosește sintaxă proprietară de function call (<start_function_call>call:nume{...}<end_function_call>), nu JSON standard — ar necesita parser custom pe partea de Echo.
  • Confirmă empiric ce spunea videoclipul: e clar mai slab decât Needle zero-shot pe cazurile noastre.
    • Halucinează date/conținut: la add_task a inventat date fixe greșite ("2024-05-23", "2026-07-27" — nici "mâine" real) și a înlocuit titlul cu text generic ("add task to task board") în loc de conținutul real al cererii, pe ambele variante RO și EN.
    • La create_note a generat un paragraf întreg fabricat, fără legătură cu "Andrei" sau întâlnirea.
    • Fals-pozitiv periculos: "Cât e în casă?" → a interpretat "cat" ca pisică (aceeași confuzie ca la Qwen) și a generat create_note(content="Noticed cat in casa") — o notă complet inventată dintr-o interogare financiară, exact tipul de acțiune nedorită de care ne temeam.
    • Pe conversație casual a refuzat corect (fără tool call), dar cu mesaje generice de eroare, nu conversație reală — așteptat, dat fiind avertismentul din README.

Concluzie

Niciun model din cele patru testate/cercetate nu e suficient de fiabil ca să înlocuiască un pas din router.py fără un prag de încredere + fallback obligatoriu la Claude. Tabel rezumat (CPU, aceleași 10 query-uri RO+EN):

Model Parametri Fals-pozitiv pe chit-chat Robustețe RO Halucinație conținut/argumente Latență/query
Needle 26M 26M Da (salut → tool call inventat) Bună Extragere incompletă, nu inventată 3-9s
Hammer2.1-0.5b 0.5B Nu Parțială (ratează weather RO) Da (content fabricat la note) 2.4-5.6s
Qwen2.5-0.5B-Instruct 0.5B Nu Inconsistentă (weather OK, treasury total incoerent) Da (răspuns halucinat pe "bancă") 2.4-8.5s
FunctionGemma-270M 270M Da (interpretare literală "pisică" → notă inventată) Slabă Da, sever (date/conținut fabricate) 1-5.7s (cel mai rapid)

Cele mai rapide (Needle, FunctionGemma) sunt și cele cu fals-pozitive periculoase pe interogări normale/financiare — corelație clară între "modele hiper-specializate pe function calling, foarte mici" și tendința de a forța un tool call chiar și când nu e cazul. Modelele mai generaliste (Hammer, Qwen) sunt mai reținute pe chit-chat, dar au propriile goluri pe română și pe extragerea corectă a argumentelor.

Decizie: nicio schimbare de cod în echo-core. Nu s-a integrat niciun pre-router local.

Dacă se reia pe viitor: niciunul dintre cele patru nu e gata "as-is". Dacă tot se alege un candidat de rafinat, Hammer2.1-0.5b rămâne cel mai sigur punct de plecare (singurul fără fals-pozitiv periculos și cu extragere de argumente parțial corectă), dar are nevoie de: (a) validare/fine-tuning explicit pe română (fals-negativul pe weather RO e blocant dat fiind traficul majoritar RO), (b) prompt/system message mai strict pentru a reduce halucinația pe câmpuri text libere (title/content). Nu merită reluat testul cu FunctionGemma-270M zero-shot — README-ul propriu spune clar că are nevoie de fine-tuning per-domeniu ca să fie utilizabil.

Artefacte

Foldere de test (/tmp/needle-test, /tmp/hammer-test) sunt în /tmp, se pierd la reboot. Cache-uri de modele rămase în ~/.cache/huggingface/hub/ (nu se pierd la reboot): models--MadeAgents--Hammer2.1-0.5b (~950MB), models--Qwen--Qwen2.5-0.5B-Instruct (~950MB), models--unsloth--functiongemma-270m-it (~520MB) — total ~2.4GB, de șters manual dacă nu se reia testarea (disc la 85% ocupare în timpul testului, atenție la spațiu).