NL ▾

OpenAI-compatible ongedecatureerde LLM APIuncensoredapis.com

HomeAgents

Agents op een Ongecatureerde LLM API: Tool Calling, Frameworks en Guardrails

Een agent is een lus: het model vraagt om een functie, jouw code voert deze uit, en het resultaat gaat terug in het gesprek. Omdat de Ongecatureerde LLM API tool calling spreekt in het standaard chat formaat, kun je die lus in veertig regels bouwen, en dezelfde endpoint koppelen aan LangChain of LlamaIndex. Deze gids behandelt beide, plus de checks die tool execution veilig houden.

Bijgewerkt

Belangrijkste punten

  1. Tools meegeven in het verzoek, let op finish_reason gelijk aan tool_calls, functies uitvoeren, tool berichten toevoegen, en herhaal totdat het model in tekst antwoordt.
  2. LangChain vereist ChatOpenAI met base_url; LlamaIndex vereist OpenAILike met api_base en de function-calling flag ingesteld.
  3. Het model stelt calls voor; jouw code beslist of ze draaien. Allowlist, valideren, time-limit en cap de lus.
  4. Agents vermenigvuldigen request counts, dus budget voor de 300 requests per minute limit en de 100.000-token context.

Anatomie van de tool-calling lus

Haal frameworks weg en elke agent doet dezelfde vier dingen. Je stuurt het gesprek plus een tools-lijst. Het model antwoordt ofwel met tekst, wat betekent dat het klaar is, of met tool_calls, wat betekent dat het iets wil uitvoeren. Je voert elke call uit en voegt per call één tool-bericht toe, met de id van de call. Vervolgens vraag je het model opnieuw met het langere gesprek.

Het signaal voor de branch is finish_reason. Wanneer het gelijk is aan tool_calls, heeft het assistant bericht content ingesteld op null en een tool_calls array, elke entry houdt een id, een functienaam en een arguments string van JSON. De reference page toont de exacte vorm. Twee details brengen mensen op het verkeerde been: de arguments zijn een string die geparsed moet worden, en het assistant bericht met de calls moet in de history blijven voor je tool results, in volgorde.

Een complete lus vanaf nul

Het script hieronder definieert één functie, beschrijft het in JSON schema, en loopt totdat het model platte tekst produceert. Het gebruikt de officiële Python SDK die wijst naar de API. Elke pass voegt het assistant bericht toe, dan een tool bericht per call, en een step counter stopt run-away gedrag.

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.uncensoredapis.com/v1", api_key=os.environ["API_KEY"])

TICKETS = {"T-101": "open", "T-102": "closed"}

def ticket_status(ticket_id: str) -> str:
    return TICKETS.get(ticket_id, "unknown ticket")

TOOLS = [{
    "type": "function",
    "function": {
        "name": "ticket_status",
        "description": "Look up the status of a support ticket by id.",
        "parameters": {
            "type": "object",
            "properties": {"ticket_id": {"type": "string"}},
            "required": ["ticket_id"],
        },
    },
}]
REGISTRY = {"ticket_status": ticket_status}

messages = [{"role": "user", "content": "Are tickets T-101 and T-102 still open?"}]

for step in range(6):                      # hard cap on loop turns
    r = client.chat.completions.create(
        model="uncensored", messages=messages, tools=TOOLS, max_tokens=400)
    msg = r.choices[0].message
    messages.append(msg.model_dump(exclude_none=True))
    if r.choices[0].finish_reason != "tool_calls":
        print(msg.content)
        break
    for call in msg.tool_calls:
        fn = REGISTRY.get(call.function.name)
        try:
            args = json.loads(call.function.arguments)
            result = fn(**args) if fn else f"error: unknown tool {call.function.name}"
        except Exception as exc:
            result = f"error: {exc}"
        messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
else:
    print("stopped: step limit reached")

Let op wat de lus doet bij een fout: foutmeldingen worden stringresultaten van tools. Hierdoor kan het model het probleem lezen en het opnieuw proberen, bijvoorbeeld door een verkeerde ticket-id te corrigeren, in plaats van dat je programma crasht. De for ... else-clausule wordt alleen geactiveerd als de limiet wordt bereikt zonder een definitief antwoord, wat aangeeft dat het model in een cirkel van herhalende fouten zit.

Houd prompts gefocust. Een kort system message dat aangeeft wanneer een tool aan te roepen en wanneer direct te antwoorden, reduceert onnodige calls, en elke overgeslagen call bespaart een round trip, tokens en één slot van je rate limit.

Tool execution bewaken

