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:
2026-08-23 07:46:46 +00:00
parent fe500a7227
commit a1d39637c8
13 changed files with 1555 additions and 39 deletions

View File

@@ -15,7 +15,7 @@ from src.claude_session import (
PROJECT_ROOT,
VALID_MODELS,
)
from src.fast_commands import dispatch as fast_dispatch, split_text_chunks, extract_url_text
from src.fast_commands import dispatch as fast_dispatch, split_text_chunks, extract_url_text, set_channel_context
from src.router import (
route_message,
_ralph_propose,
@@ -676,6 +676,35 @@ def create_bot(config: Config) -> discord.Client:
f"Heartbeat error: {e}", ephemeral=True
)
@tree.command(name="otp", description="Cod OTP pentru re-autentificare roa2web")
@app_commands.describe(cod="Codul primit pe email", email="Doar prima dată")
async def otp_cmd(
interaction: discord.Interaction, cod: str, email: str | None = None
) -> None:
# Ephemeral throughout: the code is a live credential until it is used.
await interaction.response.defer(ephemeral=True)
args = [cod] + ([email] if email else [])
result = await asyncio.to_thread(fast_dispatch, "otp", args)
await interaction.followup.send(result, ephemeral=True)
@tree.command(name="f", description="Vorbește cu modelul local de fallback (Qwen3.5-2B)")
@app_commands.describe(text="Mesajul tău; 'reset' golește istoricul")
async def f_cmd(
interaction: discord.Interaction, text: str | None = None
) -> None:
await interaction.response.defer(ephemeral=True)
# The fallback runs tools over SSH/HTTP and can take tens of seconds;
# set_channel_context is thread-local, so it must run in the same
# worker thread as the dispatch itself.
channel_id = str(interaction.channel_id)
def _run() -> str:
set_channel_context(channel_id)
return fast_dispatch("f", text.split() if text else [])
result = await asyncio.to_thread(_run)
await interaction.followup.send(result[:1990], ephemeral=True)
@tree.command(name="search", description="Search Echo's memory")
@app_commands.describe(query="What to search for")
async def search_cmd(