{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7ce8325a",
   "metadata": {},
   "source": [
    "# C7 — LangGraph pentru agenți conversaționali EchoChamber\n",
    "\n",
    "În C6 am construit un agent RAG simplu: un input politic intră în sistem, agentul recuperează context din FAISS și generează un răspuns în vocea rolului său.\n",
    "\n",
    " În C7 construim un workflow LangGraph în care mai mulți agenți pot participa la același thread. - Fiecare agent are rol propriu, corpus propriu, vede conversația anterioară și produce o intervenție. \n",
    "\n",
    "LangGraph controlează ordinea pașilor, starea conversației și oprirea după un număr de runde.\n",
    "\n",
    "- C6: input → agent RAG → răspuns\n",
    "- C7: stimulus → router → agent → thread → router → agent → ... → END\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "521cf1e2",
   "metadata": {},
   "source": [
    "# 0. Setup și verificare proiect\n",
    "\n",
    "Ne asiguram că notebook-ul rulează din rădăcina proiectului. LangGraph nu înlocuiește codul construit în C5 și C6. Îl organizează într-un workflow. De aceea trebuie să verificăm că există fișierele de bază:\n",
    "\n",
    "- core/agent.py — generează răspunsul agentului RAG;\n",
    "- core/retriever.py — caută fragmente similare în FAISS;\n",
    "- assets/roles/roles.yaml — conține rolurile agenților;\n",
    "- assets/vectorstores/ — conține indexurile FAISS construite în C5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "4d95c1c3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Project root: C:\\PROJECTS\\echochamber-app\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "import os\n",
    "\n",
    "from IPython.display import Image, display\n",
    "\n",
    "# Setăm manual rădăcina proiectului.\n",
    "PROJECT_ROOT = Path(r\"C:\\PROJECTS\\echochamber-app\")\n",
    "os.chdir(PROJECT_ROOT)\n",
    "print(\"Project root:\", Path.cwd())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ea67134",
   "metadata": {},
   "source": [
    "#### Verificam daca avem fisierele"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c7208118",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "core/agent.py: True\n",
      "core/retriever.py: True\n",
      "assets/roles/roles.yaml: True\n",
      "assets/vectorstores/: True\n"
     ]
    }
   ],
   "source": [
    "required_paths = {\n",
    "    \"core/agent.py\": Path(\"core/agent.py\"),\n",
    "    \"core/retriever.py\": Path(\"core/retriever.py\"),\n",
    "    \"assets/roles/roles.yaml\": Path(\"assets/roles/roles.yaml\"),\n",
    "    \"assets/vectorstores/\": Path(\"assets/vectorstores\"),\n",
    "}\n",
    "for label, path in required_paths.items():\n",
    "    print(f\"{label}: {path.exists()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "863580ef",
   "metadata": {},
   "source": [
    "# 1. Recap C6: agent RAG simplu\n",
    "\n",
    "În C6 am construit primul agent RAG. Agentul primește un text politic nou, caută fragmente similare în corpusul lui, folosește rolul definit în YAML și generează un răspuns.\n",
    "Formula C6:\n",
    "\n",
    "input politic\n",
    "→ FAISS retrieval\n",
    "→ context recuperat\n",
    "→ rol YAML\n",
    "→ LLM\n",
    "→ răspuns agent"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b4ae16d",
   "metadata": {},
   "source": [
    "- in C7 în loc să avem un singur agent care răspunde izolat, vom avea mai mulți agenți care intră într-un thread controlat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5efec65f",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\PROJECTS\\echochamber-app\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n",
      "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 5490.83it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Agent: Anti-sistem\n",
      "\n",
      "Stimulus:\n",
      "CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\n",
      "\n",
      "Răspuns:\n",
      "Asta e țara unde CCR, plin de penali și pupincuriști, decide să anuleze alegerile ca să-i protejeze pe cei care ne-au furat destinele. Nu vă mai mirați de ce ne ducem de râpă, sistemul ăsta e putred până în măduva oaselor și nu va fi salvat de nimeni.\n",
      "\n",
      "Context recuperat:\n",
      "[Fragment 1 | score=0.341]\n",
      "Daca nici acum nu intelegeti cine este cg si il mai votati,sunteti de doamne fereste...in afara de boti...Daca nu se anulau alegerile,oricum nu ieseai presedinte...\n",
      "\n",
      "[Fragment 2 | score=0.334]\n",
      "De vina sunt acei concetățeni care, la vot, nu au pe cine vota, stau acasă pentru că votul lor nu contează. I-a să iasă la vot 90% din populație, să vezi atunci care sunt partidele care ne reprezintă.\n",
      "\n",
      "[Fragment 3 | score=0.323]\n",
      "Simion este un escroc , și - a lăsat parlamentarii acasă , a spus râzând că AUR vrea pace , dar au înlesnit votul \" pentru \" ! Dacă ajungea el președinte , era la fel ca Tăntălăul onest ! 🤮🤮🤮\n",
      "\n",
      "[Fragment 4 | score=0.220]\n",
      "Știi ce cîștiga Robert, spălații pe creier? Bani, multi bani pe care îi primesc din banii noștrii! Singura soluție de a scăpa de acești paraziți este modificarea legii partidelor și să se termine cu banii dați de la buget partidelor! Dacă vor să plătească presă să îi pupe în cur , să plătească din banii lor!\n",
      "\n",
      "[Fragment 5 | score=0.216]\n",
      "Dar timp de 26 de ani de ce ai tacut ,? Te ai desteptat cu venirea la guvernare a lui Bolojan ,a presedintelui usrist ? Spune adevarul cu mana pe Biblie\n"
     ]
    }
   ],
   "source": [
    "from core.agent import generate_agent_response\n",
    "\n",
    "stimulus = \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\"\n",
    "\n",
    "result = generate_agent_response(\n",
    "    agent_slug=\"anti_sistem\",\n",
    "    stimulus=stimulus,\n",
    "    provider=\"gemini\",\n",
    "    k=5\n",
    ")\n",
    "\n",
    "print(\"Agent:\", result[\"agent_name\"])\n",
    "print(\"\\nStimulus:\")\n",
    "print(result[\"stimulus\"])\n",
    "\n",
    "print(\"\\nRăspuns:\")\n",
    "print(result[\"response\"])\n",
    "\n",
    "print(\"\\nContext recuperat:\")\n",
    "print(result[\"rag_text\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9549acf4",
   "metadata": {},
   "source": [
    "#### TODO\n",
    "\n",
    "- schimba agentul, vezi daca se schimba vocea, contextul?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d7db0f2",
   "metadata": {},
   "source": [
    "## 2. De ce avem nevoie de LangGraph?\n",
    "În C6 am avut un flux liniar: un input politic intra într-un agent RAG și primeam un răspuns.\n",
    "În C7 vrem o conversație controlată între mai mulți agenți. După fiecare intervenție, sistemul trebuie să decidă cine vorbește mai departe, ce informație se păstrează în conversație și când se oprește thread-ul.\n",
    "| Tip de flux | Formă | Când este suficient |\n",
    "|---|---|---|\n",
    "| Apel LLM simplu | `input → LLM → output` | când vrem un singur răspuns |\n",
    "| Chain liniar | `input → prompt → LLM → parser` | când pașii sunt ficși |\n",
    "| LangGraph | `state → router → node → state update → router / END` | când avem agenți, decizii, bucle sau conversații |\n",
    "În proiectul EchoChamber:\n",
    "| Curs | Flux | Ce produce |\n",
    "|---|---|---|\n",
    "| C6 | `input politic → agent RAG → răspuns` | un răspuns individual |\n",
    "| C7 | `state → router → agent_node → state update → router → ... → END` | un thread multi-agent |\n",
    "Ideea principală:\n",
    "> LangGraph nu face modelul mai inteligent. Face fluxul explicit, controlabil și inspectabil.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "656317ee",
   "metadata": {},
   "source": [
    "## 3. Concepte LangGraph: State, Node, Edge, Conditional Edge\n",
    "Înainte să construim graful, avem nevoie de vocabularul minim LangGraph.\n",
    "| Concept | Explicație simplă | În EchoChamber |\n",
    "|---|---|---|\n",
    "| `State` | datele care circulă prin graf | stimulus, mesaje, tură curentă |\n",
    "| `Node` | funcție Python care modifică state-ul | `router`, `agent_node` |\n",
    "| `Edge` | trecere fixă între două noduri | `agent_node → router` |\n",
    "| `Conditional edge` | alegere pe baza state-ului | `router → agent` sau `router → END` |\n",
    "| `START / END` | intrarea și ieșirea din graf | începutul și finalul conversației |\n",
    "Vizualizare simplă:\n",
    "```text\n",
    "Graf liniar:\n",
    "START → node_A → node_B → END\n",
    "Graf cu decizie:\n",
    "START → router → node_A / node_B / END"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b843d0a2",
   "metadata": {},
   "source": [
    "## 4. Primul graf fără LLM\n",
    "Construim primul graf LangGraph fără LLM și fără API key.\n",
    "Scopul este să vedem mecanica de bază:\n",
    "- definim un `state`;\n",
    "- definim un nod;\n",
    "- conectăm `START → nod → END`;\n",
    "- rulăm graful.\n",
    "Flux:\n",
    "```text\n",
    "START → count_words → END"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "583ddb04",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'message': 'LangGraph controlează pașii unui workflow.', 'word_count': 5}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from typing import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "class SimpleState(TypedDict):\n",
    "    message: str\n",
    "    word_count: int\n",
    "\n",
    "def count_words(state: SimpleState):\n",
    "    return {\"word_count\": len(state[\"message\"].split())}\n",
    "\n",
    "workflow = StateGraph(SimpleState)\n",
    "\n",
    "workflow.add_node(\"count_words\", count_words)\n",
    "workflow.add_edge(START, \"count_words\")\n",
    "workflow.add_edge(\"count_words\", END)\n",
    "\n",
    "graph = workflow.compile()\n",
    "\n",
    "graph.invoke({\n",
    "    \"message\": \"LangGraph controlează pașii unui workflow.\",\n",
    "    \"word_count\": 0\n",
    "})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "850cebed",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "{'message': 'LangGraph controlează pașii unui workflow.', 'word_count': 5}"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph = workflow.compile()\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "\n",
    "graph.invoke({\n",
    "    \"message\": \"LangGraph controlează pașii unui workflow.\",\n",
    "    \"word_count\": 0\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31bd77d1",
   "metadata": {},
   "source": [
    "#### TODO\n",
    "Schimbă textul din `message` și rulează din nou celula.\n",
    "Verifică dacă `word_count` se modifică."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1adeb18b",
   "metadata": {},
   "source": [
    "## 5. Graf cu muchie condițională\n",
    "Acum adăugăm prima decizie în graf.\n",
    "În loc să avem un singur drum:\n",
    "```text\n",
    "START → node → END\n",
    "\n",
    "vem două ramuri posibile:\n",
    "\n",
    "               short_response\n",
    "              ↗\n",
    "START → check_length\n",
    "              ↘\n",
    "               long_response\n",
    "\n",
    "Regula:\n",
    "dacă textul are sub 10 cuvinte, mergem la short_response;\n",
    "dacă textul are 10 cuvinte sau mai mult, mergem la long_response."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "57f1188a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "# State-ul: datele care circulă prin graf\n",
    "class LengthState(TypedDict):\n",
    "    text: str\n",
    "    result: str\n",
    "\n",
    "# Nod pentru texte scurte\n",
    "def short_response(state: LengthState):\n",
    "    return {\"result\": \"Text scurt.\"}\n",
    "\n",
    "# Nod pentru texte lungi\n",
    "def long_response(state: LengthState):\n",
    "    return {\"result\": \"Text lung.\"}\n",
    "\n",
    "# Router: decide ramura următoare\n",
    "def choose_path(state: LengthState):\n",
    "    if len(state[\"text\"].split()) < 10:\n",
    "        return \"short\"\n",
    "    return \"long\"\n",
    "\n",
    "# Construim graful\n",
    "workflow = StateGraph(LengthState)\n",
    "\n",
    "workflow.add_node(\"short_response\", short_response)\n",
    "workflow.add_node(\"long_response\", long_response)\n",
    "\n",
    "# Muchie condițională: START → short_response sau long_response\n",
    "workflow.add_conditional_edges(\n",
    "    START,\n",
    "    choose_path,\n",
    "    {\n",
    "        \"short\": \"short_response\",\n",
    "        \"long\": \"long_response\"\n",
    "    }\n",
    ")\n",
    "\n",
    "workflow.add_edge(\"short_response\", END)\n",
    "workflow.add_edge(\"long_response\", END)\n",
    "\n",
    "graph = workflow.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9ff67111",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "9eaf474a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'text': 'Text politic scurt.', 'result': 'Text scurt.'}"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\n",
    "    \"text\": \"Text politic scurt.\",\n",
    "    \"result\": \"\"\n",
    "})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "34f2a460",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'text': 'Acesta este un text politic mai lung care trebuie trimis pe ramura pentru texte lungi.',\n",
       " 'result': 'Text lung.'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\n",
    "    \"text\": \"Acesta este un text politic mai lung care trebuie trimis pe ramura pentru texte lungi.\",\n",
    "    \"result\": \"\"\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9cbb96bf",
   "metadata": {},
   "source": [
    "## 6. De la graf generic la thread conversațional\n",
    "Până acum am construit grafuri foarte simple. În EchoChamber, nu vrem doar un singur rezultat, ci o conversație între mai mulți agenți.\n",
    "O conversație poate fi reprezentată ca o listă de mesaje. Fiecare mesaj are:\n",
    "| Câmp | Ce înseamnă |\n",
    "|---|---|\n",
    "| `agent` | numele agentului |\n",
    "| `slug` | identificatorul agentului |\n",
    "| `text` | mesajul generat |\n",
    "| `turn` | numărul intervenției |\n",
    "Exemplu de thread:\n",
    "```text\n",
    "Turn 1 — Anti-sistem: ...\n",
    "Turn 2 — Conspiraționist: ...\n",
    "Turn 3 — Pro-european: ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "dbfb472f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Turn 1 — @LibertateRO99: Instituțiile par din nou rupte de oameni.\n",
      "Turn 2 — @EuroOptimistRO: Trebuie să discutăm pe baza procedurilor și a dovezilor.\n"
     ]
    }
   ],
   "source": [
    "HANDLES = {\n",
    "    \"anti_sistem\": \"@LibertateRO99\",\n",
    "    \"conspirationist\": \"@AdevarulViu\",\n",
    "    \"pro_european\": \"@EuroOptimistRO\",\n",
    "    \"anti_suveranist\": \"@CetateanEU\",\n",
    "    \"personalist_salvator\": \"@Marian_GS_Fan\",\n",
    "}\n",
    "\n",
    "# Exemplu manual de thread.\n",
    "# Aceste mesaje sunt doar pentru test.\n",
    "# Mai târziu, ele vor fi generate automat de agenți.\n",
    "messages = [\n",
    "    {\n",
    "        \"agent\": \"Anti-sistem\",\n",
    "        \"slug\": \"anti_sistem\",\n",
    "        \"text\": \"Instituțiile par din nou rupte de oameni.\",\n",
    "        \"turn\": 1\n",
    "    },\n",
    "    {\n",
    "        \"agent\": \"Pro-european\",\n",
    "        \"slug\": \"pro_european\",\n",
    "        \"text\": \"Trebuie să discutăm pe baza procedurilor și a dovezilor.\",\n",
    "        \"turn\": 2\n",
    "    }\n",
    "]\n",
    "\n",
    "def thread_to_text(messages):\n",
    "    lines = []\n",
    "    for message in messages:\n",
    "        handle = HANDLES.get(message[\"slug\"], message[\"slug\"])\n",
    "        line = f\"Turn {message['turn']} — {handle}: {message['text']}\"\n",
    "        lines.append(line)\n",
    "    return \"\\n\".join(lines)\n",
    "\n",
    "print(thread_to_text(messages))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3815860",
   "metadata": {},
   "source": [
    "### To do\n",
    "Adaugă manual un al treilea mesaj în lista `messages`.\n",
    "Apoi rulează din nou `thread_to_text(messages)`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3b2f26d",
   "metadata": {},
   "source": [
    "## 7. Definim `ThreadState`\n",
    "Acum definim state-ul pentru conversația multi-agent.\n",
    "În LangGraph, `state` este obiectul care circulă prin graf. Pentru EchoChamber, el trebuie să păstreze inputul inițial, mesajele produse și informația despre ordinea intervențiilor.\n",
    "| Câmp | Rol |\n",
    "|---|---|\n",
    "| `stimulus` | știrea / inputul politic inițial |\n",
    "| `messages` | conversația generată până acum |\n",
    "| `active_slugs` | agenții care participă |\n",
    "| `total_turns` | câte intervenții vrem |\n",
    "| `current_turn` | câte intervenții au fost produse |\n",
    "| `next_slug` | agentul ales pentru următoarea intervenție |\n",
    "| `provider` | providerul LLM: `gemini` / `deepseek` |\n",
    "| `k` | numărul de fragmente recuperate din FAISS |\n",
    "Vizualizare:\n",
    "```text\n",
    "ThreadState\n",
    "├── stimulus\n",
    "├── messages\n",
    "├── active_slugs\n",
    "├── current_turn / total_turns\n",
    "├── next_slug\n",
    "└── provider, k"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "fed5d6bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import TypedDict\n",
    "\n",
    "class ThreadState(TypedDict):\n",
    "    stimulus: str       # inputul politic inițial\n",
    "    messages: list      # mesajele produse până acum\n",
    "    active_slugs: list  # agenții care participă\n",
    "    total_turns: int    # câte intervenții vrem\n",
    "    current_turn: int   # câte intervenții au fost deja produse\n",
    "    next_slug: str      # agentul ales pentru următoarea intervenție\n",
    "    provider: str       # gemini sau deepseek\n",
    "    k: int              # câte fragmente FAISS recuperăm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8a523a5",
   "metadata": {},
   "source": [
    "### TODO\n",
    "De ce trebuie ca `messages` să fie în `state` și nu într-o variabilă globală?\n",
    "\n",
    "Răspuns așteptat:\n",
    "`messages` trebuie să fie în `state` pentru că fiecare nod din graf trebuie să vadă conversația actualizată. Dacă mesajele ar fi într-o variabilă globală, graful ar fi mai greu de testat, de reluat și de controlat."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d2f7355",
   "metadata": {},
   "source": [
    "## 8. Router simplu: cine vorbește următorul?\n",
    "Acum construim primul router.\n",
    "\n",
    "Routerul este funcția care decide ce agent vorbește la următoarea intervenție.\n",
    "Pentru început folosim regula cea mai simplă: `round-robin`.\n",
    "Asta înseamnă că agenții vorbesc pe rând:\n",
    "```text\n",
    "anti_sistem → conspirationist → pro_european → anti_sistem → ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "152a4018",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Turn 1: anti_sistem (@LibertateRO99)\n",
      "Turn 2: conspirationist (@AdevarulViu)\n",
      "Turn 3: pro_european (@EuroOptimistRO)\n",
      "Turn 4: anti_sistem (@LibertateRO99)\n",
      "Turn 5: conspirationist (@AdevarulViu)\n",
      "Turn 6: pro_european (@EuroOptimistRO)\n"
     ]
    }
   ],
   "source": [
    "def pick_next_round_robin(active_slugs, current_turn):\n",
    "    return active_slugs[current_turn % len(active_slugs)]\n",
    "\n",
    "active_slugs = [\"anti_sistem\", \"conspirationist\", \"pro_european\"]\n",
    "\n",
    "for turn in range(6):\n",
    "    next_slug = pick_next_round_robin(active_slugs, turn)\n",
    "    handle = HANDLES.get(next_slug, next_slug)\n",
    "    print(f\"Turn {turn + 1}: {next_slug} ({handle})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1cc0d693",
   "metadata": {},
   "source": [
    "Acum transformăm regula într-un `router_node`, adică într-un nod care primește `state` și actualizează câmpul `next_slug`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "03f6a1f1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'next_slug': 'anti_sistem'}"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def router_node(state: ThreadState):\n",
    "    # Dacă am produs deja toate intervențiile, oprim conversația\n",
    "    if state[\"current_turn\"] >= state[\"total_turns\"]:\n",
    "        return {\"next_slug\": \"__end__\"}\n",
    "    \n",
    "    # Altfel alegem următorul agent\n",
    "    next_slug = pick_next_round_robin(\n",
    "        state[\"active_slugs\"],\n",
    "        state[\"current_turn\"]\n",
    "    )\n",
    "    \n",
    "    return {\"next_slug\": next_slug}\n",
    "\n",
    "\n",
    "test_state = {\n",
    "    \"stimulus\": \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\",\n",
    "    \"messages\": [],\n",
    "    \"active_slugs\": [\"anti_sistem\", \"conspirationist\", \"pro_european\"],\n",
    "    \"total_turns\": 4,\n",
    "    \"current_turn\": 0,\n",
    "    \"next_slug\": \"\",\n",
    "    \"provider\": \"gemini\",\n",
    "    \"k\": 3\n",
    "}\n",
    "\n",
    "router_node(test_state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50777b72",
   "metadata": {},
   "source": [
    "### Mini-task\n",
    "Schimbă `current_turn` în `test_state` la `1`, `2`, `3` și `4`.\n",
    "Observă:\n",
    "- ce agent este ales;\n",
    "- când `next_slug` devine `\"__end__\"`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "928b824f",
   "metadata": {},
   "source": [
    "## 9. Agent node: cum produce un agent o intervenție\n",
    "Acum construim un nod care cheamă agentul RAG din C6.\n",
    "Un `agent_node` primește `state`, citește conversația de până acum și adaugă un mesaj nou în `messages`.\n",
    "Ce face nodul:\n",
    "1. citește `stimulus`;\n",
    "2. citește thread-ul anterior;\n",
    "3. construiește un input extins;\n",
    "4. cheamă `generate_agent_response()`;\n",
    "5. adaugă mesajul în thread;\n",
    "6. crește `current_turn`.\n",
    "Important: routerul decide **cine vorbește**. Agent node-ul decide **ce spune agentul**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "7fe9cc67",
   "metadata": {},
   "outputs": [],
   "source": [
    "from core.agent import generate_agent_response\n",
    "\n",
    "def make_agent_node(slug):\n",
    "    def agent_node(state: ThreadState):\n",
    "        thread_text = thread_to_text(state[\"messages\"])\n",
    "        my_handle = HANDLES.get(slug, slug)\n",
    "\n",
    "        if state[\"messages\"]:\n",
    "            last_message = state[\"messages\"][-1]\n",
    "            last_handle = HANDLES.get(last_message[\"slug\"], last_message[\"slug\"])\n",
    "            last_text = last_message[\"text\"]\n",
    "\n",
    "            task = f\"\"\"\n",
    "Ultimul mesaj a fost scris de {last_handle}:\n",
    "\"{last_text}\"\n",
    "\n",
    "Răspunde direct lui {last_handle}. Poți fi de acord sau poți contrazice, dar trebuie să continui conversația.\n",
    "\"\"\"\n",
    "        else:\n",
    "            task = \"\"\"\n",
    "Ești primul agent care răspunde. Reacționează la stimulusul inițial.\n",
    "\"\"\"\n",
    "\n",
    "        agent_input = f\"\"\"\n",
    "[STIMULUS]\n",
    "{state[\"stimulus\"]}\n",
    "\n",
    "[THREAD ANTERIOR]\n",
    "{thread_text}\n",
    "\n",
    "[SARCINĂ]\n",
    "Scrie ca {my_handle}.\n",
    "{task}\n",
    "\n",
    "Reguli:\n",
    "- scrie 2-3 propoziții;\n",
    "- nu repeta mecanic ce s-a spus deja;\n",
    "- menționează explicit agentul la care răspunzi dacă există mesaj anterior;\n",
    "- păstrează vocea discursivă a agentului tău.\n",
    "\"\"\"\n",
    "\n",
    "        result = generate_agent_response(\n",
    "            agent_slug=slug,\n",
    "            stimulus=agent_input,\n",
    "            provider=state[\"provider\"],\n",
    "            k=state[\"k\"]\n",
    "        )\n",
    "\n",
    "        new_message = {\n",
    "            \"agent\": result[\"agent_name\"],\n",
    "            \"slug\": slug,\n",
    "            \"handle\": my_handle,\n",
    "            \"text\": result[\"response\"],\n",
    "            \"turn\": state[\"current_turn\"] + 1\n",
    "        }\n",
    "\n",
    "        return {\n",
    "            \"messages\": state[\"messages\"] + [new_message],\n",
    "            \"current_turn\": state[\"current_turn\"] + 1\n",
    "        }\n",
    "\n",
    "    return agent_node"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "20354ee1",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 7480.83it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Turn 1 — @LibertateRO99: Păi normal că se anulează, că altfel cum să mai bage ei pe cine vor ei? Asta e țara unde legile sunt făcute să-i protejeze pe ei, nu pe noi, iar dacă cineva îndrăznește să se opună, îi bagă la sertar sau îi anulează totul.\n"
     ]
    }
   ],
   "source": [
    "test_state = {\n",
    "    \"stimulus\": \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\",\n",
    "    \"messages\": [],\n",
    "    \"active_slugs\": [\"anti_sistem\", \"conspirationist\", \"pro_european\"],\n",
    "    \"total_turns\": 4,\n",
    "    \"current_turn\": 0,\n",
    "    \"next_slug\": \"\",\n",
    "    \"provider\": \"gemini\",\n",
    "    \"k\": 3\n",
    "}\n",
    "\n",
    "anti_sistem_node = make_agent_node(\"anti_sistem\")\n",
    "result_state_update = anti_sistem_node(test_state)\n",
    "\n",
    "print(thread_to_text(result_state_update[\"messages\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5f5a505",
   "metadata": {},
   "source": [
    "### TODO\n",
    "Schimbă agentul din:\n",
    "```python\n",
    "make_agent_node(\"anti_sistem\") "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bdd9713",
   "metadata": {},
   "source": [
    "## 10. Construim graful multi-agent\n",
    "Acum punem împreună piesele construite până aici:\n",
    "- `router_node` decide cine vorbește;\n",
    "- `make_agent_node(slug)` creează câte un nod pentru fiecare agent;\n",
    "- după fiecare intervenție, agentul se întoarce la `router`;\n",
    "- când s-au terminat rundele, graful ajunge la `END`.\n",
    "Flux:\n",
    "```text\n",
    "START → router → agent_node → router → agent_node → ... → END"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "b4222db0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "def route_decision(state: ThreadState):\n",
    "    return state[\"next_slug\"]\n",
    "\n",
    "active_slugs = [\"anti_sistem\", \"conspirationist\", \"pro_european\"]\n",
    "\n",
    "workflow = StateGraph(ThreadState)\n",
    "\n",
    "# Nodul care decide următorul agent\n",
    "workflow.add_node(\"router\", router_node)\n",
    "\n",
    "# Nodurile agenților\n",
    "for slug in active_slugs:\n",
    "    workflow.add_node(slug, make_agent_node(slug))\n",
    "\n",
    "# Intrarea în graf\n",
    "workflow.add_edge(START, \"router\")\n",
    "\n",
    "# Routerul trimite fluxul către agentul ales sau către END\n",
    "workflow.add_conditional_edges(\n",
    "    \"router\",\n",
    "    route_decision,\n",
    "    {\n",
    "        \"anti_sistem\": \"anti_sistem\",\n",
    "        \"conspirationist\": \"conspirationist\",\n",
    "        \"pro_european\": \"pro_european\",\n",
    "        \"__end__\": END\n",
    "    }\n",
    ")\n",
    "\n",
    "# După ce un agent vorbește, revenim la router\n",
    "for slug in active_slugs:\n",
    "    workflow.add_edge(slug, \"router\")\n",
    "\n",
    "graph = workflow.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "08c5f7c9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba4823a7",
   "metadata": {},
   "source": [
    "În acest graf, fiecare agent se întoarce la `router` deoarece routerul trebuie să decidă următorul pas:\n",
    "- continuăm conversația?\n",
    "- cine vorbește mai departe?\n",
    "- sau ne oprim la `END`?\n",
    "### TODO\n",
    "De ce fiecare `agent_node` se întoarce la `router` și nu merge direct la următorul agent?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9fe102a",
   "metadata": {},
   "source": [
    "## 11. Rulăm primul thread multi-agent\n",
    "Acum rulăm graful complet.\n",
    "Folosim același stimulus politic și trei agenți:\n",
    "```text\n",
    "anti_sistem\n",
    "conspirationist\n",
    "pro_european"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "e8fee164",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 13767.24it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 6564.17it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 10099.05it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 7663.96it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Turn 1 — @LibertateRO99: Păi normal că se anulează, că altfel ieșea cine nu trebuia și se stricau jocurile lor! Asta e țara unde legile se fac și se desfac după cum le convine lor, iar noi, proștii, stăm și ne uităm cum ne fură pe față!\n",
      "Turn 2 — @AdevarulViu: @LibertateRO99, exact asta zic și eu, nu e deloc întâmplător că CCR a intervenit acum, când se vede clar că marile jocuri de culise sunt pe cale să fie date peste cap. Cine controlează de fapt aceste decizii și ce interese externe se ascund în spatele lor, asta e întrebarea reală.\n",
      "Turn 3 — @EuroOptimistRO: @AdevarulViu, speculațiile despre jocuri de culise și interese externe, fără dovezi concrete, alimentează doar neîncrederea în instituții. Deciziile CCR trebuie analizate prin prisma respectării procedurilor legale și a Constituției, nu prin prisma unor teorii conspiraționiste.\n",
      "Turn 4 — @LibertateRO99: @EuroOptimistRO, ce dovezi vrei, măi omule, când totul e la vedere? Pensiile alea speciale, dosarele alea făcute pe comandă, abuzurile alea zilnice, astea nu sunt dovezi? CCR-ul ăsta e doar o unealtă, la fel ca toți ceilalți, să vă păstreze voi privilegiile și noi să ne uităm cum ne jefuiți.\n"
     ]
    }
   ],
   "source": [
    "initial_state = {\n",
    "    \"stimulus\": \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\",\n",
    "    \"messages\": [],\n",
    "    \"active_slugs\": [\"anti_sistem\", \"conspirationist\", \"pro_european\"],\n",
    "    \"total_turns\": 4,\n",
    "    \"current_turn\": 0,\n",
    "    \"next_slug\": \"\",\n",
    "    \"provider\": \"gemini\",\n",
    "    \"k\": 3\n",
    "}\n",
    "\n",
    "final_state = graph.invoke(initial_state)\n",
    "\n",
    "print(thread_to_text(final_state[\"messages\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df6f10aa",
   "metadata": {},
   "source": [
    "## 12. Vizualizăm thread-ul\n",
    "În aplicație, conversația nu va fi afișată ca tabel, ci ca o listă de mesaje/carduri. De aceea facem aceeași logică și în notebook: transformăm lista `messages` într-un bloc HTML ușor de citit.\n",
    "Scopul vizualizării:\n",
    "- verificăm ordinea intervențiilor;\n",
    "- vedem ce agent a vorbit;\n",
    "- citim conversația ca thread, nu ca listă brută de dicționare."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "ea9aa35c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #e05a35;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#e05a35;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Anti-sistem\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @LibertateRO99\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #1\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                Păi normal că se anulează, că altfel ieșea cine nu trebuia și se stricau jocurile lor! Asta e țara unde legile se fac și se desfac după cum le convine lor, iar noi, proștii, stăm și ne uităm cum ne fură pe față!\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #9b5de5;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#9b5de5;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Conspiraționist\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @AdevarulViu\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #2\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @LibertateRO99, exact asta zic și eu, nu e deloc întâmplător că CCR a intervenit acum, când se vede clar că marile jocuri de culise sunt pe cale să fie date peste cap. Cine controlează de fapt aceste decizii și ce interese externe se ascund în spatele lor, asta e întrebarea reală.\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #4a9eff;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#4a9eff;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Pro-european\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @EuroOptimistRO\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #3\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @AdevarulViu, speculațiile despre jocuri de culise și interese externe, fără dovezi concrete, alimentează doar neîncrederea în instituții. Deciziile CCR trebuie analizate prin prisma respectării procedurilor legale și a Constituției, nu prin prisma unor teorii conspiraționiste.\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #e05a35;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#e05a35;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Anti-sistem\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @LibertateRO99\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #4\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @EuroOptimistRO, ce dovezi vrei, măi omule, când totul e la vedere? Pensiile alea speciale, dosarele alea făcute pe comandă, abuzurile alea zilnice, astea nu sunt dovezi? CCR-ul ăsta e doar o unealtă, la fel ca toți ceilalți, să vă păstreze voi privilegiile și noi să ne uităm cum ne jefuiți.\n",
       "            </div>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import HTML, display\n",
    "import html\n",
    "\n",
    "AGENT_COLORS = {\n",
    "    \"anti_sistem\": \"#e05a35\",\n",
    "    \"conspirationist\": \"#9b5de5\",\n",
    "    \"pro_european\": \"#4a9eff\",\n",
    "    \"anti_suveranist\": \"#00a896\",\n",
    "    \"personalist_salvator\": \"#f4a261\",\n",
    "}\n",
    "\n",
    "def show_thread_cards(messages):\n",
    "    cards = []\n",
    "\n",
    "    for msg in messages:\n",
    "        slug = msg.get(\"slug\", \"\")\n",
    "        color = AGENT_COLORS.get(slug, \"#777\")\n",
    "        agent = html.escape(str(msg.get(\"agent\", slug)))\n",
    "        handle = html.escape(str(msg.get(\"handle\", HANDLES.get(slug, slug))))\n",
    "        text = html.escape(str(msg.get(\"text\", \"\")))\n",
    "        turn = msg.get(\"turn\", \"\")\n",
    "\n",
    "        card = f\"\"\"\n",
    "        <div style=\"\n",
    "            background:#16161a;\n",
    "            border-left:3px solid {color};\n",
    "            border-top:1px solid #1e1e28;\n",
    "            border-right:1px solid #1e1e28;\n",
    "            border-bottom:1px solid #1e1e28;\n",
    "            padding:12px 14px;\n",
    "            margin:8px 0;\n",
    "            font-family:'Courier New', monospace;\n",
    "        \">\n",
    "            <div style=\"\n",
    "                display:flex;\n",
    "                align-items:baseline;\n",
    "                gap:10px;\n",
    "                margin-bottom:6px;\n",
    "            \">\n",
    "                <span style=\"\n",
    "                    color:{color};\n",
    "                    font-size:12px;\n",
    "                    font-weight:700;\n",
    "                    letter-spacing:.08em;\n",
    "                    text-transform:uppercase;\n",
    "                \">\n",
    "                    {agent}\n",
    "                </span>\n",
    "\n",
    "                <span style=\"\n",
    "                    color:#9a969f;\n",
    "                    font-size:12px;\n",
    "                    font-weight:500;\n",
    "                \">\n",
    "                    {handle}\n",
    "                </span>\n",
    "\n",
    "                <span style=\"\n",
    "                    margin-left:auto;\n",
    "                    color:#5a5660;\n",
    "                    font-size:11px;\n",
    "                \">\n",
    "                    #{turn}\n",
    "                </span>\n",
    "            </div>\n",
    "\n",
    "            <div style=\"\n",
    "                color:#c0bcb6;\n",
    "                font-size:13px;\n",
    "                line-height:1.6;\n",
    "            \">\n",
    "                {text}\n",
    "            </div>\n",
    "        </div>\n",
    "        \"\"\"\n",
    "        cards.append(card)\n",
    "\n",
    "    display(HTML(\"\\n\".join(cards)))\n",
    "\n",
    "show_thread_cards(final_state[\"messages\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed83269f",
   "metadata": {},
   "source": [
    "## 13. Variante avansate\n",
    "Ce am construit până acum este varianta minimă: router simplu + thread multi-agent.\n",
    "Pentru C7 este suficient. Mai jos sunt extensii posibile pentru aplicația finală.\n",
    "| Extensie | Ce face | Când merită |\n",
    "|---|---|---|\n",
    "| Router LLM | modelul decide cine vorbește următorul | când vrem conversații mai dinamice |\n",
    "| RSS starter | stimulusul vine dintr-o știre reală | când vrem un demo mai realist |\n",
    "| Critic agent | marchează probleme în thread | când vrem evaluare sau moderare |\n",
    "| Human review | omul validează thread-ul | când aplicația este publică sau sensibilă |\n",
    "| Checkpointing | salvează starea grafului | pentru sesiuni lungi, debug sau reluare |\n",
    "Important:\n",
    "```text\n",
    "C7 obligatoriu = router simplu + thread multi-agent.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d62f55c",
   "metadata": {},
   "source": [
    "## 14. Extensie opțională: Router LLM\n",
    "Până acum routerul alegea agenții în ordine fixă:\n",
    "`anti_sistem → conspirationist → pro_european → ...`\n",
    "Acum testăm o variantă mai agentică: modelul citește conversația și decide cine ar trebui să răspundă următorul.\n",
    "Schimbăm doar routerul. Nodurile agenților rămân aceleași."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a4cc71b",
   "metadata": {},
   "source": [
    "### router LLM:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "ded6ba49",
   "metadata": {},
   "outputs": [],
   "source": [
    "from core.agent import make_llm\n",
    "\n",
    "def router_node_llm(state: ThreadState):\n",
    "    # Oprim conversația dacă am ajuns la numărul maxim de intervenții\n",
    "    if state[\"current_turn\"] >= state[\"total_turns\"]:\n",
    "        return {\"next_slug\": \"__end__\"}\n",
    "\n",
    "    active_slugs = state[\"active_slugs\"]\n",
    "\n",
    "    # Dacă nu există încă mesaje, începem cu primul agent\n",
    "    if not state[\"messages\"]:\n",
    "        return {\"next_slug\": active_slugs[0]}\n",
    "\n",
    "    # Nu lăsăm același agent să vorbească de două ori la rând\n",
    "    last_slug = state[\"messages\"][-1][\"slug\"]\n",
    "    candidates = [slug for slug in active_slugs if slug != last_slug]\n",
    "\n",
    "    if not candidates:\n",
    "        candidates = active_slugs\n",
    "\n",
    "    options = \"\\n\".join(\n",
    "        f\"- {slug}: {HANDLES.get(slug, slug)}\"\n",
    "        for slug in candidates\n",
    "    )\n",
    "\n",
    "    prompt = f\"\"\"\n",
    "Ai o conversație între agenți.\n",
    "\n",
    "[STIMULUS]\n",
    "{state[\"stimulus\"]}\n",
    "\n",
    "[THREAD]\n",
    "{thread_to_text(state[\"messages\"])}\n",
    "\n",
    "[Agenți disponibili]\n",
    "{options}\n",
    "\n",
    "Alege agentul care ar trebui să răspundă următorul.\n",
    "Nu alege agentul care tocmai a vorbit.\n",
    "Răspunde doar cu slug-ul exact al agentului.\n",
    "\"\"\"\n",
    "\n",
    "    llm = make_llm(provider=state[\"provider\"], temperature=0.1)\n",
    "    response = llm.invoke(prompt)\n",
    "\n",
    "    chosen = response.content.strip().lower()\n",
    "\n",
    "    # Acceptăm răspunsul doar dacă este un candidat valid\n",
    "    if chosen in candidates:\n",
    "        return {\"next_slug\": chosen}\n",
    "\n",
    "    # Fallback: alegem primul candidat permis\n",
    "    return {\"next_slug\": candidates[0]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0d267e4",
   "metadata": {},
   "source": [
    "### construim graful cu router LLM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "5052873e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "def route_decision(state: ThreadState):\n",
    "    return state[\"next_slug\"]\n",
    "\n",
    "active_slugs = [\"anti_sistem\", \"conspirationist\", \"pro_european\"]\n",
    "\n",
    "workflow_llm_router = StateGraph(ThreadState)\n",
    "\n",
    "# Folosim routerul LLM în locul routerului round-robin\n",
    "workflow_llm_router.add_node(\"router\", router_node_llm)\n",
    "\n",
    "# Nodurile agenților rămân aceleași\n",
    "for slug in active_slugs:\n",
    "    workflow_llm_router.add_node(slug, make_agent_node(slug))\n",
    "\n",
    "workflow_llm_router.add_edge(START, \"router\")\n",
    "\n",
    "workflow_llm_router.add_conditional_edges(\n",
    "    \"router\",\n",
    "    route_decision,\n",
    "    {\n",
    "        \"anti_sistem\": \"anti_sistem\",\n",
    "        \"conspirationist\": \"conspirationist\",\n",
    "        \"pro_european\": \"pro_european\",\n",
    "        \"__end__\": END\n",
    "    }\n",
    ")\n",
    "\n",
    "for slug in active_slugs:\n",
    "    workflow_llm_router.add_edge(slug, \"router\")\n",
    "\n",
    "graph_llm_router = workflow_llm_router.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "5957fc29",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph_llm_router.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "8cdbea71",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 6839.62it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 16514.31it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 8764.65it/s]\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 10052.95it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Turn 1 — @LibertateRO99: @LibertateRO99: Păi bravo lor, au găsit acum scuza cu influențe externe după ce au stat și au privit cum ne fură ăștia de zeci de ani în față. Să anulezi niște alegeri pentru că nu-ți place rezultatul e culmea tupeului, dar la pensiile speciale și dosarele cu șpăgi nu mai are nimeni curaj să pună tunul. Rușine, mă, că ne tratați ca pe niște proști și ne tot băgați minciuni pe gât.\n",
      "Turn 2 — @AdevarulViu: @LibertateRO99: Ai dreptate că e suspect momentul, dar nu e deloc întâmplător că exact acum se activează această decizie, când serviciile și rețelele externe își joacă ultimele cărți în România. Cine controlează de fapt butoanele astea de la CCR știe bine că nu e vorba de vot, ci de cine trage sforile din umbră.\n",
      "Turn 3 — @EuroOptimistRO: @AdevarulViu: Nu e vorba de cine trage sforile din umbră, ci de niște documente desecretizate ale CSAT care arată exact ce fluxuri de bani și influență au venit din exterior. CCR nu a anulat alegerile pentru că nu i-a plăcut rezultatul, ci pentru că procedura electorală a fost viciată în mod sistematic, iar asta e singura cale legală să protejăm statul de drept.\n",
      "Turn 4 — @LibertateRO99: @EuroOptimistRO: Documente desecretizate, ce să mai zic, acoperă orice mizerie când vor să justifice o decizie politică. Până acum câteva luni nu vedea nimeni nicio influență externă, dar dintr-o dată s-au trezit toți să salveze democrația, în timp ce pensiile speciale și dosarele cu șpăgi zac în sertare.\n"
     ]
    }
   ],
   "source": [
    "# rulare\n",
    "initial_state_llm_router = {\n",
    "    \"stimulus\": \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\",\n",
    "    \"messages\": [],\n",
    "    \"active_slugs\": [\"anti_sistem\", \"conspirationist\", \"pro_european\"],\n",
    "    \"total_turns\": 4,\n",
    "    \"current_turn\": 0,\n",
    "    \"next_slug\": \"\",\n",
    "    \"provider\": \"deepseek\",\n",
    "    \"k\": 3\n",
    "}\n",
    "\n",
    "final_state_llm_router = graph_llm_router.invoke(initial_state_llm_router)\n",
    "\n",
    "print(thread_to_text(final_state_llm_router[\"messages\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "47f3e500",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #e05a35;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#e05a35;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Anti-sistem\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @LibertateRO99\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #1\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @LibertateRO99: Păi bravo lor, au găsit acum scuza cu influențe externe după ce au stat și au privit cum ne fură ăștia de zeci de ani în față. Să anulezi niște alegeri pentru că nu-ți place rezultatul e culmea tupeului, dar la pensiile speciale și dosarele cu șpăgi nu mai are nimeni curaj să pună tunul. Rușine, mă, că ne tratați ca pe niște proști și ne tot băgați minciuni pe gât.\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #9b5de5;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#9b5de5;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Conspiraționist\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @AdevarulViu\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #2\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @LibertateRO99: Ai dreptate că e suspect momentul, dar nu e deloc întâmplător că exact acum se activează această decizie, când serviciile și rețelele externe își joacă ultimele cărți în România. Cine controlează de fapt butoanele astea de la CCR știe bine că nu e vorba de vot, ci de cine trage sforile din umbră.\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #4a9eff;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#4a9eff;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Pro-european\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @EuroOptimistRO\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #3\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @AdevarulViu: Nu e vorba de cine trage sforile din umbră, ci de niște documente desecretizate ale CSAT care arată exact ce fluxuri de bani și influență au venit din exterior. CCR nu a anulat alegerile pentru că nu i-a plăcut rezultatul, ci pentru că procedura electorală a fost viciată în mod sistematic, iar asta e singura cale legală să protejăm statul de drept.\n",
       "            </div>\n",
       "        </div>\n",
       "        \n",
       "\n",
       "        <div style=\"\n",
       "            background:#16161a;\n",
       "            border-left:3px solid #e05a35;\n",
       "            border-top:1px solid #1e1e28;\n",
       "            border-right:1px solid #1e1e28;\n",
       "            border-bottom:1px solid #1e1e28;\n",
       "            padding:12px 14px;\n",
       "            margin:8px 0;\n",
       "            font-family:'Courier New', monospace;\n",
       "        \">\n",
       "            <div style=\"\n",
       "                display:flex;\n",
       "                align-items:baseline;\n",
       "                gap:10px;\n",
       "                margin-bottom:6px;\n",
       "            \">\n",
       "                <span style=\"\n",
       "                    color:#e05a35;\n",
       "                    font-size:12px;\n",
       "                    font-weight:700;\n",
       "                    letter-spacing:.08em;\n",
       "                    text-transform:uppercase;\n",
       "                \">\n",
       "                    Anti-sistem\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    color:#9a969f;\n",
       "                    font-size:12px;\n",
       "                    font-weight:500;\n",
       "                \">\n",
       "                    @LibertateRO99\n",
       "                </span>\n",
       "\n",
       "                <span style=\"\n",
       "                    margin-left:auto;\n",
       "                    color:#5a5660;\n",
       "                    font-size:11px;\n",
       "                \">\n",
       "                    #4\n",
       "                </span>\n",
       "            </div>\n",
       "\n",
       "            <div style=\"\n",
       "                color:#c0bcb6;\n",
       "                font-size:13px;\n",
       "                line-height:1.6;\n",
       "            \">\n",
       "                @EuroOptimistRO: Documente desecretizate, ce să mai zic, acoperă orice mizerie când vor să justifice o decizie politică. Până acum câteva luni nu vedea nimeni nicio influență externă, dar dintr-o dată s-au trezit toți să salveze democrația, în timp ce pensiile speciale și dosarele cu șpăgi zac în sertare.\n",
       "            </div>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_thread_cards(final_state_llm_router[\"messages\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2c6da0e",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "72e6c72b",
   "metadata": {},
   "source": [
    "!!!!!!aci e gresit"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f04e9e6",
   "metadata": {},
   "source": [
    " 17. Extragem codul în `core/graph.py`\n",
    "Până acum am construit graful în notebook. Următorul pas este să mutăm logica într-un fișier reutilizabil:\n",
    "```text\n",
    "core/graph.py\n",
    "\n",
    "De ce facem asta:\n",
    "\n",
    "notebook-ul este bun pentru învățare și testare;\n",
    "core/graph.py este bun pentru aplicație;\n",
    "app.py va putea apela direct run_thread().\n",
    "Funcția principală va fi:\n",
    "run_thread(stimulus, active_slugs, total_turns=4, provider=\"gemini\", k=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13f7dff4",
   "metadata": {},
   "source": [
    "## 18. Integrare minimă în aplicație\n",
    "După ce `core/graph.py` funcționează, aplicația poate apela direct funcția:\n",
    "```python\n",
    "run_thread()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2f16902",
   "metadata": {},
   "source": [
    "### Test din terminal\n",
    "După ce salvați fișierul `core/graph.py`, rulați din rădăcina proiectului:\n",
    "```bash\n",
    "python -m core.graph --agents anti_sistem conspirationist pro_european --text \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\" --turns 4 --provider gemini"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "349a6855",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "b9dfad50",
   "metadata": {},
   "source": [
    "Pentru C7 nu construim încă interfața finală. Pregătim doar ideea unui tab nou:\n",
    "\n",
    "Multi-agent thread\n",
    "\n",
    "Inputuri minime:\n",
    "\n",
    "Input\tRol\n",
    "stimulus\ttextul politic / știrea de la care pornește conversația\n",
    "active_slugs\tagenții care participă\n",
    "total_turns\tnumărul de intervenții\n",
    "provider\tmodelul folosit: gemini sau deepseek\n",
    "Output:\t\n",
    "Output\tRol\n",
    "---\t---\n",
    "thread\tlista de mesaje generate de agenți\n",
    "vizualizare HTML\tconversația afișată ca mesaje/carduri\n",
    "Structura minimă în aplicație:\t\n",
    "stimulus + agenți + număr runde\n",
    "        ↓\n",
    "run_thread()\n",
    "        ↓\n",
    "thread generat\n",
    "        ↓\n",
    "afișare ca listă de mesaje\n",
    "\n",
    "Pentru C8 putem îmbunătăți interfața cu:\n",
    "\n",
    "carduri pe culorile agenților;\n",
    "export JSONL;\n",
    "afișarea surselor RAG;\n",
    "metrici simple;\n",
    "disclaimer și limitări.\n",
    "Notă:\n",
    "Dacă nu este timp în C7, integrarea completă în app.py rămâne pentru C8.\n",
    "Mini-task\n",
    "\n",
    "Ce trebuie să afișeze aplicația pentru ca thread-ul să fie interpretabil?\n",
    "Completează tabelul:\n",
    "\n",
    "Element afișat\tDe ce este util?\n",
    "numele agentului\t...\n",
    "handle-ul agentului\t...\n",
    "numărul intervenției\t...\n",
    "textul intervenției\t...\n",
    "contextul RAG / sursele\t..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "575c509e",
   "metadata": {},
   "source": [
    "## 19. Etică și limite\n",
    "Un sistem multi-agent care simulează bule discursive poate fi util pentru cercetare și educație, dar are limite clare. Agenții nu sunt persoane reale. Ei nu exprimă opinii autentice, ci generează răspunsuri pe baza unui rol definit în YAML, a unui corpus de comentarii și a unui model lingvistic.\n",
    "Aceste răspunsuri nu trebuie citite ca adevăr factual. Ele sunt simulări discursive: arată cum poate fi reprodus un anumit stil de interpretare, nu ce „gândește” un grup social real.\n",
    "Riscuri principale:\n",
    "| Risc | Ce înseamnă |\n",
    "|---|---|\n",
    "| Antropomorfizare | putem ajunge să tratăm agenții ca persoane reale |\n",
    "| Halucinație | modelul poate adăuga afirmații care nu sunt susținute de context |\n",
    "| Amplificare discursivă | interacțiunea multi-agent poate intensifica conflictul sau polarizarea |\n",
    "| Bias din corpus | corpusul poate conține limbaj problematic, stereotipuri sau dezechilibre |\n",
    "| Confuzie metodologică | simularea nu înlocuiește analiza empirică a actorilor reali |\n",
    "Regulă de prezentare:\n",
    "```text\n",
    "EchoChamber este un instrument de simulare și analiză discursivă, nu un sistem de predicție politică și nu o reprezentare fidelă a unor persoane reale."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "194a3687",
   "metadata": {},
   "source": [
    "Scrie un disclaimer de 3–4 fraze pentru aplicația EchoChamber.\n",
    "\n",
    "Pentru fișierul `docs/ethics_and_limits.md`, aș folosi asta:\n",
    "```markdown\n",
    "# Ethics and limitations\n",
    "EchoChamber simulates discursive agents based on role prompts, retrieved corpus fragments, and LLM-generated responses. The agents are not real people and do not represent actual individuals, parties, voters, communities, or demographic groups.\n",
    "The system should be used as an educational and research prototype for exploring discourse, framing, and interaction patterns. It should not be used to infer what real social groups believe, predict political behavior, or produce factual claims about public events.\n",
    "## Main limitations\n",
    "| Limitation | Explanation |\n",
    "|---|---|\n",
    "| Simulated agents | Agents reproduce a constructed discursive role, not real human agency. |\n",
    "| Corpus bias | The corpus may contain biased, extreme, repetitive, or unbalanced comments. |\n",
    "| Unsupported claims | The model may generate claims that are not directly supported by the retrieved context. |\n",
    "| Amplification | Multi-agent interaction can intensify conflict, repetition, or conspiratorial framing. |\n",
    "| No factual authority | Generated responses are not verified facts and should not be presented as evidence. |\n",
    "| Interpretive use only | Outputs require human interpretation and methodological caution. |\n",
    "## Required disclaimer\n",
    "This application generates simulated political-discourse responses. The agents are fictional analytical constructs, not real people or representatives of real groups. The generated text may contain bias, exaggeration, unsupported claims, or problematic language inherited from the corpus and the model. Outputs should be used only for education, prototyping, and critical discourse analysis.\n",
    "## Responsible use\n",
    "Use the system to inspect how discourse can be structured, amplified, or contrasted across roles. Do not use it to profile individuals, target groups, generate political persuasion, or present synthetic responses as empirical public opinion.\n",
    "\n",
    "Pentru README.md, pune doar scurt:\n",
    "\n",
    "## Ethics and limitations\n",
    "EchoChamber is a teaching and research prototype. Its agents are simulated discursive roles, not real people or representatives of real social groups. Generated outputs may include bias, unsupported claims, or amplified conflict and must be interpreted critically.\n",
    "See [`docs/ethics_and_limits.md`](docs/ethics_and_limits.md) for the full disclaimer and limitations."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0ea1cfa",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (.venv)",
   "language": "python",
   "name": "echochamber"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
