MODUL 1 - LEKCIJA 4

Workshop: Postavljanje Multi-Model Sandbox Okruženja

Konekcija na OpenAI i Anthropic providere istovremeno uz praćenje troškova i A/B usporedbu

⏱️ Trajanje: 1h 45min (15:45 - 17:30) 📚 Nivo: Napredni (Programiranje) 🎯 Lab: Multi-Model Python Skripta
🧪

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.

User Prompt app.py (AI Sandbox) .env (API Keys) A/B Logika openai_client.chat... claude_client.messages... OpenAI API gpt-4o Anthropic API claude-3.5-sonnet Odgovor 1 + Trošak Odgovor 2 + Trošak A/B Usporedba

💡 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.

1

Priprema okruženja i instalacija SDK paketa

Otvorite VS Code u AI_Kurs folderu. Aktivirajte venv okruženje u integriranom terminalu (Terminal → New Terminal):

powershell
# 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 čita OPENAI_API_KEY iz environment varijabli.
  • anthropic — Zvanični Python paket za Claude 3 i Claude 3.5 modele. Automatski čita ANTHROPIC_API_KEY.
  • colorama — Biblioteka za obojeni terminal output (ANSI escape kodovi). Koristi se za vizualno razlikovanje GPT vs. Claude odgovora u terminalu.
2

Sigurno Upravljanje API Ključevima (.env datoteka)

Proširite .env fajl koji ste kreirali u Lekciji 1.3 sa Anthropic ključem:

env
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č.

3

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:

python
"""
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)
4

Pokretanje i Analiza Rezultata

Sačuvajte app.py i pokrenite u terminalu:

powershell
# Uvjerite se da ste u AI_Kurs folderu sa aktivnim venv-om
python app.py

Trebate vidjeti obojeni output sličan ovome (simplificirano):

📤 Očekivani Output (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¢)
5

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:

🔄 Razlike: OpenAI vs. Anthropic API
┌─────────────────────────────────────────────────────────────┐
│ 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!

python
# 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):

  1. Promijenite TASK varijablu u app.py i 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!).
  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):

  1. 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")
  2. 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!