Function calling u 2026: Vodič za paralelne alate i pouzdano izvršavanje s OpenAI i Claude

Praktični vodič za function calling u 2026: kako dizajnirati JSON Schemu, kada uključiti paralelno pozivanje alata i kako evaluirati agente prije produkcije, s primjerima za OpenAI i Claude.

Function Calling 2026: OpenAI vs Claude vodič

Ažurirano: 11. srpnja 2026.

Function calling je mehanizam kojim LLM (poput OpenAI GPT-a ili Anthropic Claude) vraća strukturirani JSON s imenom alata i argumentima umjesto slobodnog teksta, a vaš kod tada izvršava taj alat i vraća rezultat modelu. U 2026. godini oba glavna dobavljača podržavaju paralelno izvršavanje više alata u jednom odgovoru, no s bitnim razlikama u strict načinu rada, tool_choice semantici i ponašanju pri pogreškama. U ovom vodiču pokazujem kako dizajnirati sheme, kada isključiti paralelizam i kako pouzdano evaluirati pozive alata prije nego što ih pustite u produkciju.

  • U 2026. je strict: true za OpenAI de facto standard, ali je nekompatibilan s paralelnim pozivima, pa morate birati između pouzdanosti sheme i paralelizma.
  • Claude API nudi paralelizam prema zadanim postavkama; isključuje se pomoću tool_choice.disable_parallel_tool_use: true, a ne kao top-level parametar.
  • Četiri tool_choice načina rada (auto, any, tool, none) semantički nisu identična između OpenAI i Claude, pa provjerite mapiranje prije multi-provider deploymenta.
  • Schema-first pristup (Pydantic ili Zod) uklanja 80% grešaka u produkciji; sve što nije pokriveno JSON Schemom (regex, min/max) validirajte u kodu.
  • Evaluacija poziva alata mora imati tri sloja: schema-validation, argument-correctness i end-to-end task success. Bez sva tri, ne znate stvarno radi li alat.

Što je function calling u LLM-ovima?

Function calling (ili tool use, kako to Anthropic voli zvati) je API ugovor: vi modelu opisujete alate kao JSON Sheme, a model, umjesto da vam pošalje običan tekst, vraća strukturiranu poruku koja kaže "želim pozvati alat X sa sljedećim argumentima". Model ne izvršava ništa. Vaš backend izvršava alat i vraća rezultat kao tool_result poruku, a model tada sastavlja finalni odgovor za korisnika.

Ovaj obrazac je postao temelj svega što danas zovemo "AI agentima": pretraga baze, poziv API-ja, čitanje dokumenata, pokretanje SQL upita, sve se to modelira kao alat. Ono što se drastično promijenilo od 2024. je razina jamstava. Prije smo se, iskreno, molili da model vrati valjan JSON. U 2026. i OpenAI i Anthropic nude jamstva sheme na razini decodera, što znači da valjanost JSON-a više nije problem. Problem je sada ispravnost argumenata.

U svojoj praksi (dosta shipping-a i još više debug-a) naučio sam da 80% incidenata u produkciji nema veze s modelom nego s lošom shemom: preširoki enum, nedostatak required, previše opcionalnih polja, nejasan description. Prvi korak je zato uvijek isti: napišite shemu prije prompta. Više o tome piše u našem vodiču o strukturiranim izlazima s Pydantic i Instructor bibliotekom, koji je izravni pratitelj ove teme.

OpenAI vs Claude function calling: usporedba u 2026.

Ovdje je jedan od rijetkih trenutaka kad ću bez ustručavanja usporediti dva dobavljača, jer razlike stvarno utječu na dizajn koda. Ne radi se o "boljem", nego o različitim modelima jamstava.

ZnačajkaOpenAI (2026)Anthropic Claude (2026)
Jamstvo shemestrict: true, na razini decoderaNema formalnog "strict"; oslonac na trening + validaciju
Paralelno pozivanje alataPodržano, ali isključuje strict jamstvaPodržano prema zadanoj postavci; zadržava jamstva
Isključivanje paralelizmaparallel_tool_calls: false (top-level)tool_choice.disable_parallel_tool_use: true
tool_choice načiniauto, required, none, {"type":"function"}auto, any, tool, none
Nepodržana ograničenja JSON Schemepattern, format, min/maxLength, min/maximumSličan skup, ali manje formalno dokumentirano
Extended thinking + tool useReasoning modeli podržavaju sve tool_choice tipoveSamo auto i none uz extended thinking
Token-učinkovito korištenje alataUgrađeno u GPT-5.xUgrađeno u Claude 4 (u 3.7 kao beta zaglavlje)

