OpenAI-kompatibilan API na svom serveru

Sve što si do sada pokrenuo — Ollama, llama.cpp, vLLM — izlaže isti oblik API-ja. To nije slučajnost nego pristanak: OpenAI je definisao oblik zahteva, ostali su ga preuzeli, i zahvaljujući tome tvoj lokalni model radi sa alatima koji o njemu ništa ne znaju.

Ovaj tekst razlaže taj oblik do kraja. Sve dalje u serijalu — skripte, analiza logova, RAG, agenti — svodi se na zahteve opisane ovde.

Najmanji mogući zahtev

$ curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3:8b",
    "messages": [
      {"role": "user", "content": "Šta radi komanda ss -tulpn?"}
    ]
  }'

Adresa se razlikuje po alatu, ostalo je isto:

Alat Adresa Polje model
Ollama :11434/v1 Obavezno, oznaka iz ollama list
llama.cpp :8080/v1 Zanemaruje se, drži jedan model
vLLM :8000/v1 Obavezno, mora se poklopiti

Šta je servis stvarno spreman da posluži:

$ curl -s http://localhost:11434/v1/models | jq -r '.data[].id'
qwen3:8b
linux-pomocnik:latest

Oblik odgovora

{
  "id": "chatcmpl-843",
  "object": "chat.completion",
  "created": 1755870123,
  "model": "qwen3:8b",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Prikazuje otvorene portove sa procesima koji ih drže."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 24,
    "completion_tokens": 18,
    "total_tokens": 42
  }
}

Sam tekst je duboko ukopan, pa u skriptama ide kroz jq:

$ curl -s http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3:8b","messages":[{"role":"user","content":"Zdravo"}]}' \
  | jq -r '.choices[0].message.content'

Dva polja u odgovoru vredi pratiti.

finish_reason govori zašto je generisanje stalo:

Vrednost Značenje
stop Model je završio prirodno
length Dostignut max_tokens, odgovor je odsečen
tool_calls Model traži poziv funkcije, razgovor nije gotov

usage je jedini pouzdan brojač tokena. Ako pratiš koliko ti konteksta ostaje ili poredaš postavke, tu je podatak.

Uloge poruka

{
  "model": "qwen3:8b",
  "messages": [
    {"role": "system", "content": "Ti si pomoćnik za Linux administraciju. Odgovaraj kratko, na srpskom."},
    {"role": "user", "content": "Kako da vidim zauzeće diska?"},
    {"role": "assistant", "content": "Komandom df -h za particije, du -sh za direktorijume."},
    {"role": "user", "content": "A po direktorijumima, sortirano?"}
  ]
}
Uloga Čemu služi
system Uloga i pravila; ide prva, jednom
user Pitanje ili zadatak
assistant Raniji odgovori modela
tool Rezultat izvršene funkcije

Server ne pamti ništa između zahteva. Ceo dosadašnji razgovor šalješ ponovo u svakom pozivu — i to je razlog zašto dug razgovor postaje sporiji i skuplji, kako objašnjava tekst o tome šta je AI model. Istorija koju vidiš u Open WebUI je posao aplikacije, ne servisa.

Ovo pravilo je i ključ celog odeljka o tool callingu niže. Sve što nije u messages — ne postoji.

Parametri generisanja

{
  "model": "qwen3:8b",
  "messages": [{"role": "user", "content": "Napiši komandu za pretragu logova"}],
  "temperature": 0.1,
  "max_tokens": 256,
  "top_p": 0.9,
  "seed": 42,
  "stop": ["\n\n"]
}
Parametar Praktična vrednost
temperature 0 – 0.2 za komande i podatke, 0.7 – 1.0 za tekst
max_tokens Postavi ga; bez njega odgovor ume da beži
top_p 0.9, retko se dira
seed Uz temperaturu 0, za ponovljiv rezultat
stop Nizovi na kojima generisanje prestaje
stream Postepen ispis

Za skripte je kombinacija temperature: 0 i fiksiran seed ono što daje ponovljivost. Bez nje ista komanda danas i sutra daje različit rezultat, što u automatizaciji nije prihvatljivo.

