Završni Workshop Dana 1
Integrišemo cjelodnevno znanje (Arhitektura, Tokeni, API Parametri) u višemodalni Python program. Koristit ćemo zvanične SDK biblioteke (OpenAI i Anthropic Python paketi) za A/B testiranje modela — arhitektonska praksa koju koriste svaki ozbiljan AI tim pri selekciji modela za produkciju.
💡 Zašto API SDK vs. raw requests?
U Lekciji 1.3 smo koristili requests biblioteku direktno da vidimo sam format HTTP
zahtjeva.
U praksi, svaki provider nudi vlastiti SDK (Software Development Kit) —
Python paket koji enkapsulira HTTP pozive, automatski rukovodi retry-em, rate limitingom,
tipovima podataka i novijim featurima. Uvijek koristite SDK u produkcijskom kodu.
Priprema okruženja i instalacija SDK paketa
Otvorite VS Code u AI_Kurs folderu. Aktivirajte venv
okruženje u integriranom terminalu (Terminal → New Terminal):
# Aktivacija venv
.\venv\Scripts\Activate.ps1
# Provjera (trebate vidjeti (venv) u promptu)
# Instalacija zvaničnih SDK biblioteka
pip install openai anthropic
# Provjera verzija
python -c "import openai; print('OpenAI SDK:', openai.__version__)"
python -c "import anthropic; print('Anthropic SDK:', anthropic.__version__)"
# Dodajemo i colorama za ljepši terminal output
pip install colorama
Šta smo instalirali:
-
openai— Zvanični Python paket za GPT-4, GPT-4o, o1, o3 modele. Automatski čitaOPENAI_API_KEYiz environment varijabli. -
anthropic— Zvanični Python paket za Claude 3 i Claude 3.5 modele. Automatski čitaANTHROPIC_API_KEY. -
colorama— Biblioteka za obojeni terminal output (ANSI escape kodovi). Koristi se za vizualno razlikovanje GPT vs. Claude odgovora u terminalu.
Sigurno Upravljanje API Ključevima (.env datoteka)
Proširite .env fajl koji ste kreirali u Lekciji 1.3 sa Anthropic ključem:
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Ključeve dobijate na:
# OpenAI: platform.openai.com → API Keys
# Anthropic: console.anthropic.com → API Keys
⚠️ Pravila za API Ključeve — Ponavljanje!
.env fajl mora biti u .gitignore fajlu. Nikad ga ne pušite
na GitHub ili bilo koji cloud storage. OpenAI i mnogi provideri imaju automatske
skene javnih GitHub repozitorija — key-ovi koji curcu bivaju odmah
revocirani
i račun blokiran dok ne izdate novi ključ.
Pisanje Multi-Model A/B Skripte (app.py)
Napravite fajl app.py i unesite sljedeći kod liniju po liniju.
Svaki segment je detaljno komentarisan:
"""
app.py - Multi-Model AI Sandbox Aplikacija
Kurs: AI i LLM za IT Inženjere, Dan 1 - Završni Workshop
Svrha: A/B testiranje odgovora GPT-4o i Claude 3.5 za isti zadatak
"""
import os
import time
from openai import OpenAI
import anthropic
from dotenv import load_dotenv
from colorama import init, Fore, Style, Back
# ==============================================================
# INICIJALIZACIJA
# ==============================================================
# Inicijalizacija colorama (Windows kompatibilnost za ANSI boje)
init(autoreset=True)
# Učitavanje varijabli iz .env fajla u os.environ rječnik
load_dotenv()
# Provjera i instanciranje OpenAI klijenta
# OpenAI() automatski traži OPENAI_API_KEY u environment varijablama
openai_key = os.environ.get("OPENAI_API_KEY")
anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
if not openai_key:
print(Fore.RED + "❌ OPENAI_API_KEY nije pronađen!" + Style.RESET_ALL)
print(" Kreirajte .env fajl sa OPENAI_API_KEY=sk-...")
if not anthropic_key:
print(Fore.YELLOW + "⚠️ ANTHROPIC_API_KEY nije pronađen — Claude test će biti preskočen." + Style.RESET_ALL)
# Instanciranje API klijenata
# Klijenti drže konekciju i konfiguraciju (endpoint URL, auth, retry)
openai_client = OpenAI() if openai_key else None
claude_client = anthropic.Anthropic() if anthropic_key else None
# ==============================================================
# FUNKCIJA: OpenAI GPT-4o poziv
# ==============================================================
def call_openai_gpt4o(system_prompt: str, user_prompt: str, temperature: float = 0.2):
"""
Šalje zahtjev OpenAI GPT-4o modelu.
Parametri:
system_prompt: Instrukcije za ponašanje modela (string)
user_prompt: Korisničko pitanje ili zadatak (string)
temperature: Kontrola kreativnosti (float, 0.0-1.0)
Vraća:
tuple: (tekst_odgovora, input_tokeni, output_tokeni, trajanje_sekundi)
"""
print(Fore.CYAN + " → Šalje zahtjev GPT-4o..." + Style.RESET_ALL)
start_time = time.time() # Mjerimo latenciju
response = openai_client.chat.completions.create(
model="gpt-4o", # Specifikujemo model (ne koristimo "latest")
temperature=temperature, # Eksplicitno postavljamo temperature
max_tokens=800, # Output budget limit
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
elapsed = time.time() - start_time # Trajanje poziva
# Ekstrakcija podataka iz response objekta
text = response.choices[0].message.content
input_tokens = response.usage.prompt_tokens
output_tokens = response.usage.completion_tokens
return text, input_tokens, output_tokens, elapsed
# ==============================================================
# FUNKCIJA: Anthropic Claude 3.5 Sonnet poziv
# ==============================================================
def call_claude_35_sonnet(system_prompt: str, user_prompt: str, temperature: float = 0.2):
"""
Šalje zahtjev Anthropic Claude 3.5 Sonnet modelu.
Napomena: Anthropic API format je malo drugačiji od OpenAI!
- System prompt je zasebni parametar (ne u messages listi)
- Mora se zadati max_tokens (obavezno, nema default-a)
"""
print(Fore.YELLOW + " → Šalje zahtjev Claude 3.5 Sonnet..." + Style.RESET_ALL)
start_time = time.time()
response = claude_client.messages.create(
model="claude-3-5-sonnet-20241022", # Puna verzija modela (datumski tag)
max_tokens=800, # OBAVEZNO kod Anthropic API-ja
temperature=temperature,
system=system_prompt, # Anthropic: system prompt je poseban parametar!
messages=[
{"role": "user", "content": user_prompt} # Samo user poruka (bez system)
]
)
elapsed = time.time() - start_time
# Anthropic response struktura je malo drugačija od OpenAI
text = response.content[0].text # Lista content blokova
input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens
return text, input_tokens, output_tokens, elapsed
# ==============================================================
# FUNKCIJA: Ispis rezultata u terminalu
# ==============================================================
def display_result(provider_name: str, text: str, in_tok: int, out_tok: int,
elapsed: float, color, input_price: float, output_price: float):
"""
Prikazuje rezultat jednog modela sa token statistikama i troškovima.
"""
separator = "─" * 65
print(f"\n{color}┌{separator}┐{Style.RESET_ALL}")
print(f"{color}│ 🤖 {provider_name:<60}│{Style.RESET_ALL}")
print(f"{color}└{separator}┘{Style.RESET_ALL}")
print(text)
# Token statistike
total_tokens = in_tok + out_tok
cost = (in_tok * input_price / 1_000_000) + (out_tok * output_price / 1_000_000)
print(f"\n{color}📊 Statistike:{Style.RESET_ALL}")
print(f" Input tokeni: {in_tok:,}")
print(f" Output tokeni: {out_tok:,}")
print(f" Ukupno: {total_tokens:,}")
print(f" Latencija: {elapsed:.1f}s")
print(f" 💰 Trošak: ${cost:.6f} ({cost*100:.4f}¢)")
# ==============================================================
# GLAVNI PROGRAM
# ==============================================================
if __name__ == "__main__":
print(Fore.MAGENTA + Style.BRIGHT + "\n" + "="*65)
print(" 🧪 AI SANDBOX — Multi-Model A/B Test")
print(" Dan 1 Workshop: AI i LLM Kurs")
print("="*65 + Style.RESET_ALL)
# -----------------------------------------------------------
# Definiše zadatak/pitanje
# -----------------------------------------------------------
SYSTEM_PROMPT = (
"You are a senior DevOps and cloud infrastructure expert. "
"Provide concise, actionable technical advice. "
"Include relevant commands and examples where appropriate. "
"Respond in Bosnian language."
)
TASK = (
"Kreirati Dockerfile za Python Flask aplikaciju koja:"
"\n1. Koristi Python 3.11 slim base image"
"\n2. Instalira requirements.txt"
"\n3. Izlaže port 5000"
"\n4. Pokreće aplikaciju koristeći gunicorn"
"\n5. Implementira health check"
"\nObjasni svaki korak."
)
print(f"\n{Fore.WHITE}📋 Zadatak:{Style.RESET_ALL}")
print(f" {TASK[:120]}...")
print(f"\n{Fore.WHITE}⚙️ Parametri:{Style.RESET_ALL}")
print(f" Temperature: 0.2 | Max tokens: 800")
# -----------------------------------------------------------
# Test 1: OpenAI GPT-4o
# -----------------------------------------------------------
if openai_client:
print(f"\n{Fore.CYAN}{'='*65}")
print(" TEST 1: OpenAI GPT-4o")
print(f"{'='*65}{Style.RESET_ALL}")
try:
gpt_text, gpt_in, gpt_out, gpt_time = call_openai_gpt4o(
SYSTEM_PROMPT, TASK, temperature=0.2
)
display_result(
"GPT-4o (OpenAI)",
gpt_text, gpt_in, gpt_out, gpt_time,
Fore.CYAN,
input_price=5.00, # $5.00 / 1M input tokena
output_price=15.00 # $15.00 / 1M output tokena
)
except Exception as e:
print(Fore.RED + f"❌ OpenAI greška: {e}" + Style.RESET_ALL)
# -----------------------------------------------------------
# Test 2: Anthropic Claude 3.5 Sonnet
# -----------------------------------------------------------
if claude_client:
print(f"\n{Fore.YELLOW}{'='*65}")
print(" TEST 2: Anthropic Claude 3.5 Sonnet")
print(f"{'='*65}{Style.RESET_ALL}")
try:
claude_text, cl_in, cl_out, cl_time = call_claude_35_sonnet(
SYSTEM_PROMPT, TASK, temperature=0.2
)
display_result(
"Claude 3.5 Sonnet (Anthropic)",
claude_text, cl_in, cl_out, cl_time,
Fore.YELLOW,
input_price=3.00, # $3.00 / 1M input tokena
output_price=15.00 # $15.00 / 1M output tokena
)
except Exception as e:
print(Fore.RED + f"❌ Anthropic greška: {e}" + Style.RESET_ALL)
# -----------------------------------------------------------
# Finalna Usporedba
# -----------------------------------------------------------
print(f"\n{Fore.GREEN + Style.BRIGHT}{'='*65}")
print(" 📊 A/B USPOREDBA — Zaključak")
print(f"{'='*65}{Style.RESET_ALL}")
print("""
Kriteriji za Arhitektonsku Odluku:
╔════════════════════════════════════════════════════════════╗
║ Kriterij │ GPT-4o │ Claude 3.5 Sonnet ║
╠════════════════════════════════════════════════════════════╣
║ Coding │ ⭐⭐⭐⭐½ │ ⭐⭐⭐⭐⭐ ║
║ Analitika │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐½ ║
║ Input cijena │ $5.00/1M │ $3.00/1M ║
║ Context window │ 128k tokena │ 200k tokena ║
║ Latencija │ Brži odgovor │ Sporiji, detaljniji ║
║ Sigurnost/GDPR │ USA Cloud │ USA Cloud ║
╚════════════════════════════════════════════════════════════╝
Preporuka: Claude 3.5 Sonnet za coding zadatke i analitiku dugih
dokumenata. GPT-4o za multi-modal (tekst+slike) i širi ekosistem.
""")
print(Fore.GREEN + "✅ Workshop završen!" + Style.RESET_ALL)
Pokretanje i Analiza Rezultata
Sačuvajte app.py i pokrenite u terminalu:
# Uvjerite se da ste u AI_Kurs folderu sa aktivnim venv-om
python app.py
Trebate vidjeti obojeni output sličan ovome (simplificirano):
═══════════════════════════════════════════════════════════════
🧪 AI SANDBOX — Multi-Model A/B Test
Dan 1 Workshop: AI i LLM Kurs
═══════════════════════════════════════════════════════════════
📋 Zadatak: Kreirati Dockerfile za Python Flask aplikaciju...
⚙️ Parametri: Temperature: 0.2 | Max tokens: 800
════════════════════════════════════════════════════════
TEST 1: OpenAI GPT-4o
════════════════════════════════════════════════════════
→ Šalje zahtjev GPT-4o...
┌─────────────────────────────────────────────────────────────┐
│ 🤖 GPT-4o (OpenAI) │
└─────────────────────────────────────────────────────────────┘
# Dockerfile za Python Flask + Gunicorn
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt gunicorn
COPY . .
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=10s \
CMD wget -qO- http://localhost:5000/health || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
📊 Statistike:
Input tokeni: 150
Output tokeni: 287
Ukupno: 437
Latencija: 2.3s
💰 Trošak: $0.005055 (0.5055¢)
Razumijevanje Razlika u API Format-u (OpenAI vs. Anthropic)
Jedan od najčešćih početničkih problema je miješanje API formata dva providera. Evo ključnih razlika koje morate pamtiti:
┌─────────────────────────────────────────────────────────────┐
│ OPENAI │ ANTHROPIC (CLAUDE) │
├───────────────────────────┼─────────────────────────────────┤
│ client.chat.completions │ client.messages.create │
│ .create(...) │ │
│ │ │
│ messages=[ │ system="..." ← zasebni param! │
│ {"role": "system", ...} │ messages=[ │
│ {"role": "user", ...} │ {"role": "user", ...} │
│ ] │ ] │
│ │ │
│ max_tokens: opcijono │ max_tokens: OBAVEZNO! │
│ │ │
│ response.choices[0] │ response.content[0].text │
│ .message.content │ │
│ │ │
│ response.usage │ response.usage │
│ .prompt_tokens │ .input_tokens │
│ .completion_tokens │ .output_tokens │
└───────────────────────────┴─────────────────────────────────┘
Dobra vijest: Ollama i mnogi lokalni modeli podržavaju OpenAI-kompatibilni format koji je zapravo de-facto standard. Možete koristiti OpenAI SDK i sa lokalnim Ollama modelima mijenjajući samo base URL!
# OpenAI SDK sa lokalnim Ollama serverom
from openai import OpenAI
# Kreiranje klijenta koji pokazuje na lokalni Ollama umjesto cloud-a
local_client = OpenAI(
base_url="http://localhost:11434/v1", # Ollama lokalni endpoint
api_key="ollama" # Placeholder (Ollama ne treba pravi ključ)
)
# Sve ostalo je identično OpenAI pozivu!
response = local_client.chat.completions.create(
model="llama3.1", # Lokalni model
temperature=0.2,
messages=[
{"role": "system", "content": "Ti si DevOps ekspert."},
{"role": "user", "content": "Kreiraj Dockerfile za Flask."}
]
)
print(response.choices[0].message.content)
🎯 Vaši Zadaci za Kraj Dana 1
Zadatak 1 (Osnovan):
-
Promijenite
TASKvarijablu uapp.pyi tražite od modela da kreira dokumentacioni tekst za vaš fiktivni REST API na bosanskom. Usporedite broj output tokena (bosanski će biti veći — sjećamo se Lekcije 1.2!). - Utvrdite: koji provider (GPT-4o ili Claude) vraća manji Dockerfile za isti prompt? Koji košta manje za taj prompt?
Zadatak 2 (Napredniji — Bonus):
-
Dodajte u skriptu treći poziv koji koristi lokalini Ollama llama3.1
(bez API ključa, besplatno!). Usporedite kvalitet odgovora sa cloud modelima.
(Hint: koristite OpenAI SDK sa
base_url="http://localhost:11434/v1") - Izvedite ukupan financijski zaključak: za 10,000 ovakvih poziva dnevno, koji provider je najjeftiniji? Kada bi lokalni server bio isplativiji? (Hint: pogledajte kalkulacije iz Modula 2.1 koji nas čeka sutra)
📊 Šta smo naučili kroz Dan 1
Prošli smo dugačak put od matematičkih osnova do produkcijskog koda:
- Modul 1.1: Transformer arhitektura — tenzori, vektori, Self-Attention (Q, K, V), Multi-Head Attention. Praktično: BertViz vizualizacija attention matrica.
- Modul 1.2: Tokenizacija (BPE), razlike između engleskog i bosanskog, Context Window, Sliding Window strategija. Praktično: tiktoken analizator s kalkulacijom troška.
- Modul 1.3: API hiperparametri (temperature, top-p, max_tokens, penalties), sigurnost ključeva, streaming SSE. Praktično: direktni HTTP poziv sa temperature eksperimentom.
- Modul 1.4: OpenAI i Anthropic SDK integracija, A/B testiranje modela, usporedba API formata i troškova. Praktično: višemodalna sandbox skripta.
✅ Generalni Checkpoint za Dan 1
- Transformer = paralelna arhitektura + Self-Attention mehanizam (ne sekvencijalna RNN).
- Tenzor = višedimenzionalni niz podataka (sve u deep learning-u su tenzori).
- BPE tokenizacija → bosanski tekst troši 2-4× više tokena nego engleski ekvivalent.
- Context Window = radna memorija modela (mjeri se u tokenima, uključuje prompt + historiju + output).
- Temperature: 0.0 = deterministički, 1.5+ = halucinacije. Max_tokens = output budget.
- API ključevi idu ISKLJUČIVO u .env datoteke, nikada u izvorni kod!
- OpenAI SDK i Anthropic SDK imaju sličan ali ne identičan API format — pažnja na razlike!
🏁 Kraj Dana 1 — Odličan Posao!
Savladali smo suštu mehaniku LLM modela od matematičkih osnova Transformer arhitekture do izgradnje A/B Sandbox aplikacije koja direktno komunicira sa produkcijskim API-jevima. Sutra nas čeka Dan 2: Odabir modela, lokalni LLM deployment sa Ollama, GPU/VRAM optimizacija i Hugging Face ekosistem!