EchoChamber Studio — hartă vizuală și conceptuală
Acest material este harta completă a aplicației finale: cum se leagă pipeline-ul offline, vectorstore-urile, core/retriever.py, core/agent.py, core/graph.py, app/app.py, rolurile YAML și interfața Gradio. Nu este un rezumat și nu este un README — este materialul de curs complet, vizual și explicativ, care leagă fiecare componentă de etapa în care a fost construită (C1–C8).
Ce trebuie să înțelegi la final
- Aplicația are două faze: un pipeline offline care pregătește datele și indexurile, și un runtime Gradio care servește utilizatorul.
core/conține logica reutilizabilă (retrieval, agent, orchestrare);app/doar o expune într-o interfață — nu rescrie logica.- Știrea sau textul introdus este obiectul comentariului; corpusul recuperat din FAISS este context discursiv (ton, vocabular, poziționare), nu subiectul.
- C8 nu introduce o tehnologie nouă; integrează componentele construite în C2–C7 într-un singur sistem coerent.
- Orice eroare poate fi localizată cu o singură întrebare: problema este în date, în vectorstore, în agent, în graph sau în UI?
Cum folosești acest material
- Citește mai întâi schema offline / runtime (secțiunea 02) — îți dă cadrul general.
- Verifică structura repo-ului (01) — unde se află fiecare piesă.
- Urmărește apelul unui agent (06) — cum se produce un răspuns.
- Urmărește fluxul de dezbatere (07) — cum devine multi-agent.
- Rulează notebook-ul C8 (C8_gradio_tutorial_echochamber.ipynb) — construiește app-ul incremental.
- Verifică aplicația finală cu lista de criterii (secțiunea 14).
01Structura repo-ului
Această secțiune răspunde la întrebarea: Unde se află fiecare piesă a aplicației?
Trei zone, fiecare cu o responsabilitate clară: scripts/ + data/ pentru pipeline-ul offline, core/ pentru logica de business reutilizabilă, app/ pentru interfață. Albastru = folder, gri = fișier.
app/
└─ app.py # UI: 6 tab-uri — Setări·Chat·Rezumat·Agent·Toți agenții·Dezbatere
core/ # logica de business (importată de app)
├─ agent.py # un agent: rol + retrieval + apel LLM (C6)
├─ graph.py # LangGraph: orchestrare multi-agent (C7)
├─ retriever.py # FAISS retrieval pe vectorstore-ul unui agent (C5)
├─ config.py # referință (varianta minimă a app-ului nu îl mai folosește)
└─ metrics.py # placeholder
scripts/ # pipeline CLI: collect → clean → annotate → vectorstore
├─ collect_youtube.py clean_youtube.py
└─ annotate_axis.py build_vectorstore.py
assets/
├─ roles/ roles.yaml # cardurile agenților — sursa pentru app/agent
└─ vectorstores/<slug>/ index.faiss + index.pkl
data/ raw/ cleaned/ annotated/ bubbles/ # pipeline-ul de date
.env # GEMINI / DEEPSEEK / YOUTUBE keys requirements.txt
index.faiss conține vectorii numerici (reprezentarea matematică a textelor, pentru căutare rapidă). index.pkl conține textele originale + metadatele (sursă, agent). FAISS găsește care vectori sunt apropiați; pkl-ul spune ce text era acolo. Ai nevoie de ambele ca să recuperezi un fragment lizibil.Regula slug-ului. Aceeași valoare (anti_sistem, conspirationist, …) apare în 3 locuri și trebuie să fie identică: cheia din assets/roles/roles.yaml, fișierul data/bubbles/<slug>.jsonl și folderul assets/vectorstores/<slug>/. De ce contează: slug-ul este cheia care leagă cele trei. Dacă într-un loc scrie anti_sistem și în altul antisistem, retrieverul caută un folder care nu există și pică cu FileNotFoundError — agentul nu mai are corpus.
02Cele două faze
Această secțiune răspunde la întrebarea: Ce se întâmplă offline și ce se întâmplă când aplicația rulează?
Sistemul are două faze separate. Faza A (offline) se rulează o singură dată și produce indexurile FAISS. Faza B (runtime) servește utilizatorul prin Gradio. Singura legătură dintre ele: vectorstore-urile.
Pipeline de date
YouTube → colectare → curățare → adnotare → bule → vectorstore. Rulat manual din linia de comandă. Cod în scripts/, rezultate în data/ și assets/vectorstores/.
Aplicația Gradio
6 tab-uri. Tab-ul Setări încarcă o știre; restul tab-urilor o folosesc automat ca obiect al comentariului. Cod în app/app.py + core/.
Puntea unică. Faza A produce assets/vectorstores/<slug>/. Faza B le citește la fiecare cerere prin core/retriever.py. În rest, cele două faze sunt complet decuplate — poți reface pipeline-ul fără să atingi aplicația, și invers.
03Pipeline de date offline — fișier cu fișier
Această secțiune răspunde la întrebarea: Cum ajunge un comentariu brut de pe YouTube să fie un vectorstore căutabil?
Fiecare script consumă un fișier și produce altul. Dacă pipeline-ul se rupe, te uiți care fișier lipsește între două etape.
scripts/collect_youtube.py → clean_youtube.py → annotate_axis.py → build_vectorstore.py04Blocuri de construcție (building blocks)
Această secțiune răspunde la întrebarea: Cum se compune app.py din straturi, și ce intră în fiecare strat?
app.py nu este o cutie neagră — se construiește în straturi, ca niște cărămizi. Fundația sunt modulele core/ din C5–C7. Peste ele, fiecare secțiune din fișier adaugă un strat, până la interfața care rulează.
core/ (C5–C7). Săgețile din stânga = ce intră în fiecare strat. Săgețile verticale = fiecare strat se sprijină pe cel de sub el.app/app.pyCe blocuri intră în fiecare tab
Fiecare tab e compus din aceleași tipuri de cărămizi, dar combinate diferit. Citește fiecare coloană de jos în sus: input → procesare → backend.
setup·chat·summary·agent·all_agents·debate din §4–5De ce contează. Odată ce vezi cele 5 tipuri de cărămizi, oricare tab e doar o combinație de 3–4 dintre ele. Asta face codul ușor de citit și de predat: nu memorezi 6 tab-uri, înțelegi 5 piese.
05Cele 6 tab-uri — parcursul utilizatorului
Această secțiune răspunde la întrebarea: Ce face fiecare tab și de unde își ia datele?
Aplicația finală are 6 tab-uri: Setări, Chat, Rezumat, Agent, Toți agenții și Dezbatere. Nu există un sidebar separat — Setări este tab-ul care scrie starea partajată (CFG, ART); celelalte cinci o citesc. Fiecare tab este un gr.Interface.
CFG/ART. Celelalte 5 doar citesc starea și cheamă backend-ul corespunzător cursului lor.setup() scrie; chat·summary·agent·all_agents·debate citescCFG/ART; celelalte 5 citesc starea și cheamă backend-ul potrivit cursului lor.| Tab | Funcție în app.py | Ce folosește | Curs |
|---|---|---|---|
| Setări | setup() | scrie CFG+ART; requests+bs4 | C2 |
| Chat | chat() | ask_llm (răspuns pe știre, dacă există) | C2 |
| Rezumat | summary() | ask_llm (t=0.2) pe ART | C2 |
| Agent | agent() | _subject → _agent → core.agent | C5+C6 |
| Toți agenții | all_agents() | buclă pe ROLES → _agent | C6 |
| Dezbatere | debate() | core.graph.run_thread | C7 |
Regula _subject(). Dacă există ȘI o știre încărcată ȘI un subiect scris în tab → subiectul intră peste știre („discută subiectul X în contextul acestei știri"). Doar știre → se discută știrea. Doar subiect → se discută subiectul.
06Un apel agent
Această secțiune răspunde la întrebarea: Ce se întâmplă când utilizatorul apasă butonul Generează răspuns în tab-ul Agent?
Distincție conceptuală
- știrea / textul introdus
- obiectul despre care agentul trebuie să răspundă (subiectul concret).
- corpusul recuperat prin FAISS
- exemple de ton, vocabular și poziționare discursivă — stilul, nu subiectul.
- roles.yaml
- identitatea discursivă a agentului (cine vorbește și cu ce reguli).
- LLM-ul
- generatorul propriu-zis al răspunsului.
- app.py
- stratul care le conectează pe toate în interfață.
Citește traseul de mai jos având în minte distincția: agentul nu trebuie să comenteze generic corpusul; trebuie să reacționeze la știre, folosind corpusul recuperat doar ca stil și context discursiv, iar rolul ca poziție.
app.py nu generează singur răspunsul: apelează core.agent, care citește rolul din roles.yaml, caută context în FAISS și trimite promptul la LLM. Răspunsul + contextul se întorc în UI.agent() → _agent() → generate_agent_response() din core/agent.py_subject() — subiect peste știre.Reutilizare, nu duplicare. Pașii 2–11 sunt exact ce reutilizează „Toți agenții" (în buclă peste roluri) și „Dezbatere" (prin core.graph). Logica RAG este scrisă o singură dată, în core/agent.py.
07Dezbatere multi-agent
Această secțiune răspunde la întrebarea: Cum se transformă un răspuns izolat într-un thread multi-agent?
Tab-ul Dezbatere ridică un nivel deasupra unui singur agent: router_node alege round-robin cine vorbește, fiecare agent_node reutilizează generate_agent_response() din secțiunea precedentă, apoi revine la router.
core/graph.py nu trebuie să conțină UI. graph.py are o singură responsabilitate: orchestrarea (cine vorbește, în ce ordine, când se oprește). Dacă ar conține și cod de interfață, n-ai mai putea rula o dezbatere din linia de comandă sau dintr-un test, iar logica de orchestrare ar deveni imposibil de reutilizat. Separarea responsabilităților = fiecare modul face un singur lucru.agent_node reutilizează exact apelul din secțiunea 06; bucla se închide când s-a atins numărul de intervenții.run_thread() și route_decision() din core/graph.pyactive_slugs[current_turn % len(active_slugs)]. Bucla continuă până la current_turn == total_turns.08Imaginea de ansamblu — offline → runtime
Această secțiune răspunde la întrebarea: Cum se leagă tot lanțul, de la API extern la răspunsul afișat?
Cele două faze sunt separate; singura legătură este linia punctată amber: vectorstore-urile produse offline, citite de retriever la runtime.
scripts/ + data/ (offline) vs app/ + core/ (runtime)09Dependențe între module — cine importă pe cine
Această secțiune răspunde la întrebarea: Ce importă fiecare modul și de ce app.py nu trebuie să fie gros?
Săgeata = „importă / depinde de". app.py importă direct doar core.agent și core.graph (plus gradio/openai/yaml/requests/bs4). Restul logicii e ascuns în spatele lor.
core/agent.py e nodul către care converg săgețile; app.py e doar vârful subțire.import din capul app/app.py și core/graph.pyapp.py atinge doar core.agent + core.graph. Retrieverul e singurul care deschide vectorstore-urile.core/agent.py este nodul central. Și un singur agent (tab Agent), și „Toți agenții" (buclă), și „Dezbatere" (prin graph.py) trec prin generate_agent_response(). Scrii logica RAG o dată, o reutilizezi de trei ori. Dacă o repari acolo, se repară peste tot.app.py trebuie să fie un strat subțire. Dacă rescrii retrieval-ul sau prompting-ul în app.py, ai două copii ale aceleiași logici care se desincronizează. app.py doar cheamă core/ și afișează rezultatul. Schimbi backend-ul → UI rămâne neatins; schimbi UI-ul → backend rămâne neatins.10Debugging: unde cauți problema?
Această secțiune răspunde la întrebarea: Aplicația nu merge — în care strat caut?
Localizezi problema după simptom. Fiecare simptom corespunde unui strat anume — nu căuta la întâmplare.
| Simptom | Probabil problema este în | Ce verifici |
|---|---|---|
| Aplicația nu pornește | app/app.py sau importuri | rulează python -m app.app și citește mesajul de eroare (import lipsă, sintaxă) |
| Agentul nu găsește context | vectorstore / retriever | assets/vectorstores/<slug>/index.faiss există? Slug-ul e identic peste tot? |
| Agentul răspunde generic | prompt / agent | core/agent.py — separarea știre vs corpus; subiectul ajunge ca obiect, nu corpusul |
| Dezbaterea nu pornește | core/graph.py | run_thread() — minim 2 agenți, numărul de intervenții > 0 |
| Un agent lipsește din listă | roles.yaml / slug | numele agentului în roles.yaml, data/bubbles/ și vectorstores/ — identic? |
| Eroare de API | .env / provider | cheia API există în .env și corespunde providerului ales în Setări |
11Cum se leagă fișierele
Această secțiune răspunde la întrebarea: Ce importă fiecare fișier și cine îl folosește?
| Modul | Importă | Folosit de |
|---|---|---|
core/retriever.py | faiss, sentence_transformers | core/agent.py |
core/agent.py | retriever, langchain_openai, yaml | app.py, graph.py |
core/graph.py | core/agent.py, langgraph | app.py (tab Dezbatere) |
scripts/build_vectorstore.py | faiss, sentence_transformers | rulat manual offline |
scripts/annotate_axis.py | openai, annotation_prompt.md | rulat manual offline |
app/app.py | core.agent, core.graph, gradio, openai, yaml, requests, bs4 | python -m app.app |
core/agent.py · retrieval → core/retriever.py · vectorstore → assets/vectorstores/<slug>/ · roluri → assets/roles/roles.yaml · dezbatere → core/graph.py · interfață → app/app.py · pipeline offline → scripts/ + data/.12Convenții cheie
Această secțiune răspunde la întrebarea: Ce reguli nu se negociază în acest proiect?
Identitatea agentului
Aceeași valoare în 3 locuri: cheia din roles.yaml, data/bubbles/<slug>.jsonl, vectorstores/<slug>/. Inconsistent → FileNotFoundError.
Model unic
paraphrase-multilingual-MiniLM-L12-v2, 384 dim, la build ȘI la search. Schimbarea impune rebuild la toate vectorstore-urile.
Un singur dropdown
Opțiunile sunt "provider|model" (ex. gemini|gemini-2.5-flash). Imposibil ca UI-ul să cuprindă o combinație nepotrivită.
CFG + ART (modul)
Două dicționare la nivel de modul. Tab-ul Setări scrie, restul citesc. (Alternativa „canonică" Gradio = gr.State; varianta minimă alege simplitatea.)
13Entry points — cum se rulează
Această secțiune răspunde la întrebarea: Ce comandă pornește fiecare parte a sistemului?
# Aplicația (Gradio UI · 6 tab-uri)
python -m app.app # sau: python app/app.py
# Un singur agent RAG, din terminal
python -m core.agent --agent anti_sistem --text "..." --provider gemini --k 5
# Dezbatere multi-agent
python -m core.graph --agents anti_sistem conspirationist pro_european \
--text "..." --turns 4 --provider gemini
# Pipeline offline (rulat o singură dată / per refresh date)
python scripts/collect_youtube.py --handle <canal> --output data/raw/x.jsonl
python scripts/clean_youtube.py --input data/raw/x.jsonl --output data/cleaned/x.jsonl
python scripts/annotate_axis.py --input data/cleaned/x.jsonl \
--output data/annotated/x.jsonl --provider gemini
python scripts/build_vectorstore.py
14Criterii de verificare pentru C8
Această secțiune răspunde la întrebarea: Cum știu că aplicația C8 este completă și corectă?
Înainte de predare, verifică punct cu punct:
- aplicația pornește cu
python -m app.app; - se poate încărca o știre din tab-ul Setări;
- tab-ul Chat răspunde pe baza știrii încărcate;
- tab-ul Agent răspunde explicit la știre (nu comentează generic corpusul);
- tab-ul Toți agenții produce răspunsuri diferențiate între roluri;
- tab-ul Dezbatere rulează cu cel puțin doi agenți și mai multe intervenții;
- contextul FAISS nu se afișează în UI dacă este ținut în backend;
- nu există chei API în repo (sunt doar în
.env, negitat); - README explică cum se rulează aplicația și pipeline-ul.
15Timeline — C1 până la aplicația finală
Această secțiune răspunde la întrebarea: Ce a adăugat fiecare săptămână și unde a aterizat în repo?
Opt etape, fiecare cu o componentă. Săgeata = „construiește baza pentru". La C8 toate se conectează în aplicația integrată.
app/app.py care le compune pe toate16Ce am construit, curs cu curs
Această secțiune răspunde la întrebarea: Ce am învățat la fiecare curs și ce bucată din aplicație a produs?
Fiecare card: ce s-a învățat, ce s-a construit fizic în aplicație, ce fișiere au apărut. Aici culoarea transmite informație — fiecare culoare este un curs.
18Cum se asamblează etapele cursului
Această secțiune răspunde la întrebarea: Cum se combină cele opt etape într-o singură aplicație?
Etapele nu sunt o listă — sunt piese care se conectează. Săgeți groase = „construiește direct", punctate = „pregătește baza".
app.py.app/app.pyapp.py.19Cum citim app.py prin componentele cursului
Această secțiune răspunde la întrebarea: Cum recunosc, deschizând app.py, ce a venit din ce curs?
Deschizi app/app.py și fiecare secțiune are o origine clară în curs. Codul nu este o cutie neagră scrisă cândva — fiecare bloc este trasabil.
app/app.py
│
├── §1 IMPORTS & SETUP # aduce core/ + gradio
│
├── §2 CONFIG (PROVIDERS, MODELS, ROLES, ← C2 (multi-provider + .env)
│ CFG, ART) # dropdown unic provider|model
│
├── §3 APELURI LLM / BACKEND
│ ├── ask_llm() ← C2 (LLM direct)
│ ├── _agent() ← C6 (RAG: rol+FAISS+LLM)
│ └── _subject() ← (regula: subiect peste știre)
│
├── §4 TAB Setări — setup() ← C2 (provider/model + scrape știre)
│
├── §5 ACȚIUNI TAB-URI
│ ├── chat() ← C2
│ ├── summary() ← C2
│ ├── agent() ← C5 + C6
│ ├── all_agents() ← C6 (buclă pe roles.yaml)
│ └── debate() ← C7 (core.graph.run_thread)
│
└── §6 UI — 6 gr.Interface în gr.Blocks
├── Tab "Setări" ← C2
├── Tab "Chat" / "Rezumat" ← C2
├── Tab "Agent" ← C5 + C6
├── Tab "Toți agenții" ← C6
└── Tab "Dezbatere" ← C7
Aceasta este ideea centrală a cursului final. Codul nu este o cutie neagră. Fiecare linie are o etapă de origine. Poți deschide app.py și citi cele opt săptămâni în structura lui.
20Concluzie: de la exerciții la sistem integrat
★ Concluzia
C8 închide trecerea de la exerciții separate la sistem integrat. Valoarea aplicației nu stă într-un singur model sau într-un singur prompt, ci în separarea clară a responsabilităților:
datele sunt pregătite offline → contextul este recuperat prin FAISS →rolurile definesc poziții discursive → core/agent.py produce răspunsul →
core/graph.py orchestrează conversația → app.py expune totul în interfață
Acesta este sistemul integrat construit pe parcursul cursului. Fiecare componentă este trasabilă la etapa în care a fost introdusă, iar straturile se sprijină unul pe altul ca niște blocuri de construcție. Aplicația nu este un proiect separat — este suma exactă a celor opt etape.
Material conex: Notebook C8 — cum se construiește app.py pas cu pas.
17Transformarea unui comentariu în context discursiv
Această secțiune răspunde la întrebarea: Cum devine un comentariu brut de pe YouTube context pentru o voce discursivă?
Cel mai clar mod de a vedea cum se leagă etapele: urmărește un singur comentariu cum se transformă, pas cu pas, până devine context discursiv pentru un agent. Fiecare transformare corespunde unui curs.
scripts/(C3–C5) apoicore/agent.py(C6) la runtimeAici totul se leagă: un comentariu real, scris de cineva pe YouTube, devine — prin șase transformări, fiecare predată într-o săptămână — context pentru o intervenție discursivă simulată. Nimic nu este inventat: răspunsul este ancorat în comentarii reale, recuperate semantic, filtrate prin rol.