Pierwsze kroki
Przewodnik: agenci wywołujący narzędzia
Zbuduj pętlę agenta z równoległymi wywołaniami narzędzi, limitem kroków, ustrukturyzowaną odpowiedzią końcową i kontrolą kosztów.
Agent to pętla: model czyta rozmowę, decyduje o wywołaniu jednego lub kilku Twoich narzędzi, Ty je uruchamiasz i odsyłasz wyniki, a model kontynuuje, dopóki nie może odpowiedzieć. api14 obsługuje to w formacie OpenAI tools na każdym modelu z obsługą narzędzi, więc ten sam kod działa z OpenAI, Anthropic, Google i innymi dostawcami po zmianie identyfikatora modelu.
Ten przewodnik buduje małego agenta z limitem kroków, równoległym wykonywaniem narzędzi, ustrukturyzowaną odpowiedzią końcową i kontrolą kosztów. Kod używa oficjalnego pakietu openai z bazowym adresem URL https://api14.com/api/v1.
Zdefiniuj narzędzia
Każde narzędzie to nazwa, opis czytany przez model i JSON Schema jego argumentów. Pisz konkretne opisy; to jedyna dokumentacja, jaką ma model.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api14.com/api/v1", api_key=os.environ["API14_API_KEY"])
def search_orders(customer_email: str) -> list:
return [{"id": "A-1001", "status": "shipped"}] # your database query
def get_shipment(order_id: str) -> dict:
return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"} # your API call
TOOLS = {"search_orders": search_orders, "get_shipment": get_shipment}
tools = [
{"type": "function", "function": {
"name": "search_orders",
"description": "List a customer's orders with their status.",
"parameters": {"type": "object", "properties": {"customer_email": {"type": "string"}},
"required": ["customer_email"]},
}},
{"type": "function", "function": {
"name": "get_shipment",
"description": "Carrier and estimated delivery date for one order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}},
]
Pętla z limitem kroków
Każda iteracja to osobne, rozliczane żądanie, więc zawsze ograniczaj liczbę kroków. Błędy narzędzi zwracaj modelowi jako treść zamiast przerywać działanie; modele zwykle radzą sobie, poprawiając argumenty.
MAX_STEPS = 8
def run_tool(call):
try:
args = json.loads(call.function.arguments)
result = TOOLS[call.function.name](**args)
except Exception as e: # bad JSON, unknown tool, or your function failed
result = {"error": str(e)}
return {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}
def agent(task, model="openai/gpt-5-mini"):
messages = [
{"role": "system", "content": "You are a support agent. Use tools to look things up. Be brief."},
{"role": "user", "content": task},
]
spent = 0.0
for step in range(MAX_STEPS):
r = client.chat.completions.create(model=model, messages=messages, tools=tools, max_tokens=1000)
spent += r.usage.cost
msg = r.choices[0].message
if not msg.tool_calls:
return msg.content, spent
messages.append(msg)
messages.extend(run_tool(call) for call in msg.tool_calls)
raise RuntimeError(f"No answer after {MAX_STEPS} steps (spent ${spent:.4f})")
print(agent("Where is my order? My email is ada@example.com"))
Wysyłaj tools w każdym żądaniu pętli. Wielu dostawców odrzuca wiadomość tool, gdy żądanie nie zawiera definicji narzędzi.
Równoległe wywołania narzędzi
Przy parallel_tool_calls (domyślnie włączonym tam, gdzie jest obsługiwane) model może zażądać kilku wywołań w jednej turze, na przykład get_shipment dla trzech zamówień. Uruchom je współbieżnie i dołącz po jednej wiadomości tool na wywołanie, w dowolnej kolejności; tool_call_id łączy każdy wynik z jego wywołaniem. W Node:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api14.com/api/v1", apiKey: process.env.API14_API_KEY });
const MAX_STEPS = 8;
async function agent(messages, tools, impl, model = "openai/gpt-5-mini") {
for (let step = 0; step < MAX_STEPS; step++) {
const res = await client.chat.completions.create({ model, messages, tools, max_tokens: 1000 });
const msg = res.choices[0].message;
if (!msg.tool_calls?.length) return msg.content;
messages.push(msg);
const results = await Promise.all(msg.tool_calls.map(async (call) => {
let content;
try {
content = JSON.stringify(await impl[call.function.name](JSON.parse(call.function.arguments)));
} catch (err) {
content = JSON.stringify({ error: String(err) });
}
return { role: "tool", tool_call_id: call.id, content };
}));
messages.push(...results);
}
throw new Error(`No answer after ${MAX_STEPS} steps`);
}
Jeśli Twoje narzędzia mają skutki uboczne, które muszą zachodzić po kolei (utwórz, potem zaktualizuj), ustaw parallel_tool_calls: false, aby model prosił o jedno wywołanie na turę.
Ustrukturyzowana odpowiedź końcowa
Gdy odpowiedź agenta zasila inny kod, poproś o JSON zgodny ze schematem. Prosty, przenośny wzorzec to jedno dodatkowe wywołanie po tym, jak pętla zbierze fakty (messages to tutaj rozmowa z pętli; zwróć ją z agent() razem z odpowiedzią):
schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"status": {"type": "string", "enum": ["processing", "shipped", "delivered", "unknown"]},
"eta": {"type": ["string", "null"]},
"reply": {"type": "string"},
},
"required": ["order_id", "status", "eta", "reply"],
"additionalProperties": False,
}
final = client.chat.completions.create(
model="openai/gpt-5-mini",
messages=messages + [{"role": "user", "content": "Summarize the result for our ticket system."}],
tools=tools,
tool_choice="none",
response_format={"type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}},
)
ticket = json.loads(final.choices[0].message.content)
tool_choice: "none" zostawia tools w żądaniu (jest to potrzebne, bo historia zawiera wiadomości narzędzi), a jednocześnie blokuje kolejne wywołania. Zawsze waliduj sparsowany JSON. Zobacz Ustrukturyzowane wyjście.
Wybór modelu
- Możliwości: model musi mieć
toolswsupported_parameters. Sprawdź to przezGET /models, zobacz Modele. Dla kroku końcowego szukajstructured_outputslubresponse_format. - Routing: dodaj
provider: { "require_parameters": true }(w Pythonie przezextra_body), aby żądania trafiały tylko do dostawców obsługujących każdy wysyłany parametr. Zobacz Routing dostawców. - Rozmiar: małe modele, takie jak
openai/gpt-5-mini, niezawodnie i tanio obsługują kilka dobrze opisanych narzędzi. Przejdź na większy model, gdy agent ma wiele narzędzi, długie wieloetapowe plany lub wciąż wybiera złe narzędzie. - Testuj na swoich narzędziach: uruchom te same 20–50 prawdziwych zadań na dwóch lub trzech kandydatach i porównaj skuteczność, liczbę kroków na zadanie i łączny
usage.cost.
curl -s "https://api14.com/api/v1/models?category=text" \
| jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id) \(.pricing.prompt)"'
Kontrola kosztów
Agent może wykonać wiele żądań dla jednego zadania, a cała rozmowa jest wysyłana ponownie w każdym kroku. Ogranicz wydatki na kilku poziomach:
- Limit kroków (
MAX_STEPS) imax_tokensna żądanie, jak powyżej. - Budżet na zadanie: sumuj
usage.costze wszystkich kroków i zatrzymaj się, gdy przekroczy Twój próg. - Krótkie wyniki narzędzi: zwracaj tylko pola, których model potrzebuje. Duże wyniki narzędzi są opłacane jako tokeny wejściowe w każdym kolejnym kroku.
- Dzienny limit USD na klucz: utwórz dedykowany klucz dla agenta w kluczach API i ustaw dzienny limit. Po jego osiągnięciu żądania zwracają
429 daily_limit_reacheddo następnego dnia UTC; obsłuż ten kod, zatrzymując agenta, a nie ponawiając. - Lista dozwolonych modeli: ogranicz klucz do przetestowanych modeli; wszystko inne zwraca
403 model_not_allowed. - Cache promptów: utrzymuj prompt systemowy i definicje narzędzi identyczne i na początku każdego żądania, aby zadziałały zniżki za prefiks z cache. Zobacz Cache promptów.
Zobacz Wywoływanie narzędzi, Limity zapytań i Błędy.