feat(fallback): unelte, internet, retea si conversatie pe modelul local
Fallback-ul local (Qwen3.5-2B, LXC 104) raspundea ca nu are unelte sau
internet. Acum conversează, are unelte read-only si istoric per canal.
Unelte (allowlist strict read-only, src/local_fallback_tools.py):
- cauta_web — DuckDuckGo Lite, fara API key
- masini — status Proxmox/LXC prin SSH paralel (src/net_status.py)
- vremea, sold, facturi, trezorerie, doctor, logs, email, kb,
cauta_memorie, citeste_pagina
Protocol: function calling OpenAI nativ. Protocolul text anterior
("TOOL: nume arg") pierdea argumentul in 100% din cazuri.
Uneltele cu output deja formatat se intorc verbatim — modelul de 2B
transforma un `doctor` cu 5 linii OK in "Sistemul este in 5/5 state.".
Doua treceri: prima decide uneltele (cu tools, temp 0), a doua reface
turnul fara ele — prezenta definitiilor strica raspunsurile simple
("cat fac 128/4?" -> "Nu stiu ce inseamna 128/4"). Few-shot doar pe
cereri creative: pe turnuri factuale strica aritmetica (17*23 -> 471).
Ocoliri deterministe pentru ce modelul greseste reproductibil: vremea si
preturile live (raspundea din memorie), plus sarcini "instructiune: text"
(payload-ul dicta unealta — "scrie mai politicos: da-mi raportul acum"
chema `sold`). llama.cpp nu e determinist nici la temperatura 0 si
ignora tool_choice, deci promptul singur nu ajunge.
Masurat: 36/36 pe set combinat (17 alegere unealta + 19 comprehensiune),
de la 14/19 pe comprehensiune inainte de regulile negative din prompt.
Comenzi: /f (inlocuieste /testfallback, ramas alias), /f reset, /masini.
Prefixul "Claude e la limita" apare doar pe calea de rate limit.
roa2web: /otp <cod> pentru 2FA din chat — mesajul de eroare trimitea la
verify_2fa(code, email), imposibil de rulat din Discord.
Corectat memory/kb/tools/infrastructure.md: LXC 101 si 110 sunt pe pve1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B88u3KkzvUNnzU56y7GBn2
This commit is contained in:
21
CLAUDE.md
21
CLAUDE.md
@@ -100,6 +100,23 @@ source .venv/bin/activate && pip install -r requirements.txt
|
||||
|
||||
**Heartbeat** (`src/heartbeat.py`): verificări email, calendar, KB, git. Ore tăcere 23-08.
|
||||
|
||||
**Fallback local** (`src/router.py` → `_local_fallback_reply`): când Claude atinge rate limit-ul, mesajul e servit de un model local (Qwen3.5-2B pe llama.cpp, LXC 104 — `config.json → local_fallback.url`). Nu e doar un mesaj de eroare: modelul **conversează** și are unelte.
|
||||
|
||||
- **Unelte doar-citire** (`src/local_fallback_tools.py`): `doctor`, `logs`, `masini`, `vremea`, `sold`, `trezorerie`, `facturi`, `email`, `kb`, `cauta_memorie`, `cauta_web`, `citeste_pagina`. Registry-ul e allowlist — nimic care scrie/trimite/modifică nu există în el, deci un apel halucinat e respins structural, nu prin prompting. Rulează neasistat, fără permission gating.
|
||||
- **Prompt-ul de unelte spune și când NU:** enumerarea doar a cazurilor „folosește o unealtă” făcea modelul să cheme unelte pe sarcini de text obișnuite. Secțiunea negativă (conversație, traduceri, rezumate, reformulări, liste, calcule → răspuns direct) a dus un set de 19 mesaje de la 14/19 la 18/19. Nu o scoate.
|
||||
- **Sarcini pe text, ocolite determinist** (`_is_text_task` — verb de prelucrare la început **și** două puncte): la „scrie mai politicos: da-mi raportul acum” modelul chema `sold` și răspundea la o rescriere cu o eroare de OTP; „fă-l mai scurt: …despre facturi” chema `facturi`. Payload-ul dicta unealta. Formularea promptului nu repară asta reproductibil — **llama.cpp nu e determinist nici la temperatura 0**, același prompt a dat rezultate diferite între rulări (la fel ca `tool_choice`, ignorat de 2 din 3 ori). Deci apelul cu unelte e sărit de tot. Două puncte sunt obligatorii: fără ele „scrie-mi soldul” ar fi citit ca sarcină de scris și ar sări interogarea soldului.
|
||||
- **Scor curent:** 36/36 pe setul combinat (17 alegere de unealtă + 19 comprehensiune). Scripturile de evaluare nu sunt în repo — reconstruiește-le din cazurile citate aici dacă schimbi promptul.
|
||||
- **Protocol:** function calling OpenAI nativ (llama.cpp îl implementează pentru Qwen). Un protocol text anterior (`TOOL: nume arg`) nimerea numele uneltei dar **pierdea argumentul în 100% din cazuri** — nu-l reintroduce.
|
||||
- **Raw vs sinteză:** uneltele cu `raw=True` întorc textul verbatim; modelul de 2B nu apucă să-l reformuleze (a transformat un `doctor` cu 5 linii OK în „Sistemul este în 5/5 state."). Sinteza rămâne doar pentru text în vrac — `cauta_web`, `cauta_memorie`, `citeste_pagina`. Dacă o rundă amestecă ambele tipuri, **raw câștigă**.
|
||||
- **Istoric** (`src/fallback_history.py`): fereastră în memorie per canal, 6 schimburi, TTL 30 min. Doar pentru fallback — Claude are `--resume`.
|
||||
- **Rețea** (`src/net_status.py`): status read-only pentru nodurile Proxmox și LXC-urile din `10.0.20.0/24`, SSH în paralel cu timeout scurt. Containerele fără cheie SSH se accesează prin `pct exec` de pe nodul gazdă (`sh -c`, nu `bash` — gitea e Alpine). Inventarul e în modul, nu în KB: `memory/kb/tools/infrastructure.md` era stale (minecraft/moltbot sunt pe pve1, nu pveelite).
|
||||
- **Web** (`src/web_search.py`): DuckDuckGo Lite, fără API key. Atenție la parser — DDG emite `class='...'` cu ghilimele simple.
|
||||
- **Două treceri.** Prima decide uneltele (cu `tools`, temperatură 0, fără few-shot). Dacă nu iese niciun tool call, turnul se reface **fără unelte** — prezența definițiilor degradează răspunsurile simple: cu ele „cat fac 128/4?” întoarce „Nu știu ce înseamnă 128/4”, fără ele „128 / 4 = 32”. Few-shot se adaugă la a doua trecere **doar pentru cereri creative** (`_is_creative_request` — glumă/banc/poveste/poezie), unde modelul altfel deflectează („O glumă bună!”); pe turnuri factuale strică aritmetica (17*23 → 471). În apelul de decizie few-shot scade selecția de la 17/17 la 15/17, iar temperatura 0.6 tot la 15/17 — de aceea rămân separate și la 0.
|
||||
- **Prefix:** banner-ul „Claude e la limită" apare doar pe calea de rate limit. Prin `/f` userul a ales modelul local deliberat (`manual=True`) și nu se anunță nicio limită.
|
||||
- **Comenzi:** `/f <mesaj>` conversează, `/f` arată starea, `/f reset` golește istoricul (`/testfallback` rămâne alias). `/masini [nume]` e disponibilă și ca fast command normală.
|
||||
|
||||
**roa2web — re-autentificare 2FA din chat.** Când tokenul de dispozitiv expiră, orice comandă financiară (`/sold`, `/facturi`, `/trezorerie`) întoarce „Necesar cod OTP nou, trimis pe m***@…”. Codul se trimite înapoi cu **`/otp <cod>`** (Discord: slash command ephemeral, restul: text). Adresa de email e ținută în keyring ca `roa2web_email` și se reține la prima folosire — `/otp <cod> <email>` o suprascrie. Mesajul de eroare din `tools/roa2web_client.py` trimitea înainte la `verify_2fa(code, email)`, un apel Python imposibil de rulat din Discord, adică exact acolo unde apare eroarea. `otp` **nu** e în registrul de unelte al fallback-ului: acela e strict read-only, iar autentificarea schimbă stare.
|
||||
|
||||
**Ralph** (`tools/ralph/`): sistem autonom de execuție. `ralph.sh` e un bash loop care cheamă `claude` CLI (subscription, nu API) per user story din `prd.json`. PRD generat cu `tools/ralph_prd_generator.py` (Opus). Workspace la `~/workspace/`.
|
||||
|
||||
**Memory** (`memory/` în acest repo — sursa unică de adevăr). Retrieval **hibrid**, două căi:
|
||||
@@ -235,6 +252,10 @@ Fișierele Ralph (planning_session, planning_orchestrator, ralph.sh, ralph_dag,
|
||||
| `src/main.py` | Entry point — adaptoare + scheduler + heartbeat |
|
||||
| `src/router.py` | Comenzi vs mesaje Claude |
|
||||
| `src/claude_session.py` | Wrapper Claude CLI cu `--resume` |
|
||||
| `src/local_fallback_tools.py` | Registry allowlist de unelte doar-citire pentru modelul local (vezi § Fallback local) |
|
||||
| `src/fallback_history.py` | Istoric conversație per canal pentru fallback (6 schimburi, TTL 30 min) |
|
||||
| `src/net_status.py` | Status read-only mașini Proxmox/LXC prin SSH paralel |
|
||||
| `src/web_search.py` | Căutare web fără API key (DuckDuckGo Lite) |
|
||||
| `src/credential_store.py` | Secrete keyring |
|
||||
| `cli.py` | Diagnostice CLI (eco) |
|
||||
| `config.json` | Config runtime |
|
||||
|
||||
Reference in New Issue
Block a user