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 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čajka
OpenAI (2026)
Anthropic Claude (2026)
Jamstvo sheme
strict: true, na razini decodera
Nema formalnog "strict"; oslonac na trening + validaciju
Paralelno pozivanje alata
Podržano, ali isključuje strict jamstva
Podržano prema zadanoj postavci; zadržava jamstva
Isključivanje paralelizma
parallel_tool_calls: false (top-level)
tool_choice.disable_parallel_tool_use: true
tool_choice načini
auto, required, none, {"type":"function"}
auto, any, tool, none
Nepodržana ograničenja JSON Scheme
pattern, format, min/maxLength, min/maximum
Sličan skup, ali manje formalno dokumentirano
Extended thinking + tool use
Reasoning modeli podržavaju sve tool_choice tipove
Samo auto i none uz extended thinking
Token-učinkovito korištenje alata
Ugrađeno u GPT-5.x
Ugrađ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:
Uska polja pobjeđuju široka polja.enum uvijek kad je moguće; const za polja koja stvarno imaju samo jednu ispravnu vrijednost; strogi type.
Sve polja su required; opcionalnost radite preko ["string", "null"]. Ovo je zahtjev OpenAI strict mode-a i sjajna disciplina inače.
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.
Claude (pažljivo, to je unutartool_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:
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.
Truncation (finish_reason: "length"). Argumenti nisu potpuni. Povećajte max_tokens ili razložite alat na manje.
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).
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.
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.
Praktični vodič za izgradnju MCP servera u Pythonu s FastMCP okvirom: Tools, Resources, Prompts, Streamable HTTP transport, OAuth 2.1 autentikacija i sigurnost u produkciji.
Praktični vodič za A2A (Agent2Agent) protokol u Pythonu — od Agent Card i Task životnog ciklusa, preko usporedbe s MCP-om, do implementacije servera i klijenta s primjerima za Google ADK i FastA2A.
Naučite kako implementirati dugoročnu memoriju za AI agente u Pythonu koristeći Mem0. Praktični vodič s primjerima koda, arhitekturnim obrascima i usporednom analizom Mem0, Zep i Letta okvira za memoriju agenata u 2026.