Ce vei construi
Acest laborator transformă noțiunile teoretice în două programe mici, pe care le poți inspecta integral. Nu presupune experiență anterioară cu programarea: fiecare instrument și comandă sunt explicate înainte de utilizare.
- un spațiu de lucru Linux organizat și reproductibil, numit frecvent workspace în industria software;
- un joc mic în terminal care clasifică sisteme folosind etichetele lecției;
- o componentă de rutare deterministă, numită frecvent router, care trimite fiecare cerere către categoria potrivită;
- șapte teste automate, adică verificări executate de program pentru a confirma că funcțiile se comportă așa cum am stabilit;
- primul profil al agentului și primul Architecture Decision Record — ADR, adică un document scurt care notează o decizie tehnică și motivul ei.
Primul program este un joc de clasificare care prezintă situații software și cere alegerea etichetelor potrivite. Al doilea program este componenta de rutare: el nu rezolvă cererea, ci decide ce tip de tratament trebuie să primească. Regula de siguranță va impune aprobarea unui om pentru acțiuni precum plata sau ștergerea.
Aceste programe nu sunt încă agenți AI. Ele formează însă un punct de referință, numit în industrie baseline: o soluție simplă, măsurabilă și verificabilă cu care vom compara versiunile viitoare. Astfel vom putea demonstra ce adaugă un model lingvistic, un instrument, memoria sau o buclă de agent.
De ce nu instalăm încă un LLM?
Un LLM — large language model, în română model lingvistic de mari dimensiuni — ar introduce comportament probabilistic înainte să înțelegem structura programului. Începem cu cod determinist pentru a putea urmări fiecare decizie și pentru a păstra regulile de siguranță explicite.
1. Cerințe
Înainte de instalare, verificăm mediul în care vom lucra. Laboratorul folosește terminalul din sistemul de operare bazat pe Linux — Ubuntu sau Kubuntu —, adică aplicația în care introduci comenzi text. Comenzile sunt utile deoarece pot fi repetate exact și pot fi incluse ulterior în documentație sau scripturi.
Ce trebuie să știi despre sudo
sudo execută o comandă cu drepturi administrative. Sistemul îți poate cere parola contului; când o tastezi, terminalul nu afișează caractere sau asteriscuri. Folosește sudo doar pentru comenzile explicate și nu copia comenzi administrative din surse necunoscute.
- Ubuntu, Kubuntu sau o distribuție Debian apropiată;
- un cont de utilizator pe calculatorul tău care poate instala programe, adică un cont cu drepturi administrative (folosește
sudo). Este vorba despre contul cu care te autentifici în Ubuntu sau Kubuntu; nu îți trebuie niciun cont creat pe vreun site pentru acest curs; - terminal;
- aproximativ 100 MB liberi;
- niciun GPU necesar.
Comenzile folosesc instrumente Linux și biblioteca standard Python.
git, jq și tree prin Homebrew, nu prin apt. Suportul oficial al laboratorului rămâne Ubuntu/Kubuntu.Laboratoarele sunt scrise pentru Ubuntu sau Kubuntu. Pe Windows, folosește WSL (Ubuntu instalat în Windows); pe macOS, terminalul sistemului — comenzile sunt aceleași, cu mici diferențe la instalarea instrumentelor.
2. Instalează instrumentele de bază
În această etapă instalăm un set mic de instrumente generale. Pentru acest laborator limbajul de programare este Python și pe el îl vom folosi ca să scriem programele; celelalte instrumente și comenzi ne ajută să izolăm proiectul, să urmărim modificările și să inspectăm rezultatele.
Python 3
Python este un limbaj de programare general: cu el se scriu deopotrivă scripturi de câteva rânduri, aplicații web și programe de calcul științific. Se citește aproape ca engleza obișnuită, nu cere declararea tipurilor și nu trebuie compilat înainte de rulare — scrii fișierul și îl execuți imediat, ceea ce scurtează mult drumul de la idee la rezultat.
În AI a devenit limbajul implicit fiindcă aproape toate bibliotecile importante sunt disponibile aici, iar exemplele din documentație, cursuri și lucrări sunt scrise tot în Python. Nu este singurul posibil — agenți de producție se scriu și în TypeScript, Java, Go sau Rust — dar rămâne cea mai practică alegere pentru primele experimente. Versiunea folosită în acest curs este Python 3; exemplele mai vechi de pe internet, scrise pentru Python 2, nu mai rulează.
venv și pip
venv vine de la virtual environment, în română mediu virtual. Comanda creează în proiect un folder (la noi .venv) care conține o copie a interpretorului Python și un loc propriu pentru biblioteci. Cât timp mediul este activat, tot ce instalezi ajunge acolo, nu în Python-ul întregului sistem: proiectul rămâne curat, poate fi refăcut identic pe alt calculator, iar două proiecte pot folosi versiuni diferite ale aceleiași biblioteci fără să se încurce între ele.
pip instalează biblioteci Python. Chiar dacă această lecție folosește doar biblioteca standard, pregătim structura corectă pentru etapele următoare.
Git
Git păstrează istoricul fișierelor local: poți vedea ce s-a schimbat și poți reveni la o versiune anterioară. Folderul pe care Git îl urmărește împreună cu istoricul său este numit în industrie repository sau, prescurtat, repo. Git nu este același lucru cu GitHub sau GitLab; acestea sunt servicii online unde poți găzdui un repository. Un cont online este opțional pentru acest laborator.
jq
JSON este un format text folosit pentru a transmite date structurate între programe. jq citește, formatează și filtrează JSON direct în terminal. Îl vom folosi pentru a extrage o singură valoare din răspunsul componentei de rutare.
tree
tree afișează folderele și fișierele sub forma unui arbore. Este util pentru a vedea structura proiectului fără să deschizi manual fiecare folder.
apt
apt este managerul de pachete folosit de Ubuntu și Kubuntu. El descarcă programele din sursele configurate ale sistemului și urmărește actualizările lor.