Glavna praktična implikacija: ako trebate paralelizam i apsolutna jamstva sheme, Claude to nudi izvan kutije, dok kod OpenAI-ja morate birati. S druge strane, OpenAI-jev strict: true ima čvršću matematičku garanciju za jedan alat, jer je implementiran kao ograničeni decoding. Ja u produkcijskim customer-support agentima gotovo uvijek isključujem paralelizam na OpenAI-ju. Pouzdanost je važnija od 200ms latencije.

Detalji za tool_choice semantiku

Ime "any" kod Claude-a ne znači "bilo koji" nego "mora pozvati barem jedan alat, ali biraš koji". OpenAI-jev ekvivalent je required. Ako ovo krivo mapirate u LiteLLM ili sličnom sloju, dobit ćete tihe promjene ponašanja koje se teško otkrivaju. Model iznenada uvijek zove alat gdje nije trebao. Za više detalja o gateway-u koji rješava ove razlike, pogledajte naš vodič o LiteLLM proxy kao produkcijski LLM gateway.

Kako dizajnirati JSON Schemu za pouzdane pozive alata

Dobra shema alata je 90% razlike između alata koji radi i alata koji "radi 8 od 10 puta". Slijedim tri pravila koja sam skupljao kroz greške:

  1. Uska polja pobjeđuju široka polja. enum uvijek kad je moguće; const za polja koja stvarno imaju samo jednu ispravnu vrijednost; strogi type.
  2. Sve polja su required; opcionalnost radite preko ["string", "null"]. Ovo je zahtjev OpenAI strict mode-a i sjajna disciplina inače.
  3. Description za polja je vaš najbolji prompt. Model čita description. Uložite u to.

Evo praktičnog primjera, alat za pretragu narudžbi, u Pythonu, koji radi identično na oba dobavljača:

from pydantic import BaseModel, Field
from typing import Literal, Optional
from openai import OpenAI
from anthropic import Anthropic

# 1) Shema-prvo: definirajte model
class SearchOrdersArgs(BaseModel):
    """Pretražuje narudžbe kupca po statusu i vremenskom prozoru."""
    customer_id: str = Field(
        description="Interni ID kupca, uvijek 8 znakova, prefiks 'C-'."
    )
    status: Literal["pending", "shipped", "delivered", "returned"] = Field(
        description="Točan status; NE koristiti sinonime."
    )
    days_back: int = Field(
        ge=1, le=90,
        description="Koliko dana unatrag pretraživati (1-90)."
    )
    limit: Optional[int] = Field(
        default=None,
        description="Maksimalan broj rezultata; ako je null, backend koristi 20."
    )

# 2) Pretvorba u JSON Schemu za OBA dobavljača
tool_schema = SearchOrdersArgs.model_json_schema()

# --- OpenAI (strict mode) ---
openai_tool = {
    "type": "function",
    "function": {
        "name": "search_orders",
        "description": "Pretrazi narudzbe kupca. Koristi samo kad imas tocan customer_id.",
        "strict": True,
        "parameters": {
            **tool_schema,
            "additionalProperties": False,
        },
    },
}

# --- Anthropic Claude ---
claude_tool = {
    "name": "search_orders",
    "description": "Pretrazi narudzbe kupca. Koristi samo kad imas tocan customer_id.",
    "input_schema": tool_schema,
}

Ograničenja JSON Scheme koja modeli ignoriraju

Ovo često iznenadi ljude: strict: true ne enforce-a pattern, format, minLength, maxLength, minimum ni maximum. Ako trebate regex validaciju ili numeričke granice, morate ih provjeriti u vlastitom kodu nakon što model vrati argumente. Pydantic to radi za vas, što je još jedan razlog zašto ga preferiram nad ručno pisanim shemama.

Kako rukovati paralelnim pozivima alata

Kad korisnik pita "koliko je sati u NYC-u i Tokiju?", model može u istom odgovoru vratiti dva tool_use bloka. Vaš kod ih mora izvršiti i vratiti oba rezultata prije nego što model sastavi finalni odgovor. Evo referentnog obrasca s Claude-om, s asyncio.gather za pravi paralelizam:

import asyncio
from anthropic import AsyncAnthropic

client = AsyncAnthropic()

async def run_tool_call(block):
    """Dispatchira tool_use blok na odgovarajucu Python funkciju."""
    if block.name == "search_orders":
        return await search_orders_impl(**block.input)
    if block.name == "get_weather":
        return await get_weather_impl(**block.input)
    raise ValueError(f"Nepoznat alat: {block.name}")