Model output is untrusted input. Een tool name of argument gekozen door het model kan fout, vreemd, of beïnvloed door tekst die het model van een webpagina of gebruiker las. Behandel elke call als een request van het publieke internet.

  • Allowlist by name. Zoek de functie op in een registry die je hebt gebouwd. Gebruik de string nooit om code te importeren of evalueren.
  • Scheid read van write. Read-only tools draaien vrijuit. Alles dat mail verstuurt, geld uitgeeft, data verwijdert of bestanden raakt, vereist expliciete menselijke goedkeuring.
  • Valideer argumenten tegen het schema dat je hebt gepubliceerd: vereiste sleutels, geen extra sleutels, typen en lengtelimieten.
  • Limiteer tijd en output. Pas time-outs toe en knip resultaten af voordat je ze terugkoppelt, zodat een enorme tool-output je contextvenster van 100.000 tokens niet opvreet.
  • Limiteer iteraties en kosten. Beperk stappen per taak en tokens per sessie, met behulp van de usage-blok.
  • Log elke call met name, arguments en outcome zodat je gedrag kunt auditen.
import json
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutTimeout

ALLOWED = {"ticket_status", "lookup_invoice"}      # read-only tools only
NEEDS_APPROVAL = {"refund_order"}                  # side effects wait for a human
POOL = ThreadPoolExecutor(max_workers=4)

def validate(args: dict, schema: dict) -> None:
    props = schema["properties"]
    for key in schema.get("required", []):
        if key not in args:
            raise ValueError(f"missing argument: {key}")
    for key, val in args.items():
        if key not in props:
            raise ValueError(f"unexpected argument: {key}")
        if props[key]["type"] == "string" and not isinstance(val, str):
            raise ValueError(f"{key} must be a string")
        if isinstance(val, str) and len(val) > 200:
            raise ValueError(f"{key} too long")

def run_tool(name, raw_args, registry, schemas, approve=lambda n, a: False):
    if name in NEEDS_APPROVAL:
        args = json.loads(raw_args)
        if not approve(name, args):
            return "denied: this action needs human approval"
    elif name not in ALLOWED:
        return f"error: tool {name} is not permitted"
    args = json.loads(raw_args)
    validate(args, schemas[name]["parameters"])
    future = POOL.submit(registry[name], **args)
    try:
        return str(future.result(timeout=5))[:2000]    # time and size limits
    except FutTimeout:
        return "error: tool timed out"

Behandel tool results ook als untrusted. Als een tool scraped tekst retourneert met instructies, kan het model ze volgen. Wikkel zo'n content in duidelijke delimiters in het tool bericht en geef in je system prompt aan dat tool output data is, geen commands.

Tool schemas schrijven die het model kan gebruiken

Het schema is de enige documentatie die het model ziet, dus het verdient dezelfde zorg als een publieke API. Noem functies met werkwoorden en zelfstandige naamwoorden die bij de taak passen, zoals ticket_status of lookup_invoice, in plaats van generieke labels zoals run of helper. Schrijf de description als een instructie: wat de functie doet, wat het retourneert, en wanneer je het niet moet gebruiken.

  • Houd parameters weinig en plat. Twee of drie eenvoudige velden worden veel vaker correct aangeroepen dan een genest object met tien.
  • Gebruik enums voor gesloten sets, bijvoorbeeld "enum": ["open", "closed"], zodat het model geen waarde kan verzinnen.
  • Beschrijf elke property met een unit of voorbeeld: "Ticket id zoals T-101" is beter dan een kale string.
  • Retourneer compacte resultaten. Strings of kleine JSON objecten werken het beste; lange dumps verbruiken context voor weinig winst.
  • Voorkeur aan meerdere smalle tools boven één omnibus tool met een mode flag.

Wanneer een call validatie faalt, retourneer het validatie bericht als het tool result in plaats van raising. Het model repareert de call meestal in de volgende turn, omdat het kan lezen wat er mis was. Als het op dezelfde manier twee keer faalt, herschrijf de description, niet de lus.

LangChain configuratie

LangChain's ChatOpenAI class accepteert een base_url, dus geen custom provider package nodig. Stel model in op uncensored, geef de sleutel uit je environment door, en bind tools met bind_tools. Installeer langchain-openai en langchain-core.

import os
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

@tool
def ticket_status(ticket_id: str) -> str:
    """Return the status of a support ticket."""
    return {"T-101": "open", "T-102": "closed"}.get(ticket_id, "unknown ticket")

llm = ChatOpenAI(
    model="uncensored",
    base_url="https://api.uncensoredapis.com/v1",
    api_key=os.environ["API_KEY"],
    temperature=0.3,
    max_tokens=400,
)
llm_with_tools = llm.bind_tools([ticket_status])

ai = llm_with_tools.invoke("What is the status of ticket T-101?")
for call in ai.tool_calls:
    print(call["name"], call["args"], "->", ticket_status.invoke(call["args"]))

Het resultaat van invoke is een AIMessage waarvan de lijst tool_calls geparseerde namen en argumenten bevat, zodat je de handmatige JSON-stap overslaat. Om een volledig autonome lus te krijgen, geef je dezelfde llm-object aan de agentconstructors van LangChain; het endpoint hoeft alleen function calling te ondersteunen, wat het doet. Als een agent zijn tools lijkt te negeren, controleer dan of de docstring en type hints aanwezig zijn, omdat deze het schema vormen dat het model ziet.