Streaming

$ curl -N http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3:8b",
    "messages": [{"role": "user", "content": "Objasni šta je inode"}],
    "stream": true
  }'
data: {"choices":[{"delta":{"content":"Inode"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":" je"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":" struktura"},"finish_reason":null}]}
data: [DONE]

Format je Server-Sent Events. Umesto message dobijaš delta sa komadićem teksta, a niz se završava oznakom [DONE].

Sklapanje u bashu:

$ curl -sN http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen3:8b","messages":[{"role":"user","content":"Zdravo"}],"stream":true}' \
  | grep '^data: ' | sed 's/^data: //' | grep -v '^\[DONE\]' \
  | jq -j '.choices[0].delta.content // empty'

Oznaka -N kod curl isključuje baferovanje. Bez nje sve stiže odjednom na kraju i cela poenta otpada.

Za skripte streaming najčešće nije potreban. Ako obrađuješ ceo odgovor odjednom, izostavi ga i uzmi jednostavniji oblik. Koristan je samo tamo gde neko gleda u ekran.

Strukturiran izlaz

Ovo je najkorisnija stvar u celom tekstu za administratora. Umesto da parsiraš prozu, tražiš JSON:

$ curl -s http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3:8b",
    "messages": [
      {"role": "system", "content": "Odgovaraj isključivo u JSON obliku, bez ikakvog dodatnog teksta."},
      {"role": "user", "content": "Klasifikuj ovu poruku iz loga: SSH login failed for root from 203.0.113.45. Vrati polja: tip, ozbiljnost (1-5), ip."}
    ],
    "temperature": 0,
    "response_format": {"type": "json_object"}
  }' | jq -r '.choices[0].message.content'
{
  "tip": "neuspela_prijava",
  "ozbiljnost": 4,
  "ip": "203.0.113.45"
}

Polje response_format tera model da vrati ispravan JSON. vLLM ide korak dalje i podržava tačnu šemu:

"response_format": {
  "type": "json_schema",
  "json_schema": {
    "name": "log_zapis",
    "schema": {
      "type": "object",
      "properties": {
        "tip": {"type": "string"},
        "ozbiljnost": {"type": "integer", "minimum": 1, "maximum": 5},
        "ip": {"type": "string"}
      },
      "required": ["tip", "ozbiljnost", "ip"]
    }
  }
}

Sa šemom je izlaz zajamčeno ispravan, jer se ograničenje primenjuje pri samom biranju tokena. Bez nje se oslanjaš na to da će model poslušati — što obično hoće, ali ne uvek.

I dalje proveravaj rezultat pre upotrebe. Ispravan JSON nije isto što i tačan JSON; model može uredno vratiti pogrešnu vrednost u ispravnom obliku.

Tool calling

Tool calling je mesto gde model prestaje da bude sagovornik i postaje deo tvog sistema. Ali radi drugačije nego što na prvi pogled izgleda, pa ide korak po korak.

Šta se zapravo dešava

Model ne izvršava ništa. On nema pristup tvom serveru, ne ume da pokrene komandu i ne zna šta se nalazi na disku. Ono što ume jeste da, umesto teksta, vrati strukturiran zahtev: „pozovi funkciju provera_diska sa argumentom /". Tvoj kod tu funkciju izvršava i rezultat vraća modelu kao još jednu poruku u razgovoru.

Ceo mehanizam su četiri koraka:

  1. Šalješ pitanje zajedno sa spiskom dostupnih funkcija (tools)
  2. Model vraća tool_calls i finish_reason: "tool_calls"
  3. Ti izvršavaš funkciju u svom kodu
  4. Šalješ novi zahtev sa celom istorijom plus rezultatom

Najvažnija stvar, koja se najčešće promaši: ne postoji poseban endpoint za alate. Sve ide na isti POST /v1/chat/completions, samo ga pozoveš dva puta. Jedina razlika između ta dva zahteva je sadržaj polja messages.

Kako se alat definiše