async def agent_turn(messages, tools):
    resp = await client.messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    if resp.stop_reason != "tool_use":
        return resp  # Model je odgovorio tekstom, gotovi smo

    tool_blocks = [b for b in resp.content if b.type == "tool_use"]

    # Izvrsi sve alate paralelno
    results = await asyncio.gather(*[run_tool_call(b) for b in tool_blocks])

    # Sastavi tool_result poruku za sljedeci turn
    tool_results = [
        {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": str(result),
        }
        for block, result in zip(tool_blocks, results)
    ]

    messages.append({"role": "assistant", "content": resp.content})
    messages.append({"role": "user", "content": tool_results})
    return await agent_turn(messages, tools)  # Rekurzija dok model ne zavrsi

Tri stvari koje ovaj kod radi ispravno, a većina tutorijala ne. Prvo, izvršava alate stvarno paralelno preko asyncio.gather, ne u petlji. Drugo, čuva izvorni resp.content u sljedećoj poruci; model treba svoj vlastiti tool_use blok kao kontekst. Treće, rekurzivno nastavlja jer model može tražiti još alata nakon što vidi rezultate.

Kada isključiti paralelno izvršavanje alata

Ovo je vjerojatno najčešće pitanje koje mi šalju u DM: "trebam li paralelizam?". Moje pravilo palca:

  • Isključite ga ako je backend alat non-idempotentan, ako alati imaju ovisnosti redoslijeda (jedan piše, drugi čita), ili ako koristite OpenAI-jev strict: true i pouzdanost sheme vam je važnija od latencije.
  • Uključite ga za čiste read-only lookup alate, batch pretragu (weather, currency, tržišni podaci) i sve gdje su alati stvarno neovisni.

Sintaksa se razlikuje po dobavljaču. OpenAI:

client.chat.completions.create(
    model="gpt-5.4",
    messages=messages,
    tools=[openai_tool],
    parallel_tool_calls=False,  # top-level parametar
    tool_choice="auto",
)

Claude (pažljivo, to je unutar tool_choice objekta):

client.messages.create(
    model="claude-sonnet-5",
    messages=messages,
    tools=[claude_tool],
    tool_choice={
        "type": "auto",
        "disable_parallel_tool_use": True,  # ovdje, ne na top-levelu
    },
)

Ova diskrepanca je izvor mnogih tihih bugova. Iskreno, ja sam sam u zadnjem projektu izgubio pola dana debugiranja jer sam automatski očekivao top-level flag na Claudeu. Ako pišete apstrakciju iznad oba dobavljača, testirajte oba puta. Za detaljan pregled Anthropic-ovog parametra, službena je referenca u Claude Platform Docs – Parallel tool use.

Rukovanje greškama i retry obrasci

Postoje četiri klase grešaka koje morate rukovati. Sve se ponašaju drugačije:

  1. Model refuse. OpenAI vraća refusal polje u odgovoru, Claude vraća tekstualni odgovor koji objašnjava zašto neće pozvati alat. Ne pokušavajte retry; pošaljite fallback poruku korisniku.
  2. Truncation (finish_reason: "length"). Argumenti nisu potpuni. Povećajte max_tokens ili razložite alat na manje.
  3. Schema violation. Sa strict: true (OpenAI) ne događa se; bez toga, validirajte s Pydantic-om i vratite model natrag s porukom o grešci (retry s eksplicitnim opisom pogreške).
  4. Tool execution error. Vratite grešku kao tool_result s is_error: true (Claude) ili prefiksom u sadržaju (OpenAI). Model će se često ispraviti sam.
from pydantic import ValidationError

def safe_execute(block):
    try:
        args = SearchOrdersArgs(**block.input)   # Pydantic validacija
        result = search_orders_impl(**args.model_dump())
        return {"type": "tool_result", "tool_use_id": block.id,
                "content": str(result)}
    except ValidationError as e:
        return {"type": "tool_result", "tool_use_id": block.id,
                "content": f"Argument validation failed: {e.errors()}. "
                           f"Please retry with corrected arguments.",
                "is_error": True}
    except Exception as e:
        return {"type": "tool_result", "tool_use_id": block.id,
                "content": f"Tool execution failed: {type(e).__name__}: {e}",
                "is_error": True}

Kako evaluirati pozive alata prije produkcije

Ako pratite moj rad, znate da nemam mišljenje o tome, nego čvrsto mišljenje: function calling bez eval pipeline-a je alfa verzija. Većina timova radi jedan sloj evaluacije (schema pass/fail) i onda se čude zašto agent u produkciji radi budalaštine. Treba vam tri sloja.

Sloj 1: Schema validation (unit test)

Pošaljite 30-100 zlatnih poruka i provjerite: pozove li se ispravan alat, pass-a li output JSON-Schema validaciju, jesu li svi required parametri prisutni. Ovo je jeftino i mora biti u CI.

Sloj 2: Argument correctness (LLM-as-judge)