Ce fac cele două comenzi
sudo apt updateactualizează catalogul local de pachete. Nu actualizează și programele deja instalate.sudo apt install -y ...instalează pachetele enumerate. Opțiunea-yconfirmă automat întrebarea de instalare.
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git jq treeDupă instalare, cerem fiecărui program să-și afișeze versiunea. Este un mod simplu de a verifica dacă executabilul există și dacă terminalul îl găsește:
python3 --version
git --version
jq --version
tree --versionNumerele exacte pot fi diferite. Important este ca fiecare comandă să afișeze un nume și o versiune.
Dacă apare mesajul „command not found”, instrumentul nu a fost instalat ori terminalul nu îl poate găsi. Revino la instalare, citește mesajele afișate de apt și repetă verificarea.
3. Pregătește folderul proiectului și mediul Python
În continuare vom crea un folder principal al proiectului și subfolderele care separă programele, testele, datele și documentația. Programatorii numesc adesea folderul principal project root, iar întregul spațiu de lucru poate fi numit workspace. Organizarea nu schimbă rezultatul programului, dar face proiectul mai ușor de înțeles, testat și extins.
src/codul programelortests/teste automatedata/date de lucru viitoaredocs/lecții și decizii arhitecturaleconfigs/configurațiiskills/proceduri reutilizabile viitoareevaluations/cazuri și rezultate de evaluarelogs/jurnale produse la rulareCum citești comenzile
mkdir -pcreează foldere. Opțiunea-pcreează și folderele intermediare, iar dacă acestea există deja nu dă eroare.~înseamnă folderul personal al utilizatorului, de exemplu/home/ana.cdschimbă folderul curent al terminalului.- Bara inversă
\continuă aceeași comandă pe linia următoare, pentru lizibilitate. python3 -m venv .venvcreează un mediu Python izolat în folderul ascuns.venv.source .venv/bin/activateactivează acel mediu pentru sesiunea curentă de terminal.python -m pip install --upgrade pipactualizează instalatorul de biblioteci din mediul izolat, nu pe cel al întregului sistem.git inittransformă folderul într-un repository Git local.
mkdir -p ~/ai-agents-zero-to-expert
cd ~/ai-agents-zero-to-expert
mkdir -p \
src \
tests \
data \
docs/lessons \
docs/architecture-decisions \
configs \
skills \
evaluations \
logs
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
git config --global init.defaultBranch main
git initDacă activarea a reușit, promptul terminalului începe cu (.venv). Acest marcaj arată că Python și pip rulează acum în mediul proiectului. Pentru a ieși din el mai târziu, rulezi deactivate.
3.1 Creează .gitignore
.gitignore este un fișier text care îi spune lui Git ce fișiere sau foldere să nu includă în istoricul repository-ului. Programatorii spun că acele fișiere sunt ignorate de Git. Fișierul nu le ascunde pe calculator, nu le protejează cu parolă și nu le șterge; el previne doar adăugarea lor accidentală prin git add.
Ignorăm mediul virtual, fișierele temporare Python, secretele din fișiere .env și jurnalele generate la rulare. Acestea pot fi mari, dependente de calculator sau sensibile și pot fi recreate.
Ce înseamnă sintaxa cat > fișier <<'EOF'
Aceasta este o metodă Linux numită here-document. Terminalul copiază toate liniile dintre prima linie și marcajul final EOF într-un fișier nou. O folosim pentru ca tu să poți crea exact conținutul necesar fără un editor separat.
cat > .gitignore <<'EOF'
.venv/
__pycache__/
*.py[cod]
.env
.env.*
logs/*
!logs/.gitkeep
.DS_Store
EOF
touch logs/.gitkeepRegula logs/* ignoră conținutul folderului, iar !logs/.gitkeep păstrează fișierul gol care marchează existența folderului. Deoarece Git nu urmărește foldere goale, touch logs/.gitkeep creează acel fișier. Jurnalele propriu-zise, inclusiv fișierele .jsonl, nu ajung în istoricul Git.
Inspectează structura:
tree -a -L 24. Mini-joc: „Ce tip de sistem este?”
Vom crea acum primul program al laboratorului: un joc în terminal care selectează situații, primește etichetele alese de cursant, le compară cu o clasificare de referință și calculează un scor. Prin acest program transformăm teoria într-un comportament observabil: poți rula aceleași reguli, poți greși, poți vedea explicația și poți îmbunătăți rezultatul.
Programul este salvat într-un singur fișier Python. Un program mic de acest tip este numit frecvent script. Folosim numai biblioteca standard Python, adică modulele instalate împreună cu Python, fără pachete externe. Un modul este un fișier sau o componentă de cod care poate fi importată și reutilizată; instrucțiunea import aduce în program funcții definite într-un asemenea modul. Textele afișate de program sunt scrise fără diacritice, intenționat: așa se citesc corect în orice terminal, inclusiv în cele configurate greșit.
Structura programului înainte să-l copiezi
- Importuri: aduc funcții standard pentru argumentele din linia de comandă, alegeri aleatoare și structuri de date.
VALID_TAGS: definește singurele etichete acceptate.Scenario: descrie forma fiecărui scenariu: nume, text, etichete și explicație.SCENARIOS: păstrează exemplele jocului.- Funcțiile de parsare și scor: transformă textul introdus de utilizator în etichete și compară mulțimile rezultate.
main(): rulează rundele și afișează rezultatul final.
Comanda următoare creează fișierul src/classification_game.py printr-un here-document. Textul dintre <<'PY' și marcajul final PY devine codul programului.
cat > src/classification_game.py <<'PY'
#!/usr/bin/env python3
"""Jocul de clasificare din Lectia 1.
Jocul arata ca etichetele se suprapun. Un sistem poate fi ML + workflow +
automat fara sa fie agent.
"""
from __future__ import annotations
import argparse
import random
from dataclasses import dataclass
VALID_TAGS = {"ALG", "ML", "GEN", "FLOW", "AGENT", "AUTO"}
@dataclass(frozen=True)
class Scenario:
name: str
description: str
tags: frozenset[str]
explanation: str
SCENARIOS = [
Scenario(
"Calculator",
"Evalueaza 17 x 43.",
frozenset({"ALG"}),
"Un algoritm determinist transforma intrarea intr-o iesire exacta.",
),
Scenario(
"Backup nocturn",
"Un job cron copiaza un folder pe alt disc in fiecare noapte.",
frozenset({"ALG", "FLOW", "AUTO"}),
"Este un workflow automat fix. Actioneaza fara aprobare imediata, dar nu este machine learning.",
),
Scenario(
"Rutarea spamului",
"Un model invatat prezice spamul, iar sistemul de e-mail muta mesajul automat.",
frozenset({"ALG", "ML", "FLOW", "AUTO"}),
"Predictia este invatata, iar mutarea in folder o face o regula determinista.",
),
Scenario(
"Poezie dintr-o singura cerere",
"Un model lingvistic scrie o poezie dintr-un singur prompt.",
frozenset({"ML", "GEN"}),
"Este inferenta generativa, dar nu exista o bucla cu instrumente, orientata spre un obiectiv.",
),
Scenario(
"Arbore de raspunsuri FAQ",
"Un bot de suport urmeaza un arbore de decizie stabilit dinainte.",
frozenset({"ALG", "FLOW"}),
"Este automatizare conversationala, nu neaparat AI si nu un agent.",
),
Scenario(
"Investigator de facturi",
"Sistemul interogheaza inregistrari, alege alta verificare cand lipsesc dovezi, redacteaza un mesaj si asteapta aprobarea.",
frozenset({"ALG", "ML", "GEN", "FLOW", "AGENT"}),
"Alege dinamic actiunile, dar nu trimite singur mesajul in exterior.",
),
Scenario(
"Aspirator robotic",
"Cartografiaza camerele si schimba traseul cand intalneste un obstacol.",
frozenset({"ALG", "ML", "AGENT", "AUTO"}),
"Percepe si actioneaza intr-o bucla cu feedback. Implementarile pot folosi si reguli, si modele invatate.",
),
Scenario(
"Robot de sudura cu traseu fix",
"Repeta acelasi traseu de sudura programat.",
frozenset({"ALG", "FLOW", "AUTO"}),
"Automatizarea fizica nu inseamna automat un agent adaptiv.",
),
Scenario(
"Motor clasic de sah",
"Cauta mutarile posibile cu o logica de evaluare scrisa de oameni.",
frozenset({"ALG", "AGENT"}),
"Alege actiuni catre un obiectiv, fara sa aiba nevoie de machine learning.",
),
Scenario(
"Pipeline RAG de raspuns",
"Recupereaza pasaje din documente si produce un raspuns cu citari, intr-o secventa fixa.",
frozenset({"ALG", "ML", "GEN", "FLOW"}),
"Un RAG obisnuit este de regula un pipeline. Devine agentic daca sistemul planifica si repeta cautarea in timpul rularii.",
),
Scenario(
"Asistent de cod",
"Sugereaza cod, dar nu poate modifica fisiere si nu poate rula comenzi.",
frozenset({"ML", "GEN"}),
"Este un asistent, nu un agent care actioneaza.",
),
Scenario(
"Agent de programare",
"Planifica modificari, editeaza fisiere, ruleaza teste, observa erorile si incearca din nou.",
frozenset({"ALG", "ML", "GEN", "AGENT", "AUTO"}),
"Are o bucla cu instrumente, orientata spre obiectiv, si modifica mediul.",
),
]
def parse_tags(raw: str) -> set[str]:
"""Transforma textul scris de utilizator in etichete valide, normalizate."""
normalized = raw.upper().replace(",", " ").split()
return {tag for tag in normalized if tag in VALID_TAGS}
def score_tags(expected: set[str] | frozenset[str], actual: set[str]) -> float:
"""Intoarce un scor de suprapunere, intre 0 si 1, care iarta greselile partiale."""
union = set(expected) | actual
if not union:
return 1.0
return len(set(expected) & actual) / len(union)
def print_reference() -> None:
print("\nEtichete:")
print(" ALG comportamentul vine din reguli si calcule scrise de oameni")
print(" ML in sistem exista un model invatat din exemple")
print(" GEN produce continut nou")
print(" FLOW urmeaza un flux automat stabilit dinainte")
print(" AGENT isi alege singur actiunile pentru un obiectiv")
print(" AUTO actioneaza fara aprobarea imediata a unui om")
def run_game(rounds: int, seed: int | None) -> None:
rng = random.Random(seed)
selected = SCENARIOS.copy()
rng.shuffle(selected)
selected = selected[: min(rounds, len(selected))]
total = 0.0
print("\nCE FEL DE SISTEM ESTE?")
print("Scrie toate etichetele potrivite, separate prin spatii sau virgule.")
print_reference()
for index, scenario in enumerate(selected, start=1):
print(f"\n[{index}/{len(selected)}] {scenario.name}")
print(scenario.description)
try:
answer = input("Etichetele tale: ")
except (EOFError, KeyboardInterrupt):
print("\nJoc intrerupt. Nu s-a salvat nimic.")
return
actual = parse_tags(answer)
round_score = score_tags(scenario.tags, actual)
total += round_score
expected = ", ".join(sorted(scenario.tags))
print(f"Raspunsul cursului: {expected}")
print(f"Explicatie: {scenario.explanation}")
print(f"Scor runda: {round_score:.0%}")
percentage = 100 * total / len(selected)
print(f"\nScor final: {percentage:.1f}%")
if percentage >= 85:
print("Excelent: separi corect capabilitatile, controlul si autoritatea.")
elif percentage >= 65:
print("Inceput bun: reciteste diferenta dintre workflow, agent si autonomie.")
else:
print("Reia jocul dupa ce revezi harta celor trei axe.")
def list_scenarios() -> None:
print_reference()
for scenario in SCENARIOS:
tags = ", ".join(sorted(scenario.tags))
print(f"\n{scenario.name} [{tags}]\n {scenario.description}\n {scenario.explanation}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--rounds", type=int, default=8)
parser.add_argument("--seed", type=int)
parser.add_argument("--list", action="store_true")
args = parser.parse_args()
if args.rounds < 1:
parser.error("--rounds trebuie sa fie cel putin 1")
if args.list:
list_scenarios()
else:
run_game(args.rounds, args.seed)
if __name__ == "__main__":
main()
PY
chmod +x src/classification_game.pyDeschide explicația principalelor elemente de cod
@dataclass(frozen=True) creează o structură de date compactă și nemodificabilă după inițializare. frozenset păstrează etichetele fără duplicate. Funcția parse_tags acceptă virgule, spații și litere mici sau mari. Funcția score_tags folosește indicele Jaccard: numărul etichetelor comune împărțit la numărul total de etichete distincte.
argparse permite opțiuni precum --rounds, --seed și --list. O valoare fixă de pornire (seed) produce aceeași ordine aleatoare, lucru util pentru teste și demonstrații reproductibile.
Rulează programul cu Python. Varianta implicită alege 8 dintre cele 12 scenarii; apasă Enter după ce introduci etichetele.
python src/classification_game.pyPentru a le vedea pe toate, într-o ordine reproductibilă:
python src/classification_game.py --rounds 12 --seed 42Afișează lista de referință rulând direct scriptul:
./src/classification_game.py --listDacă apeși Enter fără etichete, runda primește scor 0; programul nu se blochează.
Ce învață jocul
- Etichetele se pot suprapune.
- Un sistem poate fi automat fără să fie agent.
- Un sistem poate folosi ML fără să genereze conținut.
- Un agent poate exista fără LLM.
- Clasificarea cere definiții și presupuneri explicite.
5. Testează logica jocului
Un test automat rulează o funcție cu o intrare cunoscută și verifică dacă rezultatul este cel așteptat. Testele nu demonstrează că programul nu are nicio eroare, dar previn reapariția unor erori deja cunoscute și ne permit să modificăm codul cu mai multă încredere.
Vom testa funcțiile mici, nu interacțiunea completă din terminal. Acest tip de test se numește unit test. Modulul unittest este inclus în Python.
Fișierul de mai jos conține patru verificări independente. El importă funcțiile din program, le oferă intrări controlate și compară rezultatele cu valorile așteptate. Creează fișierul de test cu următoarea comandă:
cat > tests/test_classification_game.py <<'PY'
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from classification_game import parse_tags, score_tags
class ClassificationGameTests(unittest.TestCase):
def test_parse_tags_accepts_commas_spaces_and_case(self):
self.assertEqual(parse_tags("ml, gen agent"), {"ML", "GEN", "AGENT"})
def test_parse_tags_ignores_unknown_values(self):
self.assertEqual(parse_tags("ML MAGIC FLOW"), {"ML", "FLOW"})
def test_exact_score(self):
self.assertEqual(score_tags({"ML", "GEN"}, {"ML", "GEN"}), 1.0)
def test_partial_score(self):
self.assertAlmostEqual(score_tags({"ML", "GEN"}, {"ML"}), 0.5)
if __name__ == "__main__":
unittest.main()
PYRulează acum toate testele din folderul tests. Opțiunea -v înseamnă verbose și afișează numele fiecărei verificări, nu doar rezultatul final:
python -m unittest discover -s tests -vRezultatul intermediar trebuie să includă:
Ran 4 tests ... OKTestarea apare încă din Lecția 1 deoarece agenții fiabili vor depinde de componente deterministe care trebuie să rămână corecte chiar când schimbăm modele și prompturi.
6. Proiect scurt: componentă de rutare deterministă
Al doilea program primește o cerere scrisă în limbaj obișnuit și o trimite către o categorie prestabilită. Componenta care face această alegere se numește în industrie router sau componentă de rutare. Nu este routerul de internet sau Wi-Fi din locuință; este o piesă de software care decide către ce funcție, serviciu ori proces trebuie direcționată o cerere.
Într-un agent viitor, componenta de rutare ar putea trimite o cerere către un calculator, un model lingvistic, o bază de date sau o coadă de aprobare. O coadă este o structură în care sarcinile așteaptă să fie prelucrate. În această lecție folosim reguli explicite, astfel încât fiecare decizie să poată fi citită și verificată.
Programul nu este încă un agent AI. El este primul nostru baseline, termen folosit în industrie pentru un punct de referință simplu cu care vom compara soluțiile viitoare. Dacă un model mai complex nu depășește baseline-ul la precizie, siguranță ori cost, complexitatea suplimentară nu este justificată.
6.1 Găsește greșeala: precedența regulilor
Analizează exemplul și deschide explicația
Ordinea regulilor contează atunci când o cerere conține mai multe intenții. Dacă programul verifică mai întâi cuvântul calculate, o cerere precum „calculate and pay” ar putea fi declarată sigură înainte să observe cuvântul pay. De aceea, regulile care blochează sau cer aprobare trebuie evaluate înaintea regulilor obișnuite.
Privește ordinea nesigură:
ROUTES = [
({"calculate"}, "calculation", False),
({"pay"}, "high-impact-action", True),
]Dacă programul returnează prima regulă potrivită, cererea calculate and pay the invoice poate fi tratată drept calcul inofensiv și poate ocoli aprobarea. Aceasta este o eroare de securitate.
Implementarea corectată verifică mai întâi cuvintele cu impact mare: regulile de interdicție ori aprobare trebuie să aibă prioritate față de regulile obișnuite de permitere.
6.2 Creează implementarea sigură
Implementarea sigură separă cuvintele cu impact ridicat într-o mulțime proprie și le verifică înainte de rutele normale. Rezultatul este afișat ca JSON — un format text cu perechi de tip cheie–valoare, folosit frecvent pentru schimbul de date între programe. De exemplu, {"requires_human": true} spune explicit că este necesară aprobarea unui om.
Ce conține routerul
RouteDecisiondescrie ruta, motivul și necesitatea aprobării.HIGH_IMPACT_KEYWORDSpăstrează cuvintele care marchează acțiuni ce nu au voie să fie executate fără aprobare.ROUTESdefinește categoriile obișnuite.route_tasknormalizează cuvintele și aplică precedența de siguranță.json.dumpstransformă rezultatul în text JSON, ușor de procesat.
cat > src/task_router.py <<'PY'
#!/usr/bin/env python3
"""Un baseline determinist de rutare.
Clasifica o sarcina dupa reguli explicite si intoarce pasul urmator stabilit dinainte.
Nu are model invatat, nu are plan adaptiv, nu are instrumente si nu are bucla de actiune.
"""
from __future__ import annotations
import json
import sys
from dataclasses import asdict, dataclass
@dataclass(frozen=True)
class RouteDecision:
route: str
reason: str
requires_human: bool
HIGH_IMPACT_KEYWORDS = {"send", "delete", "publish", "pay"}
ROUTES = [
({"calculate", "sum", "multiply", "total"}, "calculation", "Aritmetica exacta se face in cod determinist.", False),
({"summarize", "rewrite", "draft"}, "language-task", "Transformarea de limbaj poate folosi mai tarziu un model generativ.", True),
({"classify", "detect", "predict"}, "prediction-task", "Predictia poate folosi mai tarziu reguli sau machine learning.", True),
]
def route_task(task: str) -> RouteDecision:
words = {word.strip(".,!?;:").lower() for word in task.split()}
# Precedenta de siguranta: regulile de interdictie sau de aprobare sunt evaluate
# inaintea rutelor obisnuite. Altfel, o cerere precum "calculate and pay the invoice"
# s-ar potrivi intai cu "calculate" si ar ocoli gresit aprobarea umana.
if words & HIGH_IMPACT_KEYWORDS:
return RouteDecision(
"high-impact-action",
"Actiunile externe sau distructive cer aprobare explicita.",
True,
)
for keywords, route, reason, requires_human in ROUTES:
if words & keywords:
return RouteDecision(route, reason, requires_human)
return RouteDecision(
"manual-review",
"Nicio regula determinista nu se potriveste sigur cu aceasta sarcina.",
True,
)
def main() -> None:
task = " ".join(sys.argv[1:]).strip()
if not task:
task = input("Descrie o sarcina: ").strip()
if not task:
raise SystemExit("Este nevoie de o sarcina, nu de un text gol.")
decision = route_task(task)
print(json.dumps(asdict(decision), indent=2))
if __name__ == "__main__":
main()
PY
chmod +x src/task_router.pyÎncearcă mai multe sarcini:
python src/task_router.py "calculate the invoice total"
python src/task_router.py "draft a polite reminder"
python src/task_router.py "delete the original files"
python src/task_router.py "investigate what happened"Folosește jq pentru a citi un singur câmp din JSON:
python src/task_router.py "calculate and pay the invoice" | jq .requires_humanSimbolul | trimite ieșirea primei comenzi ca intrare pentru jq. Expresia .requires_human extrage doar câmpul respectiv din obiectul JSON. Rezultatul trebuie să fie true.
Rezultatul trebuie să fie:
true6.3 Adaugă teste de regresie
Adăugăm acum un test de regresie pentru exact combinația care ar putea ocoli aprobarea. Un test de regresie fixează comportamentul corect după descoperirea unei erori, astfel încât o modificare viitoare să nu readucă problema.
cat > tests/test_task_router.py <<'PY'
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from task_router import route_task
class TaskRouterTests(unittest.TestCase):
def test_calculation_is_low_risk_by_itself(self):
result = route_task("calculate the invoice total")
self.assertEqual(result.route, "calculation")
self.assertFalse(result.requires_human)
def test_high_impact_rule_overrides_calculation(self):
result = route_task("calculate and pay the invoice")
self.assertEqual(result.route, "high-impact-action")
self.assertTrue(result.requires_human)
def test_unknown_task_goes_to_manual_review(self):
result = route_task("investigate what happened")
self.assertEqual(result.route, "manual-review")
self.assertTrue(result.requires_human)
if __name__ == "__main__":
unittest.main()
PY
python -m unittest discover -s tests -vSuita completă trebuie să raporteze 7 teste și să se termine cu OK.
De ce componenta de rutare nu este încă agent
Routerul:
- nu se descurcă bine cu formulări pe care nu le-a mai întâlnit;
- nu are obiectiv pe termen lung;
- nu are acces la instrumente externe;
- nu observă efectul unei acțiuni;
- nu poate revizui un plan;
- face o singură clasificare și se oprește.
Acesta este avantajul baseline-ului: comportamentul este transparent, testabil și comparabil cu versiuni viitoare.
7. Documentează agentul pe care îl vei construi
Ingineria unui agent nu înseamnă doar cod. Înainte să conectezi modele și instrumente, trebuie să definești cine îl folosește, ce rezultat urmărește, ce date poate citi și ce acțiuni necesită aprobare. Aceste decizii vor limita riscul și vor face evaluarea posibilă.
Vom crea două documente Markdown. Markdown este un format text simplu, în care titlurile încep cu #, listele cu -, iar casetele de bifat cu - [ ]. Fișierele pot fi citite deopotrivă de oameni și de programe: Git, editoare precum Obsidian, instrumente de dezvoltare.
7.1 Profilul inițial al agentului
Profilul agentului este o fișă de intenție și de limite. Nu alegem încă modelul sau framework-ul; definim problema, utilizatorul și nivelul inițial de autoritate.
cat > docs/agent-profile.md <<'EOF'
# Profilul agentului meu
## Nume provizoriu
`[Alege un nume temporar]`
## Utilizatorul vizat
`[Cine îl va folosi?]`
## Rezultatul dorit
`[Ce trebuie să devină mai ușor, rapid, sigur sau exact?]`
## Domeniu
`[Documente, cercetare, operațiuni, programare, media, știință etc.]`
## Informații pe care le-ar putea citi
- `[Sursa 1]`
- `[Sursa 2]`
## Acțiuni pe care le-ar putea pregăti
- `[Acțiunea 1]`
- `[Acțiunea 2]`
## Acțiuni executabile automat
- `[Începe doar cu acțiuni reversibile și cu risc redus]`
## Acțiuni care cer aprobare umană
- ștergere;
- plată;
- publicare;
- mesaje trimise extern;
- modificări ale înregistrărilor sensibile;
- `[Adaugă acțiuni specifice domeniului]`.
## Nivel inițial de autonomie
**Nivelul 1 — doar consultativ.**
## Dovezi de succes
`[Cum vei măsura că agentul a ajutat?]`
## Riscuri cunoscute
- răspunsuri nesusținute;
- permisiuni excesive;
- expunerea datelor private;
- selectarea greșită a instrumentului;
- `[Riscuri specifice proiectului]`.
EOF7.2 Architecture Decision Record — ADR-0001
Un Architecture Decision Record — ADR — este un document scurt care notează o decizie importantă, contextul și consecințele sale. Îl folosim pentru a evita situația în care, peste câteva luni, nu mai știm de ce proiectul a fost construit într-un anumit mod.
cat > docs/architecture-decisions/ADR-0001-start-with-a-deterministic-baseline.md <<'EOF'
# ADR-0001: Începem cu un baseline determinist
## Stare
Acceptată
## Context
Proiectul va deveni treptat un agent AI. Dacă am începe direct cu un LLM, ar fi greu să separăm comportamentul modelului de comportamentul orchestrării.
## Decizie
Construim și testăm un router determinist înainte de a conecta un model.
## Consecințe
- Avem un baseline transparent.
- Putem măsura dacă AI aduce îmbunătățire reală.
- Regulile de siguranță rămân deterministe.
- Prima versiune are flexibilitate lingvistică redusă.
EOF8. Alege traseul agentului tău
În această etapă alegi doar domeniul general. Toate traseele vor folosi componente comune — instrumente, stare, evaluări și permisiuni — dar exemplele de date și acțiuni vor diferi.
| Traseu | Exemplu de scop inițial |
|---|---|
| A — Documente și cercetare | Caută în surse, extrage dovezi, pregătește un răspuns citat |
| B — Operațiuni de business | Clasifică cereri, pregătește acțiuni și respectă aprobările |
| C — Programare | Analizează repository-uri, propune modificări și rulează teste în sandbox |
| D — Media | Organizează metadatele, transcrierile și căutarea semantică |
| E — Știință și inginerie | Interoghează date și apelează simulatoare ori calcule validate |
| F — Domeniu propriu | Definește utilizatorul, rezultatul și limitele de autoritate |
Nu trebuie să alegi definitiv stack-ul, modelul sau framework-ul. Alegi doar problema și granițele inițiale.
9. Adaugă fișa lecției
Fișa lecției este un jurnal scurt al progresului. Ea te obligă să notezi ce poți explica, ce caz ți s-a părut dificil și unde proiectul tău ar trebui să folosească reguli, modele sau aprobare umană.
cat > docs/lessons/lesson-01.md <<'EOF'
# Fișa Lecției 1
## Concepte pe care le pot explica
- [ ] AI versus automatizare
- [ ] machine learning versus AI generativă
- [ ] model versus agent
- [ ] workflow versus agent
- [ ] agentic versus autonom
- [ ] clasificarea pe cele trei axe
## Cel mai dificil caz-limită
`[Scrie exemplul și motivul.]`
## Un pas al agentului meu care trebuie să folosească reguli
`[Răspuns]`
## Un pas care ar putea necesita model învățat
`[Răspuns]`
## O acțiune care trebuie aprobată
`[Răspuns]`
EOF10. Salvează munca în Git
Git salvează versiuni ale proiectului sub forma unor commit-uri. Un commit este o înregistrare locală care conține fișierele selectate, autorul, momentul și un mesaj despre schimbare. Commit-urile rămân pe calculator până când alegi să le trimiți către un serviciu online, operație numită de obicei push.
Ai nevoie de cont GitHub?
Nu pentru acest pas. Poți folosi Git complet local. Un cont GitHub, GitLab sau Codeberg devine util dacă vrei backup online, colaborare sau publicarea proiectului. Nu încărca un proiect public înainte să verifici că fișierele secrete și datele personale sunt excluse.
Ce fac comenzile Git
git config --global user.nameșiuser.emailstabilesc autorul commit-urilor pe acest calculator.git add .pregătește fișierele neignorate pentru următorul commit.git statusarată ce va fi inclus.git commit -m "..."creează instantaneul și îi atribuie mesajul indicat.
Configurează identitatea înainte de primul commit. Omite primele două comenzi dacă sunt deja configurate.
git config --global user.name "Numele tău"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git add .
git status
git commit -m "Complete lesson 1 system classification lab"Într-un repository public nu folosi o adresă de e-mail personală, dacă nu vrei să devină publică. GitHub și alte platforme oferă adrese de tip noreply, create tocmai ca să nu-ți expună adresa reală.
11. Exerciții practice
Exercițiile de mai jos consolidează exact pașii executați: clasificare, siguranță, baseline și organizarea proiectului. Fă cel puțin exercițiile de bază și de siguranță înainte de a considera laboratorul terminat.
Exercițiu de bază
Joacă toate cele 12 scenarii și obține peste 80%:
python src/classification_game.py --rounds 12Ce trebuie să obții și de ce
Rezultatul așteptat: 12 runde jucate și scor peste 80% la prima serie completă. Un scenariu este o descriere scurtă a unui sistem real, iar sarcina ta este să-i bifezi etichetele corecte — aceleași șase din secțiunea 11 a lecției. Scorul fiecărei runde este suprapunerea dintre etichetele tale și cele ale cursului (etichete comune împărțite la toate etichetele distincte), iar explicațiile apar după fiecare răspuns.
Capcana: să joci în grabă, fără să citești explicațiile greșelilor — exact acolo spune jocul ce diferență ai confundat (de pildă automat cu agent). Pentru constructorul de agenți, clasificarea corectă este primul filtru de proiectare: din ea decizi dacă unui sistem i se dau instrumente sau doar întrebări.
Exercițiu pentru cazuri-limită
Adaugă un Scenario discutabil și explică presupunerile în câmpul explanation.
Ce trebuie să obții și de ce
Livrabilul: un Scenario nou, adăugat la lista jocului, cu explicația presupunerilor în câmpul explanation. Scenario este clasa din care sunt construite cazurile jocului, iar explanation este textul afișat după răspuns — locul unde declari de ce etichetele alese sunt corecte.
Un exemplu bun de caz discutabil: „aspiratorul robotic care se întoarce singur la stație” — se poate justifica fie doar automat, fie și agent, în funcție de definiția declarată. Capcana: un caz „discutabil” nu înseamnă un caz formulat vag; înseamnă un caz pentru care definiția declarată decide. Pentru constructorul de agenți, exercițiul antrenează exact ce faci la acceptarea unui sistem: spui dinainte sub ce definiție îl testezi.
Exercițiu pentru punctul de referință (baseline)
Adaugă o rută sigură în task_router.py și un test pentru ea.
Ce trebuie să obții și de ce
Livrabilul: o rută nouă în task_router.py pentru o categorie fără impact extern — de exemplu cuvintele „translate”, „explain” → ruta „language-task” sau o rută proprie „lookup” pentru „find”, „search” —, plus un test care verifică ruta și faptul că requires_human are valoarea pe care ai decis-o. O rută (route) este o ramură a routerului: o condiție (cuvintele-cheie) și decizia ei. Ruta este „sigură” fiindcă nu poate declanșa nicio acțiune externă; tot ce face este să claseze cererea.
Capcana: să adaugi cuvinte-cheie care se suprapun cu lista HIGH_IMPACT_KEYWORDS sau cu altă rută („send” într-o rută „sigură”, de pildă) — ordinea regulilor decide atunci rezultatul, iar testul tău trebuie să prindă exact acest caz. Pentru constructorul de agenți, fiecare rută nouă intră cu testul ei: baseline-ul rămâne verificabil doar cât timp fiecare ramură are o dovadă.
Exercițiu de siguranță
Mută temporar verificarea de impact sub rutele obișnuite și rulează calculate and pay the invoice. Notează rezultatul nesigur, restaurează ordinea corectă și explică de ce regulile de aprobare au prioritate. Nu păstra versiunea nesigură.
Ce trebuie să obții și de ce
Rezultatul așteptat: cererea „calculate and pay the invoice” este rutată la „calculation” cu requires_human: false — o plată care ar trece fără aprobare, adică o eroare de siguranță, produsă intenționat și înțeleasă. Verificarea de impact este regula care cere aprobare înainte de acțiunile cu efect extern; mutând-o sub rutele obișnuite, cererea ajunge la execuție înainte de control.
Capcana: să tratezi experimentul drept „o simplă mutare de linii”. El este o demonstrație că ordinea verificărilor este parte din semantica sistemului: aceleași reguli, în altă ordine, produc o plată neaprobată. De aceea regulile de aprobare se pun înaintea tuturor rutelor care pot acționa, și de aceea restaurezi imediat ordinea corectă. Pentru constructorul de agenți, exercițiul arată forma cea mai ieftină a unui incident de autoritate — una pe care o poți provoca și vedea pe cont propriu, înainte să o provoace un agent real.
Extensie avansată
Modifică jocul pentru a salva rezultate JSON Lines în logs/lesson01-results.jsonl. Confirmă cu:
git status --ignoredFișierul trebuie să apară ca ignorat. Nu salva date personale sau sensibile.
Ce trebuie să obții și de ce
Rezultatul așteptat: rezultatele jocului ajung în logs/lesson01-results.jsonl, iar git status --ignored arată fișierul ca ignorat. JSON Lines înseamnă un fișier text cu câte un obiect JSON pe fiecare linie — formatul standard pentru jurnalele de rulări, fiindcă se adaugă rând cu rând și se citește pe bucăți. „Ignorat” înseamnă că regula din .gitignore îl scoate din istoricul repository-ului.
Capcana: dacă fișierul apare ca untracked, nu ca ignorat, regula din .gitignore nu corespunde căii; verifică numele folderului și calea din regulă. Pentru constructorul de agenți, exercițiul fixează un obicei din producție: rezultatele personale și datele de test rămân pe discul local, nu intră în istoricul partajat.
12. Depanare
Depanarea începe prin citirea mesajului complet de eroare și verificarea folderului curent. Nu repeta la întâmplare comenzile cu sudo; folosește simptomele de mai jos pentru a identifica etapa care a eșuat.
python: command not found
În afara mediului virtual folosește python3; în interiorul lui, python există. Comanda python nu a dispărut din sistem: mediul virtual (venv) își pune propriul executabil python în PATH — lista de foldere în care shell-ul caută comenzile — și îl retrage când mediul nu mai este activ. Reactivează mediul:
Capcana: să instalezi Python global cu sudo ca să „repari” eroarea; asta maschează simptomul și încurcă proiectul. Pentru laboratoarele următoare contează ca obicei: agenții rulează din mediul proiectului, iar prima întrebare de depanare este întotdeauna „ce mediu este activ acum?”.
source ~/ai-agents-zero-to-expert/.venv/bin/activateCrearea mediului virtual eșuează
Reinstalează pachetul care conține modulul venv, apoi reia crearea mediului. Pe Ubuntu, modulul de medii virtuale vine într-un pachet separat (python3-venv); fără el, comanda python3 -m venv eșuează de fiecare dată cu același mesaj, oricât ai repeta-o — de aceea pachetul se instalează o singură dată, la nivelul sistemului.
Capcana: să creezi mediul cu sudo sau manual, prin copiere; ambele produc un mediu greu de reprodus. Pentru constructorul de agenți, mediul virtual este prima izolare pe care o folosești în curs: același principiu ca sandbox-ul din lecție — proiectul primește doar ce i se dă explicit.
sudo apt install --reinstall python3-venvGit refuză commit-ul
Setează identitatea cerută de fiecare commit — numele și adresa din Secțiunea 10 — apoi reia comanda. Git semnează fiecare commit cu un autor, iar fără user.name și user.email configurate refuză să înregistreze nimic; eroarea numește exact ce lipsește.
Capcana: să scrii altă adresă decât cea pe care vrei să o vezi în istoric — identitatea intră în fiecare commit și rămâne acolo. Setarea cu --global este valabilă pentru toate proiectele de pe calculator; fără --global, doar pentru repository-ul curent. Pentru constructorul de agenți, istoricul commit-urilor este primul tău jurnal de execuție: cine a schimbat ce și când — exact ce va cere orice audit.
Scriptul nu este executabil
Dă scripturilor permisiunea de executare, apoi rulează-le așa cum a fost gândit laboratorul. Eroarea vine de la bitul de executare: un fișier nou creat nu are voie să pornească direct din shell până când chmod +x nu îl marchează explicit. Repară permisiunile:
Capcana: chmod 777 „ca să funcționeze oricum” — dă tuturor totul, exact inversul practicii corecte. Pentru constructorul de agenți, exercițiul arată regula permisiunilor explicite și minime: unelte pe care un agent le apelează primesc exact drepturile de care au nevoie, niciunul în plus.
chmod +x src/classification_game.py src/task_router.pyPoți rula întotdeauna prin Python:
python src/classification_game.pyTestele nu pot importa modulul
Rulează testele din rădăcina proiectului, cu python -m unittest discover. Comanda discover -s tests caută folderul tests pornind din directorul curent; rulată din alt folder, nu îl găsește și raportează „Ran 0 tests” sau o eroare despre folder. Dacă vezi ModuleNotFoundError pentru task_router, cauza este alta: fișierul lipsește din src/ sau are alt nume.
Capcana: să „repari” mutând fișiere prin foldere; aproape întotdeauna problema este doar directorul din care ai lansat comanda. Pentru constructorul de agenți, testele acestea verifică rutele sigure ale routerului — prima capabilitate care trebuie să treacă testele înainte de orice altceva.
cd ~/ai-agents-zero-to-expert
python -m unittest discover -s tests -v13. Checklist de finalizare
Bifează fiecare punct numai după ce ai rulat comanda și ai verificat rezultatul. Checklist-ul confirmă că proiectul poate fi reluat în etapa următoare fără să lipsească vreun pas.