import json
import os
import subprocess
import time
import httpx
from openai import OpenAI
# ===========================================================================
# 0. CONFIGURARE CLIENT LOCAL (RUTARE CĂTRE PROXMOX / LLAMA.CPP)
# ===========================================================================
client = OpenAI(
base_url=f"http://localhost:11434/v1",
api_key="nu-conteaza-pentru-local", # Serverul local nu o verifică, dar librăria o cere
)
# ===========================================================================
# 1. DEFINIREA UNELTELOR (FUNCTIILOR PYTHON PE CARE AI-UL LE POATE APELA)
# ===========================================================================
def scrie_cod_in_fisier(nume_fisier: str, cod: str) -> str:
"""Scrie sau suprascrie un fișier local cu codul primit."""
try:
with open(nume_fisier, "w", encoding="utf-8") as f:
f.write(cod)
return f"[Unealtă] Codul a fost salvat cu succes în '{nume_fisier}'."
except Exception as e:
return f"[Unealtă] Eroare la scrierea fișierului '{nume_fisier}': {str(e)}"
def ruleaza_script_python(nume_fisier: str) -> str:
"""Rulează un script Python local și returnează ce afișează în terminal."""
if not os.path.exists(nume_fisier):
return (
f"[Unealtă Rulare] Eroare: Fișierul '{nume_fisier}' nu există pe"
" disc."
)
try:
# Rulăm scriptul folosind executabilul curent 'python' sau 'py'
# Capturăm output-ul text (stdout) și erorile (stderr)
rezultat = subprocess.run(
[
"py",
nume_fisier,
], # Schimbă în ["python", nume_fisier] dacă ești pe Linux/Mac
capture_output=True,
text=True,
timeout=15,
)
if rezultat.returncode == 0:
return f"[Unealtă Rulare] Succes!\nIeșire:\n{rezultat.stdout}"
else:
return f"[Unealtă Rulare] Eroare la rulare (Cod {rezultat.returncode}):\n{rezultat.stderr}"
except subprocess.TimeoutExpired:
return (
"[Unealtă Rulare] Eroare: Scriptul a durat prea mult și a primit"
" Timeout."
)
except Exception as e:
return f"[Unealtă Rulare] Eroare neașteptată de sistem: {str(e)}"
# ===========================================================================
# 2. DEFINIREA SCHEMEI JSON PENTRU OPENAI (HARTA UNELTELOR)
# ===========================================================================
unelte_openai = [
{
"type": "function",
"function": {
"name": "scrie_cod_in_fisier",
"description": (
"Scrie sau creează un fișier local cu cod Python. Folosește-o"
" ori de câte ori trebuie să generezi cod sau să salvezi un"
" script."
),
"parameters": {
"type": "object",
"properties": {
"nume_fisier": {
"type": "string",
"description": (
"Numele fișierului în care se salvează codul (ex:"
" 'calcul.py')."
),
},
"cod": {
"type": "string",
"description": (
"Codul sursă complet în Python care va fi scris în"
" acel fișier."
),
},
},
"required": ["nume_fisier", "cod"],
},
},
},
{
"type": "function",
"function": {
"name": "ruleaza_script_python",
"description": (
"Rulează un fișier Python local existent pe disc și colectează"
" rezultatul printat în terminal sau erorile aruncate."
),
"parameters": {
"type": "object",
"properties": {
"nume_fisier": {
"type": "string",
"description": (
"Numele exact al fișierului de pe disc care"
" trebuie executat (ex: 'calcul.py')."
),
}
},
"required": ["nume_fisier"],
},
},
},
]
# ===========================================================================
# 3. BUCLA PRINCIPALĂ A AGENTULUI (ORCHESTRAREA ȘI PARTEA DIDACTICĂ)
# ===========================================================================
def ruleaza_agent_coder(cerinta_utilizator: str, max_iteratii: int = 5):
# Promptul de sistem setează personalitatea și regulile de comportament ale agentului
prompt_sistem = (
"Ești un agent AI autonom specializat în programare. Sarcina ta este"
" să scrii cod, să îl salvezi în fișiere locale și SĂ ÎL RULEZI"
" obligatoriu folosind uneltele puse la dispoziție pentru a te asigura"
" că funcționează corect și nu conține bug-uri. Dacă terminalul îți"
" returnează o eroare la rulare, analizeaz-o, corectează codul și"
" rulează-l din nou. Nu te opri până când codul nu rulează cu succes și"
" îndeplinește cerința utilizatorului. Răspunde în limba română la"
" final."
)
# Inițializăm memoria de lucru (istoria conversației)
istoric_conversatie = [
{"role": "system", "content": prompt_sistem},
{"role": "user", "content": cerinta_utilizator},
]
print(f"🚀 Pornire agent pentru sarcina: '{cerinta_utilizator}'\n")
iteratie = 1
while iteratie <= max_iteratii:
print("\n" + "=" * 60)
print(f"📊 [Iterația {iteratie}/{max_iteratii}]")
print(
"📋 Dimensiune Istoric/Context actual:"
f" {len(str(istoric_conversatie))} caractere"
)
print("=" * 60)
print("\n🤖 Agentul trimite contextul și gândește...")
# Măsurăm timpul în care procesorul din Proxmox calculează răspunsul
timp_start_api = time.time()
try:
# Solicitare către serverul local cu un timeout masiv de 10 minute
raspuns_llm = client.chat.completions.create(
model="qwen3:8b", # llama.cpp folosește implicit modelul deja încărcat
messages=istoric_conversatie,
tools=unelte_openai,
tool_choice="auto",
timeout=httpx.Timeout(600.0, connect=10.0),
temperature=0.0,
)
except Exception as e:
print(
"\n❌ Eroare de comunicare sau Timeout după"
f" {time.time() - timp_start_api:.2f} secunde!"
)
print(f"Detalii eroare: {e}")
break
timp_total_api = time.time() - timp_start_api
print(
f"⏱️ Răspuns primit de la server în {timp_total_api:.2f} secunde."
)
mesaj_agent = raspuns_llm.choices[0].message
# --- SECȚIUNEA DIDACTIC REVEAL: Ce se ascunde în mintea LLM-ului ---
print(
"\n--- 🧠 [LOG INTRINSIC] Ce a generat „creierul” LLM în această"
" fază: ---"
)
if mesaj_agent.content:
print(f"📝 Text/Gândire: {mesaj_agent.content}")
if mesaj_agent.tool_calls:
print("🛠️ Intenție detectată! Modelul cere rularea unei unelte:")
for tool_call in mesaj_agent.tool_calls:
print(f" -> Numele uneltei: {tool_call.function.name}")
print(
" -> Argumente brute primite:"
f" {tool_call.function.arguments}"
)
print("-" * 60)
# ------------------------------------------------------------------
# Salvăm răspunsul modelului în istoricul conversației
istoric_conversatie.append(mesaj_agent)
# Verificăm dacă modelul a decis să ruleze unelte
if mesaj_agent.tool_calls:
for tool_call in mesaj_agent.tool_calls:
nume_functie = tool_call.function.name
# Încercăm să parsăm argumentele venite ca text de la LLM în obiect JSON
try:
argumente = json.loads(tool_call.function.arguments)
except Exception as e:
print(
"❌ Modelul a generat un JSON invalid pentru"
f" argumente: {e}"
)
argumente = {}
print(
"\n⚙️ Execut unealta locală în Python:"
f" '{nume_functie}'..."
)
timp_start_unealta = time.time()
# Ruterul local de unelte
if nume_functie == "scrie_cod_in_fisier":
rezultat_actiune = scrie_cod_in_fisier(
nume_fisier=argumente.get(
"nume_fisier", "script_eroare.py"
),
cod=argumente.get("cod", ""),
)
elif nume_functie == "ruleaza_script_python":
rezultat_actiune = ruleaza_script_python(
nume_fisier=argumente.get("nume_fisier", "")
)
else:
rezultat_actiune = (
f"Eroare: Unealta '{nume_functie}' nu este definită în"
" sistem."
)
timp_total_unealta = time.time() - timp_start_unealta
print(rezultat_actiune)
print(
"⏱️ Execuția uneltei în sistemul local a durat"
f" {timp_total_unealta:.4f} secunde."
)
# IMPORTANT: Trimitem feedback-ul înapoi în memorie cu rolul "tool"
istoric_conversatie.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": nume_functie,
"content": rezultat_actiune,
})
else:
# Dacă nu a cerut nicio unealtă, înseamnă că modelul consideră că a terminat treaba
print(f"\n🤖 Răspunsul Final al Agentului:\n{mesaj_agent.content}")
print("\n✅ Agentul a considerat sarcina finalizată cu succes!")
break
iteratie += 1
# ===========================================================================
# 4. LANSAREA SARCINII (CITIRE DE LA TASTATURĂ)
# ===========================================================================
if __name__ == "__main__":
# Citire cerință
cerinta = input("✍️ Introdu cerința pentru agent: ").strip()
# Citire număr de iterații cu tratare de eroare (pentru siguranță în caz că introduci text)
iteratii_input = input(
"🔢 Introdu numărul maxim de iterații (implicit 5): "
).strip()
if iteratii_input.isdigit():
max_iter = int(iteratii_input)
else:
print("⚠️ Intrare nevalidă pentru iterații. Se folosește valoarea implicită (5).")
max_iter = 5
# Apelare agent
if cerinta:
ruleaza_agent_coder(cerinta, max_iter)
else:
print("❌ Nu ai introdus nicio cerință! Scriptul s-a oprit.")