Definicija je JSON Schema opis potpisa funkcije. Model ne vidi tvoj kod — vidi samo ovaj opis i na osnovu njega odlučuje kada i kako da funkciju pozove.

"tools": [
  {
    "type": "function",
    "function": {
      "name": "provera_diska",
      "description": "Vraća zauzeće diska za zadatu putanju na serveru",
      "parameters": {
        "type": "object",
        "properties": {
          "putanja": {
            "type": "string",
            "description": "Apsolutna putanja, npr. / ili /var"
          },
          "jedinica": {
            "type": "string",
            "enum": ["G", "M"],
            "description": "Jedinica prikaza"
          }
        },
        "required": ["putanja"]
      }
    }
  }
]

Tri stvari odlučuju da li će ovo raditi:

  • description funkcije je jedini signal kada da je pozove. „Vraća zauzeće diska za zadatu putanju" radi; „disk funkcija" ne radi. Piši kao da objašnjavaš kolegi koji ne poznaje tvoj sistem.
  • description svakog parametra odlučuje šta će model staviti unutra. Bez primera formata model izmisli format.
  • enum je najjači alat koji imaš. Sve što može biti zatvoren spisak — neka bude enum. Time skidaš čitav sloj validacije sa sebe.

Polje parameters je običan JSON Schema, pa važi sve — integer sa minimum i maximum, ugnježđeni objekti, nizovi. Ako funkcija nema argumente, ide "parameters": {"type": "object", "properties": {}}.

Korak 1: prvi zahtev

Sadržaj fajla zahtev-1.json:

{
  "model": "qwen3:8b",
  "messages": [
    {"role": "user", "content": "Koliko je slobodno na root particiji?"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "provera_diska",
        "description": "Vraća zauzeće diska za zadatu putanju na serveru",
        "parameters": {
          "type": "object",
          "properties": {
            "putanja": {"type": "string", "description": "Apsolutna putanja, npr. / ili /var"}
          },
          "required": ["putanja"]
        }
      }
    }
  ]
}
$ curl -s http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @zahtev-1.json | jq '.choices[0]'

Korak 2: odgovor modela

{
  "index": 0,
  "message": {
    "role": "assistant",
    "content": null,
    "tool_calls": [
      {
        "id": "call_abc123",
        "type": "function",
        "function": {
          "name": "provera_diska",
          "arguments": "{\"putanja\":\"/\"}"
        }
      }
    ]
  },
  "finish_reason": "tool_calls"
}

Polje content je prazno, a finish_reason je tool_calls — razgovor nije gotov, model čeka podatak.

Dve zamke u ovom odgovoru:

  • arguments je string, ne objekat. Mora dvostruko parsiranje, kroz fromjson.
  • tool_calls je niz. Model sme da traži više poziva odjednom, i na svaki moraš odgovoriti zasebnom tool porukom.
$ echo "$ODGOVOR" | jq -r '.choices[0].message.tool_calls[0].function.arguments | fromjson | .putanja'
/

Korak 3: ti izvršavaš

Ovaj korak se dešava potpuno izvan API-ja. Model tu nema nikakve veze:

$ df -h /
/dev/sda1  234G  189G  33G  86% /

Korak 4: drugi zahtev, isti endpoint

Ovde je deo koji se najčešće pogrešno uradi. Ne šalješ samo tool poruku — šalješ ceo razgovor, jer server ne pamti ništa. Sadržaj fajla zahtev-2.json:

{
  "model": "qwen3:8b",
  "messages": [
    {"role": "user", "content": "Koliko je slobodno na root particiji?"},
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {"name": "provera_diska", "arguments": "{\"putanja\":\"/\"}"}
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_abc123",
      "content": "/dev/sda1  234G  189G  33G  86% /"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "provera_diska",
        "description": "Vraća zauzeće diska za zadatu putanju na serveru",
        "parameters": {
          "type": "object",
          "properties": {
            "putanja": {"type": "string", "description": "Apsolutna putanja, npr. / ili /var"}
          },
          "required": ["putanja"]
        }
      }
    }
  ]
}
$ curl -s http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @zahtev-2.json | jq -r '.choices[0].message.content'