LlamaIndex configuratie

LlamaIndex levert OpenAILike voor servers die het chat-completiesformaat volgen. Drie instellingen zijn belangrijk. api_base wijst naar de API, is_chat_model=True selecteert de chatroute, en is_function_calling_model=True vertelt het framework dat het tools mag sturen. Stel context_window in op 100000 zodat de tekstsplitters en geheugenbuffers zich correct dimensioneren. Installeer llama-index-llms-openai-like.

import os
from llama_index.llms.openai_like import OpenAILike
from llama_index.core.tools import FunctionTool

def ticket_status(ticket_id: str) -> str:
    """Return the status of a support ticket."""
    return {"T-101": "open", "T-102": "closed"}.get(ticket_id, "unknown ticket")

llm = OpenAILike(
    model="uncensored",
    api_base="https://api.uncensoredapis.com/v1",
    api_key=os.environ["API_KEY"],
    is_chat_model=True,
    is_function_calling_model=True,
    context_window=100000,
    max_tokens=400,
)

tool = FunctionTool.from_defaults(fn=ticket_status)
response = llm.predict_and_call([tool], "Check ticket T-102 for me.")
print(response)

Vanaf hier kun je dezelfde llm aan een agent worker of query engine meegeven. Onthoud dat retrieval pipelines veel chunks in één prompt kunnen proppen; met een 100,000-token window gedeeld tussen prompt en completion, cap het aantal opgehaalde chunks en houd max_tokens bescheiden.

Agents beheren op budget

Een agent zet één gebruikersbericht om in meerdere verzoeken, dus plan je capaciteit. Een taak met vijf tool-rondes kost zes modelaanroepen. Tien gelijktijdige taken produceren dus zestig verzoeken in korte tijd, en de limiet van 300 verzoeken per minuut wordt eerder bereikt dan in een chat-app. Plaats de token-bucket-throttle uit het handboek over fouten en rate limits voor je client.

Context groeit met elke round omdat elk tool result wordt toegevoegd. Samenvatten of oude tool outputs verwijderen zodra ze zijn gebruikt, en houd een oog op usage.prompt_tokens; wanneer het 50,000 nadert, comprimeer history. Voor kosten, neem een illustratieve taak van 6 calls gemiddeld 3,000 prompt tokens en 200 completion tokens: dat is 18,000 input tokens (ongeveer $0,0045 bij $0,25 per miljoen) en 1.200 output tokens (ongeveer $0,0012 bij $1,00 per miljoen). Die token counts zijn aannames voor de sommen, geen metingen. Als je nieuw bent bij de endpoint, begin met de docs en het gratis proeftegoed.

Een agent testen en debuggen

Agents falen op manieren die unit tests op een enkele functie missen, dus test het gesprek. Noteer de volledige message list voor elke run, inclusief tool arguments en results, en replay failures uit de log. Drie gewoontes betalen snel uit.

  1. Fake het model. Stub de client zodat het scripted tool_calls berichten retourneert. Dit test je registry, validatie en lus terminatie zonder netwerk en zonder spend.
  2. Vastzetten van sampling. Gebruik een lage temperature, zoals 0,2, voor tool-gestuurde taken. Minder willekeur zorgt voor meer reproduceerbare argument-JSON, en je kunt dit later verhogen voor tekstzware stappen.
  3. Controleer de trajecten. Controleer voor een handvol vaste prompts welke tools zijn aangeroepen en in welke volgorde, niet alleen de eindtekst.

Hou ook finish_reason in de gaten. Een waarde van length in het midden van een toolaanroep betekent dat de argumenten zijn afgekapt; verhoog max_tokens (tot 32.000) en de JSON wordt compleet. Streaming werkt ook met tools, maar verzamel argumentfragmenten per call-index en parseer pas nadat de stream is gesloten. Voor een herhaling van die fragmenten, zie de streaming-sectie van de referentie.

Vragen en antwoorden

Ondersteunt de API gelijktijdige toolaanroepen in één antwoord?

Het bericht bevat een tool_calls-array, dus verwerk elk item daarin en stuur één tool-bericht per call-id.

Waarom loopt mijn agent in een oneindige lus?

Meestal zijn de toolfouten niet informatief of zegt het systeem-prompt nooit wanneer je moet stoppen. Voeg een staplimiet toe en retourneer duidelijke foutstrings.

Heb ik LangChain of LlamaIndex nodig?

Nee. Een simpele lus met de OpenAI SDK is genoeg, en frameworks voegen alleen orkestratie toe bovenop dezelfde endpoint.

Hoe voorkom ik dat het model destructieve tools uitvoert?

Registreer ze nooit zonder een goedkeuringsstap. Alleen de functies in jouw registry kunnen draaien, dus houd schrijfacties achter menselijke bevestiging.

Je sleutel is nog maar één formulier verwijderd

Maak een account aan, kopieer de sleutel, pas de base URL aan. Dat is de hele setup.