Za svaki uspješan poziv alata, drugi model (ili ljudski recenzent) ocjenjuje jesu li argumenti semantički ispravni. Primjerice, je li customer_id zaista onaj koji je korisnik spomenuo. Ovdje se najviše skrivaju greške. Za detaljan setup s DeepEval i RAGAS-om, pogledajte naš vodič o eval cjevovodima s DeepEval, RAGAS i Langfuseom.

Sloj 3: End-to-end task success

Definirajte "je li korisnikov problem riješen na kraju konverzacije?" i mjerite u produkciji preko trace-ova. OpenAI-jev službeni vodič o function calling-u preporučuje ovaj sloj kao ključni KPI.

# Primjer eval funkcije za DeepEval
from deepeval.metrics import BaseMetric
from deepeval.test_case import LLMTestCase

class ToolCallCorrectness(BaseMetric):
    def __init__(self, expected_tool: str, expected_args: dict):
        self.expected_tool = expected_tool
        self.expected_args = expected_args
        self.threshold = 1.0

    def measure(self, test_case: LLMTestCase) -> float:
        tool_calls = test_case.actual_output  # lista dict-ova
        if not tool_calls:
            self.score, self.reason = 0.0, "Nijedan alat nije pozvan"
            return self.score
        first = tool_calls[0]
        if first["name"] != self.expected_tool:
            self.score = 0.0
            self.reason = f"Ocekivan {self.expected_tool}, dobio {first['name']}"
            return self.score
        for key, expected in self.expected_args.items():
            if first["arguments"].get(key) != expected:
                self.score = 0.5
                self.reason = f"Krivi argument za {key}"
                return self.score
        self.score, self.reason = 1.0, "OK"
        return self.score

    def is_successful(self) -> bool:
        return self.score >= self.threshold

    @property
    def __name__(self):
        return "ToolCallCorrectness"

Za advanced obrasce (MCP kao izvor alata, dinamičko učitavanje tool-ova, memorija stanja između poziva) preporučujem naš vodič o izgradnji MCP servera s FastMCP-om, koji pokazuje kako alate exposati na standardizirani način. Vrijedi se upoznati i s OpenAI-jevom objavom strukturiranih izlaza i pratećom dokumentacijom kako biste razumjeli garancije modela.

Anti-obrazac: "Radi u playground-u"

Ako je jedini test bio ručno klikanje u OpenAI ili Anthropic playground-u, nemate eval. Playground testovi imaju N=1, nema regresija, nema pokrivenosti edge case-ova. Bez tri sloja gore, ne znate. Samo se nadate.

Često postavljana pitanja

Koja je razlika između function calling i tool use?

To su različiti nazivi za isti koncept. OpenAI je originalno koristio "function calling", a od 2024. preimenovao je API pojam u "tools" kako bi obuhvatio i buduće tipove (npr. code interpreter). Anthropic koristi "tool use" od početka. Semantika je identična: model vraća strukturirani zahtjev za pozivanjem imenovane funkcije s argumentima.

Mogu li koristiti isti kod alata za OpenAI i Claude?

Da, i preporučujem to preko schema-first pristupa: definirajte jednu Pydantic ili Zod shemu, generirajte OpenAI i Claude formate iz nje. Razlike su na razini omotača (naziv polja parameters vs input_schema, prisutnost strict). Poslovna logika alata ostaje ista. LiteLLM ili sličan proxy automatizira konverziju.

Zašto moj model neprestano zove pogrešan alat?

Skoro uvijek je kriva shema, ne model. Provjerite tri stvari: (1) je li description alata jasan i razlikuje se od drugih alata; (2) preklapaju li se enum vrijednosti između alata; (3) je li primjer korištenja u system promptu. Ako i dalje pada, jasno napišite u sistemskoj poruci kada ne koristiti alat; model bolje razumije negacije nego što biste očekivali.

Je li strict mode uvijek najbolja opcija?

Ne. Strict mode kod OpenAI-ja isključuje paralelno pozivanje alata i uvodi latenciju pri prvoj upotrebi sheme (kompilacija). Ako imate mnogo dinamičkih shema koje se rijetko koriste, latencija hita može biti primjetna. Za većinu produkcijskih agenata, koristi (nulta shema kršenja) nadmašuju troškove, ali izmjerite prije nego što uključite globalno.

Kako pratiti troškove function calling-a u produkciji?

Definicije alata (JSON Sheme) šalju se u svakoj poruci i broje se kao input tokeni. Za agenta s 20+ alata, to može biti 5000+ tokena po zahtjevu. Rješenja: (1) tool search / lazy loading, gdje model najprije bira relevantan podskup; (2) prompt caching za statičke definicije alata; (3) MCP server kao dinamički registar. Redoviti audit prompt trace-ova otkriva 90% pretjeranih troškova.

Daichi Watanabe
O Autoru Daichi Watanabe

LLM integration specialist with a strong opinion about function calling and an even stronger one about evaluations.