Root particija ima 234G ukupno, zauzeto je 189G, a slobodno 33G — 86% popunjenosti.

Sada je finish_reason jednak stop. Gotovo.

Šta se menja između dva zahteva

Polje Zahtev 1 Zahtev 2
Adresa /v1/chat/completions ista
model qwen3:8b isti
tools definicija ista, ponovo poslata
messages 1 poruka 3 poruke

Menja se samo messages. Ništa drugo.

Tri greške koje daju 400 ili besmislen odgovor

  1. Izostavljena assistant poruka sa tool_calls u drugom zahtevu. Tada tool_call_id pokazuje u prazno. vLLM na ovo vraća 400, Ollama je popustljivija ali odgovor bude besmislen.
  2. Izmišljen tool_call_id. Mora biti doslovno onaj iz odgovora modela.
  3. Izostavljen tools u drugom zahtevu. Model tada ne zna šta je provera_diska i ne ume da protumači tool poruku.

Više alata — model bira sam

Primer sa jednim alatom je samo najmanji mogući. U praksi šalješ ceo spisak i model odlučuje šta mu treba:

"tools": [
  {"type": "function", "function": {"name": "provera_diska",   ...}},
  {"type": "function", "function": {"name": "status_servisa",  ...}},
  {"type": "function", "function": {"name": "pretraga_logova", ...}},
  {"type": "function", "function": {"name": "lista_korisnika", ...}}
]
  • „Koliko ima mesta na disku?" — pozove provera_diska
  • „Da li nginx radi i ima li grešaka u logu?" — pozove status_servisa i pretraga_logova
  • „Šta znači inode?" — ne pozove ništa, samo odgovori tekstom

To je podrazumevano ponašanje i ne moraš ništa da uključuješ. Kvalitet izbora zavisi isključivo od toga koliko su ti description polja jasna.

Kontrolu imaš kroz tool_choice:

Vrednost Ponašanje
"auto" Podrazumevano — model odlučuje
"none" Zabranjeno, samo tekst
{"type":"function","function":{"name":"provera_diska"}} Prisiljen na tačno tu funkciju

Prisiljavanje je korisno kada tool calling koristiš kao ekstraktor podataka — model mora da popuni šemu, i time si dobio strukturiran izlaz na zadnja vrata.

Petlja, ne jedan prolaz

Model sme da traži još jedan poziv i nakon što je dobio prvi rezultat. Pravi tok je zato while, a ne dva curl-a:

#!/bin/bash
API="http://localhost:11434/v1/chat/completions"
MODEL="qwen3:8b"

ALATI='[{"type":"function","function":{
  "name":"provera_diska",
  "description":"Vraća zauzeće diska za zadatu putanju na serveru",
  "parameters":{"type":"object",
    "properties":{"putanja":{"type":"string","description":"Apsolutna putanja"}},
    "required":["putanja"]}}}]'

PORUKE='[{"role":"user","content":"Koliko je slobodno na root particiji?"}]'

for i in 1 2 3; do
    ZAHTEV=$(jq -n --arg m "$MODEL" --argjson p "$PORUKE" --argjson t "$ALATI" \
        '{model:$m, messages:$p, tools:$t, temperature:0}')

    ODGOVOR=$(curl -s "$API" -H "Content-Type: application/json" -d "$ZAHTEV")
    PORUKA=$(echo "$ODGOVOR" | jq '.choices[0].message')
    RAZLOG=$(echo "$ODGOVOR" | jq -r '.choices[0].finish_reason')

    if [ "$RAZLOG" != "tool_calls" ]; then
        echo "$PORUKA" | jq -r '.content'
        break
    fi

    # 1. assistant poruka sa tool_calls ide nazad u istoriju
    PORUKE=$(jq -n --argjson p "$PORUKE" --argjson n "$PORUKA" '$p + [$n]')

    # 2. izvrši svaki traženi poziv
    BROJ=$(echo "$PORUKA" | jq '.tool_calls | length')
    for ((j=0; j<BROJ; j++)); do
        ID=$(echo "$PORUKA"  | jq -r ".tool_calls[$j].id")
        IME=$(echo "$PORUKA" | jq -r ".tool_calls[$j].function.name")
        PUT=$(echo "$PORUKA" | jq -r ".tool_calls[$j].function.arguments | fromjson | .putanja")

        case "$IME:$PUT" in
            provera_diska:/|provera_diska:/var|provera_diska:/home)
                REZ=$(df -h "$PUT" | tail -1) ;;
            *)
                REZ="Greška: nedozvoljena funkcija ili putanja" ;;
        esac

        # 3. rezultat kao tool poruka
        PORUKE=$(jq -n --argjson p "$PORUKE" --arg id "$ID" --arg c "$REZ" \
            '$p + [{role:"tool", tool_call_id:$id, content:$c}]')
    done
