livrare lot 2

This commit is contained in:
EVOTECH IT SRL 2026-07-10 03:39:53 -07:00
commit 8ecc78e729
763 changed files with 164593 additions and 0 deletions

View file

@ -0,0 +1,629 @@
# Brain v2 Extension — Design Doc
Status: DRAFT 2026-04-30
Owner: tehnic@finesynergy.eu
Companion doc: HIL_MODERATION_DESIGN.md (queue + UI — separat)
---
## Goal
Extindem didi-brain să cache-uiască rezultate de inferență pentru **techniques** și **ai_tampered** (la fel ca face deja pentru claims), cu 3 tier-uri de încredere (gold/silver/bronze) și invalidare la schimbare prompt/framework. Atomi gold provin din corecții moderator (vezi HIL doc); silver din LLM cache regular; bronze NU se servește.
**Out of scope**: workflow moderare, UI, queue management — vezi HIL_MODERATION_DESIGN.md.
---
## Decizii agreate
| # | Decizie |
|---|---|
| 1 | 1 atom per (componentă × content_hash) — `techniques` separat de `ai_tampered` separat de `claims` |
| 2 | Embedding model = ce folosește brain deja (NU adăugăm infra nouă). Brain are deja semantic search + cross-encoder reranking |
| 3 | 3 tier-uri: `gold` (human_validated), `silver` (LLM cache neverificat), `bronze` (pending review, nu se servește) |
| 4 | Write trigger: silver pe LLM run (fire-and-forget); gold pe PATCH cu human_validated=true; bronze pe LLM run cu confidence < 60 |
| 5 | Confidence threshold pe write — sub 60, NU se scrie silver (lasă miss → re-try cu poate model premium) |
| 6 | Prompt versioning: gold supraviețuiește la schimbare prompt; silver e invalidat |
---
## Architecture
```
┌──────────────────────────────────────────────────────────────────┐
│ agent-v3 (techniques/ai-tampered/claims executors) │
│ │
│ 1. Pre-LLM: │
│ POST http://10.11.10.13:8090/v1/analysis_atom/lookup │
│ body: { content_hash, content?, component, tier, │
│ prompt_hash, framework_version } │
│ │
│ 2. Brain răspunde: │
│ { │
│ hit: true | false, │
│ atom: { tier, result_processed, ... } | null, │
│ staleness: 'fresh' | 'stale_prompt' | 'stale_framework', │
│ hit_count_incremented: true │
│ } │
│ │
│ 3. Branch logic: │
│ - tier=gold + fresh → return cached, skip LLM │
│ - tier=silver + fresh → return cached, skip LLM │
│ - tier=silver + stale_prompt → run LLM (prompt changed) │
│ - miss / bronze → run LLM normal │
│ │
│ 4. Post-LLM (miss path, fire-and-forget): │
│ POST /v1/analysis_atom │
│ body: { ..., tier: 'silver' | 'bronze' (if conf<60) }
│ │
│ 5. Post-moderation (HIL flow): │
│ PATCH /v1/analysis_atom/:atom_id │
│ body: { tier: 'gold', human_validated: true, │
│ human_corrections: {...}, validator_user_id } │
└──────────────────────────────────────────────────────────────────┘
```
---
## Data Model (în PG-ul brain, NU în DIDI cluster)
### Tabel nou `analysis_atom`
```sql
-- Migration brain-side: analysis_atom_v1.sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE analysis_atom (
atom_id BIGSERIAL PRIMARY KEY,
content_hash TEXT NOT NULL,
content_embedding vector(1024), -- ajustat la dimensiunea modelului brain (poate fi 768/1024/3072)
content_preview TEXT, -- first 200 chars pentru debug, NU full text
component TEXT NOT NULL CHECK (component IN ('techniques', 'ai_tampered', 'claims')),
tier TEXT NOT NULL CHECK (tier IN ('free', 'premium')),
prompt_hash TEXT NOT NULL,
framework_version TEXT NOT NULL,
model_used TEXT, -- ex: 'qwen35:Qwen3.5-397B-A17B' sau 'openrouter:google/gemini-3-flash-preview'
result_processed JSONB NOT NULL, -- mapped la canonical types din agent-v3 (TechniquesResult, AITamperedResult etc.)
result_raw JSONB, -- raw LLM output (pentru re-mapping ulterior dacă schimba schema)
cache_tier TEXT NOT NULL DEFAULT 'silver'
CHECK (cache_tier IN ('gold', 'silver', 'bronze')),
human_validated BOOLEAN NOT NULL DEFAULT false,
human_corrections JSONB, -- diff din moderation (vezi HIL doc)
validator_user_id TEXT,
validated_at TIMESTAMP,
hit_count INTEGER NOT NULL DEFAULT 0,
last_hit_at TIMESTAMP,
llm_confidence NUMERIC, -- 0-100, folosit pentru bronze decision
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP, -- TTL 90 zile pentru silver/bronze, NULL pentru gold
UNIQUE (content_hash, component, tier, prompt_hash)
);
-- Indexuri
CREATE INDEX idx_analysis_atom_lookup
ON analysis_atom(content_hash, component, tier);
CREATE INDEX idx_analysis_atom_embedding
ON analysis_atom USING ivfflat (content_embedding vector_cosine_ops)
WITH (lists = 100);
CREATE INDEX idx_analysis_atom_gold
ON analysis_atom(component, cache_tier)
WHERE cache_tier = 'gold';
CREATE INDEX idx_analysis_atom_expires
ON analysis_atom(expires_at)
WHERE expires_at IS NOT NULL;
-- Trigger pentru updated_at
CREATE TRIGGER trigger_analysis_atom_updated_at
BEFORE UPDATE ON analysis_atom
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- TTL job (pg_cron sau external cleanup zilnic)
-- DELETE FROM analysis_atom WHERE expires_at < NOW() AND cache_tier != 'gold';
```
### `result_processed` shapes — exemple per component
**Pentru `component='techniques'`** (schema TechniquesResult din agent-v3):
```json
{
"manipulation_score": 65,
"total_severity": 23.4,
"dimensions_affected": ["emotional_appeal", "loaded_language"],
"techniques_count": 4,
"techniques_detected": [
{ "code": "loaded_language", "intensity": 3, "evidence": "...", "severity": 7.2 }
],
"coupling_context": null,
"screening_duration_ms": 1200,
"deep_analysis_duration_ms": 3400
}
```
**Pentru `component='ai_tampered'`**:
```json
{
"ai_probability": 78,
"verdict": "LIKELY_AI",
"risk_score": 65,
"categories_affected": ["text_patterns"],
"indicators_count": 6,
"disclosure_detected": false,
"indicators_detected": [...]
}
```
**Pentru `component='claims'`** (păstrăm compatibilitatea cu schema existentă verification_cache):
- Minimal: 1 row în `analysis_atom` cu component='claims' acoperă **TOATE claim-urile** din analiza acelui content (rezultatul agregat)
- SAU: continuăm pe schema veche `verification_cache` per claim individual + un `analysis_atom` agregator
- **Recomandare**: păstrăm `verification_cache` per-claim cum e (atomar, granular) + adăugăm `analysis_atom` ca agregator opțional pentru "claims output" (rezultat final post-LLM-extraction)
---
## Cache Tier Logic
### Write rules
```
Trigger: post LLM run în executor
Input: { result, component, tier, content, prompt_hash, framework_version, llm_confidence }
if (component === 'claims') {
// Claims continuă să folosească verification_cache existent (nu schimbăm)
// Plus adăugăm un atom agregator pentru "claims output as a whole"
}
confidence = result.confidence ?? estimateConfidence(result);
if (confidence < 60) {
cache_tier = 'bronze'; // se scrie pentru audit/debug, NU se servește
} else {
cache_tier = 'silver'; // se scrie și se servește
}
POST /v1/analysis_atom { ..., cache_tier, human_validated: false }
```
### Lookup rules (priority order)
```
1. Match exact pe (content_hash, component, tier, prompt_hash)
→ if found: check tier
- gold + fresh → SERVE
- silver + fresh → SERVE
- bronze → IGNORE (treat as miss)
- silver + stale (prompt_hash diferă față de current) → IGNORE pe stale_prompt
2. Match semantic pe content_embedding cu cosine_distance < 0.08
AND component=X AND tier=Y AND cache_tier='gold'
AND human_validated=true
→ if found: SERVE (gold supraviețuiește prompt change pentru că răspunsul corect nu depinde de prompt)
3. Match semantic pe embedding < 0.08 AND cache_tier='silver' AND prompt_hash matches
→ if found: SERVE (silver semantic, dar doar pe prompt actual)
4. Miss → return { hit: false }
```
### Promotion rules
```
silver → gold: când moderator face PATCH cu human_validated=true
+ human_corrections=null (mod a aprobat fără modificări)
SAU human_corrections!=null (mod a corectat → result_processed actualizat)
bronze → silver: dacă același content e re-analizat și produce confidence ≥ 60
→ atom-ul vechi bronze e șters, se scrie silver nou
gold → expires never: gold rămâne forever (sau până prompt schimbă fundamental schema, atunci marcaj manual)
```
---
## API Endpoints (brain side)
### `POST /v1/analysis_atom/lookup`
Request:
```json
{
"content": "text full optional pentru semantic search",
"content_hash": "sha256:abc123...",
"component": "techniques",
"tier": "free",
"prompt_hash": "p_v3_2026_03_15",
"framework_version": "fw_v1_2026_03",
"allow_semantic_match": true,
"semantic_threshold": 0.08
}
```
Response:
```json
{
"hit": true,
"atom": {
"atom_id": 4242,
"cache_tier": "gold",
"human_validated": true,
"result_processed": { ... },
"model_used": "qwen35:Qwen3.5-397B-A17B",
"validator_user_id": "admin-123",
"validated_at": "2026-04-15T12:34:56Z",
"hit_count": 17
},
"staleness": "fresh",
"match_type": "exact" // sau "semantic"
}
```
Sau pe miss:
```json
{ "hit": false, "atom": null, "staleness": null }
```
### `POST /v1/analysis_atom`
Request:
```json
{
"content": "full text pentru embedding compute",
"content_hash": "sha256:abc123...",
"content_preview": "first 200 chars...",
"component": "techniques",
"tier": "free",
"prompt_hash": "p_v3_2026_03_15",
"framework_version": "fw_v1_2026_03",
"model_used": "qwen35:Qwen3.5-397B-A17B",
"result_processed": { ... },
"result_raw": { ... },
"llm_confidence": 78,
"cache_tier": "silver" // calculat client-side din confidence
}
```
Response:
```json
{ "success": true, "atom_id": 4243, "cache_tier": "silver" }
```
Idempotent: dacă există atom pentru același (content_hash, component, tier, prompt_hash), face UPDATE (sau ignoră dacă cache_tier=gold — nu suprascriu gold cu silver).
### `PATCH /v1/analysis_atom/:atom_id`
Request:
```json
{
"human_validated": true,
"human_corrections": { "verdict": {...}, "techniques": {...} },
"validator_user_id": "admin-123",
"result_processed": { ... corrected version ... },
"cache_tier": "gold"
}
```
Response:
```json
{ "success": true, "atom_id": 4242, "cache_tier": "gold", "expires_at": null }
```
### `GET /v1/analysis_atom/stats`
Response:
```json
{
"total_atoms": 12345,
"by_tier": { "gold": 234, "silver": 11000, "bronze": 1111 },
"by_component": { "techniques": 4500, "ai_tampered": 4200, "claims": 3645 },
"hit_rate_24h": 0.42,
"writes_24h": 850,
"promotions_24h": { "silver_to_gold": 12, "bronze_to_silver": 3 }
}
```
Folosit de admin dashboard pentru tracking.
---
## Modificări în agent-v3
### 1. `shared/brain/client.ts` (extindere)
```typescript
// Pseudo-cod, nu pentru implementare directă
// Existent: gatherFromBrain, writeVerificationCacheAsync (pentru claims)
// NOU:
export interface AnalysisAtomLookupResult {
hit: boolean;
atom: {
atom_id: number;
cache_tier: 'gold' | 'silver' | 'bronze';
human_validated: boolean;
result_processed: any;
hit_count: number;
} | null;
staleness: 'fresh' | 'stale_prompt' | 'stale_framework' | null;
}
export async function lookupAnalysisAtom(params: {
content: string;
contentHash: string;
component: 'techniques' | 'ai_tampered' | 'claims';
tier: 'free' | 'premium';
promptHash: string;
frameworkVersion: string;
}): Promise<AnalysisAtomLookupResult | null> {
if (!process.env.DIDI_BRAIN_URL) return null;
try {
const res = await fetch(`${process.env.DIDI_BRAIN_URL}/v1/analysis_atom/lookup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({...}),
signal: AbortSignal.timeout(2000), // brain MUST respond fast
});
return await res.json();
} catch {
return null; // fail open — fallback la LLM
}
}
export function writeAnalysisAtomAsync(params: {
content: string;
contentHash: string;
component: 'techniques' | 'ai_tampered' | 'claims';
tier: 'free' | 'premium';
promptHash: string;
frameworkVersion: string;
modelUsed: string;
resultProcessed: any;
resultRaw: any;
llmConfidence: number;
}) {
// Fire-and-forget, nu așteaptă răspuns
const cacheTier = params.llmConfidence < 60 ? 'bronze' : 'silver';
fetch(`${process.env.DIDI_BRAIN_URL}/v1/analysis_atom`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...params, cacheTier }),
signal: AbortSignal.timeout(5000),
}).catch(() => {}); // log warn, ignore
}
export async function patchAnalysisAtomGold(params: {
atomId: number;
validatorUserId: string;
humanCorrections: any | null;
resultProcessed: any;
}) {
// Apelat din moderation/queue-manager.ts după resolve
...
}
```
### 2. `components/techniques/executor.ts` (modificare)
```typescript
// La începutul execute():
const promptHash = computePromptHash(systemPrompt, userTemplate);
const frameworkVersion = computeFrameworkVersion();
const contentHash = sha256(input.text).slice(0, 16);
const lookup = await lookupAnalysisAtom({
content: input.text,
contentHash,
component: 'techniques',
tier: searchTier,
promptHash,
frameworkVersion,
});
if (lookup?.hit && lookup.atom) {
if (lookup.atom.cache_tier === 'gold' || (lookup.atom.cache_tier === 'silver' && lookup.staleness === 'fresh')) {
// Return cached, skip LLM
return {
...lookup.atom.result_processed,
_cache_hit: true,
_cache_tier: lookup.atom.cache_tier,
};
}
}
// Miss path — run LLM normal:
const result = await runLLMScreeningAndDeepAnalysis(...);
// Fire-and-forget write back
writeAnalysisAtomAsync({
content: input.text,
contentHash,
component: 'techniques',
tier: searchTier,
promptHash,
frameworkVersion,
modelUsed: result.llm_screening,
resultProcessed: result,
resultRaw: result._raw,
llmConfidence: estimateConfidence(result),
});
return result;
```
### 3. `components/ai-tampered/executor.ts` (modificare)
Identic cu techniques. Schimbă `component: 'ai_tampered'`.
### 4. `components/claims/executor.ts` (NU modifica)
Claims continuă pe `verification_cache` existent (granular per-claim). Eventual adăugăm `analysis_atom` agregator în Faza 2 doar dacă măsurăm și hit rate scăzut pe claims output ca whole.
### 5. `api/moderation-routes.ts` resolve handler (din Doc A, dar dependent de brain)
```typescript
// În handler-ul PUT /api/v3/moderation/queue/:queueId/resolve:
if (action === 'corrected') {
// ... salvăm corecții în analysis_session ...
// Pentru fiecare componentă afectată în human_corrections, găsim atom-ul corespunzător și PATCH gold
for (const component of ['techniques', 'ai_tampered', 'claims']) {
if (corrections[component]) {
const atomLookup = await lookupAnalysisAtom({...});
if (atomLookup?.atom) {
await patchAnalysisAtomGold({
atomId: atomLookup.atom.atom_id,
validatorUserId: userId,
humanCorrections: corrections[component],
resultProcessed: applyCorrections(atomLookup.atom.result_processed, corrections[component]),
});
}
}
}
}
if (action === 'approved') {
// Userul a aprobat fără modificări → ridicăm silver la gold
// PATCH cu human_corrections=null
...
}
```
---
## Bootstrap Strategy
Brain pornește gol pe atomi. Pentru a accelera hit rate:
### Opțiunea A — re-rulare istoric (recomandată)
Avem ~thousands de sesiuni completate în `bos_analysis.analysis_session`. Putem face un script one-time:
```
1. SELECT session_id, input_text, techniques (JSONB), ai_tampered (JSONB)
FROM analysis_session WHERE status='completed' AND created_at > '2026-01-01'
2. Pentru fiecare row, calculează content_hash + apel POST /v1/analysis_atom
cu cache_tier='silver', human_validated=false
3. Skip dacă confidence < 60 (sau marcaj bronze)
```
Hit rate jump: 0% → ~25-35% peste noapte.
### Opțiunea B — start gol, learn organic
Hit rate creste lent (5% săpt 1 → 25% săpt 4). Mai puțin risc de a propaga erori vechi.
**Recomandare**: Opțiunea A doar pentru sesiuni completate ≥ 90 zile (probabilitate mare că nu au erori), iar pentru ultimele 90 zile — start gol și lasă organic.
---
## Embedding Model
Brain folosește deja embedding pentru semantic search (vezi `/v1/search` cu cross-encoder reranking). NU schimbăm modelul. Dimensiunea vector field în PG (`vector(1024)` în schema) trebuie aliniată cu modelul actual al brain.
**Action item**: Înainte de migration, query brain `SELECT vector_dims(embedding) FROM atom LIMIT 1` (sau echivalent) ca să confirmăm dimensiunea exactă.
---
## Cost & Latency Impact
### Cost
Cu hit rate 50% după 3 luni, cost LLM scade ~40-50%. Pentru analiză text simplă premium:
- Înainte: $0.05/analiză (techniques + ai + claims + verdict)
- După (50% hits): $0.025-0.030/analiză amortizat
Brain operating cost neglijabil — postgres + pgvector pe mașina existentă (10.11.10.13). Singura cost suplimentar: storage atomi (~ 5KB per atom, 20K atomi/lună = 100MB).
### Latency
| Scenariu | Înainte | După |
|---|---|---|
| Cache HIT exact | n/a | ~80-150ms (brain lookup + serialize) |
| Cache HIT semantic | n/a | ~120-250ms (vector search adaugat) |
| Cache MISS | 5-15s | 5-15s + 100ms (lookup overhead pe miss) |
Lookup overhead pe miss path = ~100ms cost net. Acceptabil dacă hit rate ≥ 30%.
---
## Rollout Plan
### Faza 1 — Brain schema + endpoints (2 zile)
- [ ] Migration brain-side (`analysis_atom` tabel + indexuri)
- [ ] Endpoint-uri `/v1/analysis_atom/lookup`, POST, PATCH, GET stats
- [ ] Test cu Postman: write silver, read back, write gold, lookup match exact + semantic
- [ ] Deploy brain v2 pe staging-ul brain (separate environment dacă există)
### Faza 2 — agent-v3 client integration (2 zile)
- [ ] Extindere `shared/brain/client.ts` cu `lookupAnalysisAtom`, `writeAnalysisAtomAsync`, `patchAnalysisAtomGold`
- [ ] Modificare `techniques/executor.ts` cu lookup + write back
- [ ] Modificare `ai-tampered/executor.ts` la fel
- [ ] Feature flag: `BRAIN_ATOMS_ENABLED=false` default. Activate per env var pentru testing
- [ ] Test E2E: rulează 2x aceeași analiză → a doua vine din cache silver
### Faza 3 — HIL integration (depinde de Doc A)
- [ ] Modificare `moderation-routes.ts` resolve handler să cheme `patchAnalysisAtomGold`
- [ ] UI badge "Verified by analyst" în extensie + dashboard pentru sesiuni cu atom gold
### Faza 4 — Bootstrap (1 zi)
- [ ] Script `scripts/bootstrap-brain-from-history.ts` pentru re-import sesiuni vechi
- [ ] Run pe sesiuni completate înainte de `now() - 90 days`
- [ ] Monitor hit rate creste
### Faza 5 — Monitoring + tuning (continuous)
- [ ] Dashboard Grafana: hit rate per component, gold/silver ratio, drift detection
- [ ] Alertă: hit rate scade brusc → cineva a schimbat prompt
- [ ] Alertă: bronze ratio crește → modelul produce confidence scăzut, problemă de calitate
---
## Open Questions
1. **Repo brain location**: unde e fizic codul brain? Pe ce mașină rulează? Cum se face deploy? (necesar pentru Faza 1)
2. **Brain stack**: e FastAPI + Python? Folosește SQLAlchemy/asyncpg? Pgvector deja instalat? (necesar pentru migration)
3. **Embedding model exact**: Qwen embed local sau alt model? Dimensiune exactă vector? (necesar pentru `vector(N)` în schema)
4. **Cross-encoder reranking**: brain face deja reranking pe `/v1/search`. Aplicăm același reranking și pe `/v1/analysis_atom/lookup` semantic match? Sau e overkill (lookup-ul trebuie să fie fast)?
5. **Atom expiration**: TTL 90 zile pentru silver/bronze. Gold = forever. Acceptabil sau prea generos? Putem pune 30 zile silver pentru a forța regenerare cu modele mai noi.
---
## Risk Register
| Risc | Probabilitate | Impact | Mitigare |
|---|---|---|---|
| Brain endpoint slow (>500ms) → analize globale lente | Medie | Mare | AbortSignal.timeout(2000) pe lookup. Dacă brain nu răspunde în 2s, fail open la LLM |
| Embedding drift (model schimbat în brain) | Mică | Mare | Versionare strict embedding_model_id în atom; la schimbare → invalidate tot |
| Storage atomi explodează | Mică | Mediu | TTL 90 zile + cleanup zilnic; gold păstrat forever (volum mic) |
| Race condition write atom (2 agenți scriu același content_hash) | Medie | Mic | UPSERT pe UNIQUE constraint (content_hash, component, tier, prompt_hash) |
| Mock human_corrections invalid (mod scrie diff care nu se aplică) | Mică | Mare | Validare schema diff în resolve handler; dacă invalid, aprobă fără gold write |
| Brain v2 endpoint break compat cu brain v1 (claims) | Mică | Mare | NU modificăm `verification_cache` table sau `/v1/gather` endpoint. analysis_atom e separat |
---
## Metrice cheie
- `brain_lookup_total{component, tier, hit}` — counter
- `brain_lookup_latency_ms{component, hit}` — histogram
- `brain_atoms_count{component, tier, cache_tier}` — gauge (din /v1/analysis_atom/stats)
- `brain_writes_total{component, cache_tier}` — counter
- `brain_promotions_total{from, to}` — counter
- `analyses_with_cache_hit_ratio` — derived: hit_total / lookup_total
- `analyses_with_gold_ratio` — derived: gold_hits / hit_total
Toate scrise via /metrics endpoint pe agent-v3 + brain.
---
## Dependencies cu Doc A
Brain v2 funcționează STANDALONE pe path-ul silver (LLM cache) FĂRĂ HIL. Beneficiile inițiale (cost reduction 30%) vin de aici, nu din gold.
Gold path (HIL → brain) e benefic doar după ce moderatorii produc atomi gold (~200/lună la 1-2 mod). Beneficiul gold = quality lift + badge "Verified", nu cost reduction.
**Concluzie**: putem deploy Brain v2 înainte de a avea HIL complet. HIL doar adaugă layer gold deasupra.