done

Petlja ide do tri kruga jer model sme da traži novi poziv i posle prvog rezultata. Uvek stavi brojač iteracija — model ume da upadne u krug pozivanja iste funkcije.

Pokreni skriptu sa bash -x i posmatraj kako promenljiva PORUKE raste iz kruga u krug. To najbolje objasni ceo mehanizam.

Definiši jednom, koristi kroz razgovor

Server je bez stanja, pa spisak alata ide u svakom zahtevu — ali to je mehanika ispod, ne tvoj svakodnevni posao. Registar napišeš jednom i on se dalje šalje sam. U Pythonu:

import subprocess, ollama

# ---- definišeš jednom ----
def provera_diska(putanja: str) -> str:
    """Vraća zauzeće diska za zadatu putanju na serveru."""
    if putanja not in ("/", "/var", "/home"):
        return "Nedozvoljena putanja"
    return subprocess.run(["df", "-h", putanja],
                          capture_output=True, text=True).stdout

def status_servisa(ime: str) -> str:
    """Vraća da li je systemd servis trenutno aktivan."""
    if not ime.isalnum():
        return "Nedozvoljeno ime servisa"
    return subprocess.run(["systemctl", "is-active", ime],
                          capture_output=True, text=True).stdout.strip()

REGISTAR = {"provera_diska": provera_diska,
            "status_servisa": status_servisa}

# ---- i onda samo pričaš ----
poruke = [{"role": "user", "content": "Da li nginx radi i koliko ima mesta na /var?"}]

for _ in range(5):
    odgovor = ollama.chat(model="qwen3:8b", messages=poruke,
                          tools=list(REGISTAR.values()))
    poruke.append(odgovor["message"])

    pozivi = odgovor["message"].get("tool_calls")
    if not pozivi:
        print(odgovor["message"]["content"])
        break

    for p in pozivi:
        ime = p["function"]["name"]
        rez = REGISTAR[ime](**p["function"]["arguments"])
        poruke.append({"role": "tool", "tool_call_id": p["id"], "content": rez})

Ollama biblioteka iz same Python funkcije izvlači JSON šemu — iz naziva, tipova argumenata i docstringa. Zato je docstring bukvalno ono što model čita da bi odlučio kada da funkciju pozove. Loš docstring je isto što i loš description u ručnom JSON-u.

Ako ni ovo ne želiš da pišeš, postoje gotovi slojevi:

Sloj Šta dobiješ
Ollama / OpenAI biblioteka Šema iz funkcije, ali petlju i dalje pišeš ti
Open WebUI Tools Python funkcija napisana u pretraživaču, odmah dostupna u chatu — bez ijedne linije petlje
MCP Alat kao zaseban server; vidi ga svaki klijent koji zna MCP

Open WebUI je najbliži tome što najčešće želiš — alat napišeš u administraciji, uključiš ga za model, i dalje samo kucaš u chat prozor. MCP rešava sledeći nivo: alat napisan jednom radi i u Open WebUI, i u drugim klijentima, i u tvojoj skripti, bez prepisivanja. Ispod svega i dalje teče isti ciklus opisan ovde.

Zašto onda ručni curl

Ne da bi ga svakodnevno koristio, nego da bi znao šta se dešava kada alat „ne okine". Odgovor je uvek u jednom od tri sloja: model nema tools sposobnost, description je loš, ili se tool poruka ne vraća ispravno. Bez razumevanja ciklusa to izgleda kao magija koja se pokvarila.

Zašto tool calling često ne radi

  • Model nema sposobnost za alate. Provera: ollama show qwen3:8b | grep -A5 Capabilities
  • Model manji od oko 7B — halucinira imena funkcija ili vrati poziv kao običan tekst umesto u polju tool_calls
  • temperature je previsoka; za tool calling drži je na 0
  • Previše alata odjednom — iznad pet do šest tačnost izbora naglo pada

Bezbednosna granica

Ime funkcije i argumente predlaže model, a model može biti naveden podmetnutom instrukcijom u podacima koje čita — u logu, u fajlu, u tuđem tekstu. Argument koji stigne od modela tretiraj isto kao argument iz nepoverljivog HTTP zahteva.

# NIKAD
eval "df -h $PUTANJA"

# Nego
case "$PUTANJA" in
    /|/var|/home) df -h "$PUTANJA" ;;
    *) echo "Nedozvoljena putanja" ;;
esac

Beli spisak, nikad crni. Drži spisak dozvoljenih radnji uzak i nikad ne prosleđuj argumente pravo u ljusku. Tema je razrađena u tekstu o bezbednosti i prompt injectionu.

Embeddings

Zaseban put, za pretvaranje teksta u numerički zapis značenja:

$ curl -s http://localhost:11434/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"model": "nomic-embed-text", "input": "restart nginx servisa"}' \
  | jq '.data[0].embedding | length'
768

Model za razgovor ovo ne radi — treba poseban, namenski. Osnova je za RAG.

Autentifikacija

$ curl http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer tajni-kljuc-ovde" ...

Ollama ovo polje ignoriše i prihvata bilo šta. llama.cpp i vLLM ga proveravaju ako je servis podignut sa --api-key. Za ozbiljnu zaštitu ide proxy ispred, o čemu govore tekstovi o reverse proxy-ju i zaštiti API-ja.

Greške

$ curl -s -w '\n%{http_code}\n' http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"nepostojeci","messages":[{"role":"user","content":"test"}]}'
Kod Uzrok
400 Neispravan JSON, predugačak kontekst ili neispravan sled tool poruka
401 Pogrešan ili izostavljen ključ
404 Model nije preuzet ili naziv ne odgovara
500 Servis puca, najčešće zbog memorije
503 Model se učitava ili je red pun

Skripte piši tako da uvek gledaju kod odgovora:

#!/bin/bash
ODGOVOR=$(curl -s -w '\n%{http_code}' http://localhost:11434/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d "$ZAHTEV")

KOD=$(echo "$ODGOVOR" | tail -1)
TELO=$(echo "$ODGOVOR" | head -n -1)

if [ "$KOD" != "200" ]; then
    echo "Greška $KOD: $(echo "$TELO" | jq -r '.error.message // .')" >&2
    exit 1
fi

echo "$TELO" | jq -r '.choices[0].message.content'

Ovaj obrazac se ponavlja kroz ceo ostatak serijala. Osnove pisanja skripti pokriva serijal o bash skriptama.

Praktični scenariji

Scenario 1: odgovor se seče na pola rečenice

Pogledaj finish_reason. Vrednost length znači da je max_tokens premali.

Scenario 2: isti zahtev daje različite odgovore

Postavi temperature na 0 i fiksiraj seed. Bez oba nema ponovljivosti.

Scenario 3: JSON u odgovoru okružen objašnjenjem

Dodaj response_format, a u sistemski prompt izričito napiši da nema propratnog teksta. Ako i to ne pomogne, izvuci deo između vitičastih zagrada.

Scenario 4: greška 400 sa pomenom konteksta

Razgovor je premašio prozor. Skrati istoriju ili podigni kontekst servisa, uz proveru memorije.

Scenario 5: prvi zahtev traje dugo, ostali su brzi

Model se učitava pri prvom pozivu. Podigni OLLAMA_KEEP_ALIVE, kako je opisano u tekstu o systemd jedinici.

Scenario 6: model zanemaruje sistemski prompt

Proveri da nije base verzija. Kod nekih modela pomaže i da se pravilo ponovi na kraju korisničke poruke.

Scenario 7: model uporno traži isti alat u krug

Rezultat koji si vratio kao tool poruku mu ne odgovara na pitanje — najčešće je prazan ili je poruka o grešci. Vrati razumljiv tekst umesto praznog niza i ograniči broj iteracija.

Scenario 8: model ignoriše alat i odgovara napamet

Popravi description funkcije, spusti temperature na 0, i proveri da model uopšte ima sposobnost za alate. Ako mora da ga pozove, koristi tool_choice sa imenom funkcije.

Scenario 9: greška 400 pri drugom zahtevu sa alatom

Skoro uvek nedostaje assistant poruka sa tool_calls u istoriji, ili se tool_call_id ne poklapa. Ispiši messages pre slanja i uporedi identifikatore.

Scenario 10: model šalje pogrešnu vrednost argumenta

Dopuni description parametra primerom formata, a ako je spisak vrednosti konačan — pretvori ga u enum.

Kratka referenca

  • POST /v1/chat/completions — glavni put, isti i za razgovor i za alate
  • GET /v1/models — dostupni modeli
  • POST /v1/embeddings — numerički zapis teksta
  • messages — system, user, assistant, tool
  • .choices[0].message.content — put do teksta odgovora
  • finish_reason — stop, length, tool_calls
  • usage — pouzdan broj tokena
  • temperature: 0 + seed — ponovljiv rezultat
  • max_tokens — postavi ga uvek
  • stream: true + curl -N — postepen ispis
  • response_format: {"type":"json_object"} — JSON umesto proze
  • tools — spisak funkcija; šalje se u svakom zahtevu
  • tool_calls — model predlaže poziv, tvoj kod izvršava
  • tool_call_id — mora se poklopiti sa id-jem iz odgovora
  • tool_choice — auto, none ili prisiljena funkcija
  • arguments — string sa JSON-om, ide kroz fromjson
  • Authorization: Bearer — ključ, koji Ollama ignoriše
  • Server ne pamti ništa — istorija ide u svakom zahtevu

Vežba

  1. Pošalji zahtev kroz curl i izvuci samo tekst odgovora pomoću jq.
  2. Namerno postavi max_tokens na 10 i potvrdi vrednost finish_reason.
  3. Pošalji isti zahtev dvaput sa temperaturom 0 i fiksiranim seed, pa uporedi odgovore znak po znak.
  4. Napiši zahtev koji vraća JSON sa tri polja i proveri ga kroz jq -e.
  5. Sastavi razgovor od četiri poruke i uporedi prompt_tokens sa vrednošću za jednu poruku.
  6. Napiši skriptu koja proverava HTTP kod i vraća razumnu poruku pri grešci.
  7. Napravi fajlove zahtev-1.json i zahtev-2.json iz ovog teksta i ručno prođi ceo ciklus tool callinga kroz dva curl poziva.
  8. Namerno izbaci assistant poruku sa tool_calls iz drugog zahteva i zabeleži šta servis vrati.
  9. Dodaj drugi alat, status_servisa, i postavi pitanje koje traži oba poziva odjednom. Prebroj stavke u nizu tool_calls.
  10. Pokreni bash petlju sa bash -x i prati kako PORUKE raste iz kruga u krug.
  11. Postavi tool_choice na tačno određenu funkciju i postavi pitanje koje sa njom nema veze. Pogledaj šta model uradi sa argumentima.

Sledeći tekst u serijalu: Nginx reverse proxy i HTTPS ispred lokalnog LLM-a

Povezano:

Comments

Popular posts from this blog

Početak u Linuxu — šta je i zašto se uči

groupadd

Konverzija tipova podataka u Pythonu