NL ▾
API-sleutel aanvragen

GLM API Python: voorbeelden voor chatten, streaming en tools

Voor GLM API Python-werk heb je alleen het standaard openai-pakket nodig: wijs het naar https://api.glmapi.cc/v1, gebruik de model ID uncensored en de gebruikelijke chat-aanroepen werken. Deze pagina doorloopt elk patroon met code die je kunt uitvoeren, van een eerste verzoek tot asynchrone batches en rate-limit afhandeling.

Belangrijkste punten

  • Installeer openai, stel base_url in op https://api.glmapi.cc/v1 en gebruik model uncensored.
  • Streaming, JSON-modus en toolaanroepen gebruiken dezelfde parameters als de OpenAI chat API.
  • Een volledige sleutel staat 8 gelijktijdige verzoeken en 300 per minuut toe, dus beperk je asynchrone workers tot 8.
  • Handel 429-fouten af door de foutcode en de Retry-After-header te lezen.
Lint van licht streamende gegevensblokken van een laptopterminal naar een cloudserver, dat Python API-aanroepen voorstelt

Installeren en eerste verzoek

Maak een virtuele omgeving aan en installeer de SDK. Voor de openai package wordt versie 1 of hoger aangenomen.

python -m venv .venv
source .venv/bin/activate
pip install --upgrade openai

Haal een sleutel op van de API-sleutelpagina; nieuwe gebruikers kunnen met één klik een proeftegoed van $0,50 claimen, geldig voor 7 dagen en zonder kaart. Bewaar de sleutel in een omgevingsvariabele en maak vervolgens het eerste verzoek:

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.glmapi.cc/v1",
    api_key=os.environ["GLM_API_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    messages=[
        {"role": "system", "content": "You are a concise writing assistant."},
        {"role": "user", "content": "Suggest five titles for a space-western novel."},
    ],
    temperature=0.8,
)

print(resp.choices[0].message.content)
print(resp.usage)

Dat is de hele integratie. De service is een onafhankelijk, OpenAI-compatibel endpoint voor één open-weight model, dus het is niet de officiële API van de leverancier achter GLM of van iemand anders. Alleen POST /v1/chat/completions en GET /v1/models bestaan. De quickstart somt de ondersteunde verzoekvelden op.

Twee kleine gewoontes helpen vanaf het begin. Roep eerst client.models.list() één keer aan in een smoke test: dit roept GET /v1/models aan, zodat je bewijst dat de sleutel, de basis-URL en het netwerkpad werken voordat je een enkel token uitgeeft voor een completion. Print daarnaast resp.usage tijdens de ontwikkeling. Het aantal tokens voor de prompt en de completion laat je vroeg zien of een system prompt groter is dan je dacht, en tegen $0,15 per 1M input en $0,50 per 1M output kun je de kosten van een call in je hoofd berekenen.

Chat met meerdere beurten en sampling-instellingen

De API is statusvrij, dus je beheert de gesprekslijst zelf en stuurt deze elke keer mee. Een kleine klasse is genoeg:

class Chat:
    def __init__(self, client, system, max_turns=12):
        self.client = client
        self.system = system
        self.max_turns = max_turns
        self.history = []

    def say(self, text):
        self.history.append({"role": "user", "content": text})
        window = self.history[-self.max_turns * 2:]
        resp = self.client.chat.completions.create(
            model="uncensored",
            messages=[{"role": "system", "content": self.system}] + window,
            temperature=0.9,
            top_p=0.95,
            frequency_penalty=0.3,
        )
        reply = resp.choices[0].message.content
        self.history.append({"role": "assistant", "content": reply})
        return reply

De slice self.history[-self.max_turns * 2:] houdt de laatste twaalf gebruikers- en assistentparen bij. Een echte applicatie kan ook een paar vroege berichten vastpinnen, zoals een personagebeschrijving of belangrijke feiten, en het midden samenvatten. Het bijsnijden tot de laatste 12 beurten houdt de invoerkosten gelijk terwijl het gesprek groeit. Het contextvenster is 100.000 tokens voor prompt en voltooiing samen, dus dit is een kostenkeuze meer dan een noodzaak. De parameters temperature, top_p, stop, seed en de straffen worden doorgegeven. Stel seed in als je herhaalbare runs wilt tijdens het testen van een prompt, en laat max_tokens weg tenzij je de lengte wilt beperken.

Streaming-antwoorden

Streaming gebruikt server-sent events en geeft je tokens zodra ze gegenereerd zijn, wat belangrijk is voor elke interactieve tool. Geef stream=True door en itereer:

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Describe a harbour at dawn in 120 words."}],
    stream=True,
)

parts = []
for chunk in stream:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta.content
    if delta:
        parts.append(delta)
        print(delta, end="", flush=True)

text = "".join(parts)

De guard op chunk.choices is belangrijk omdat het laatste stukje gebruiksinformatie bevat en een lege choices-lijst kan hebben. Een operationeel punt: een gestreamd verzoek houdt zijn concurrency-slot vast tot de stream eindigt. Wikkel de lus in een try-blok met een finally-clausule die de stream sluit als je code halverwege een fout geeft, bijvoorbeeld wanneer een client de verbinding verbreekt. Sluit de stream altijd af, anders raakt een lus die streams verlaat de concurrency limit terwijl er geen nuttig werk wordt verricht.

JSON-modus

Als de uitvoer andere code voedt, vraag dan om een JSON-object. Vermeld JSON ook in de prompt, zodat het model weet welke vorm je verwacht:

import json

resp = client.chat.completions.create(
    model="uncensored",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "Reply with a JSON object only."},
        {"role": "user", "content": "Give a character with keys name, age, motto."},
    ],
)

data = json.loads(resp.choices[0].message.content)
print(data["name"])

Wikkel json.loads nog steeds in een try-blok in productiecode. JSON-modus maakt geldige uitvoer waarschijnlijk, en een verzoek dat wordt afgekapt door een stopsequentie of je eigen limiet kan nog steeds een afgekapt object achterlaten. Eenmalig opnieuw proberen met dezelfde invoer is een goedkope oplossing, en omdat mislukte verzoeken niet worden aangerekend, zijn de kosten van een retry alleen de kosten van de geslaagde aanroep.

Function calling

Tool calling volgt de OpenAI-vorm: je beschrijft functies in tools, het model retourneert tool_calls, je voert ze uit en stuurt resultaten terug met de rol tool. Een volledige cyclus:

import json

tools = [{
    "type": "function",
    "function": {
        "name": "roll_dice",
        "description": "Roll a die with the given number of sides.",
        "parameters": {
            "type": "object",
            "properties": {"sides": {"type": "integer"}},
            "required": ["sides"],
        },
    },
}]

messages = [{"role": "user", "content": "Roll a twenty-sided die for me."}]
first = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = first.choices[0].message

if msg.tool_calls:
    messages.append(msg)
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = {"value": 17, "sides": args["sides"]}  # replace with real logic
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result),
        })
    final = client.chat.completions.create(
        model="uncensored", messages=messages, tools=tools
    )
    print(final.choices[0].message.content)
else:
    print(msg.content)

De lus is belangrijker dan de details van de dobbelsteen. Het model vraagt om een aanroep, je code voert deze uit, en je voegt zowel het assistentbericht als het tool-resultaat toe voordat je opnieuw vraagt. Sla je het assistentbericht over, dan heeft de tool_call_id in het resultaat niets om aan te koppelen, wat de gebruikelijke oorzaak is van een verwarde tweede reactie. Met tool_choice ingesteld op auto beslist het model of het überhaupt een tool aanroept, dus behandel altijd de tak waarin het direct antwoordt, zoals de bovenstaande code doet. Gestreamde reacties kunnen ook tool_calls bevatten, geleverd in stukken die je samenvoegt per index voordat je de argumenten parset.

Valideer de argumenten voordat je ze uitvoert. Het model kan een foutieve aanroep produceren, en je code moet de argumenten behandelen als onbetrouwbare invoer, hetzelfde als van een gebruiker.

Asynchrone batches binnen de limieten

Voor veel prompts tegelijk, gebruik AsyncOpenAI met een semaphore die overeenkomt met je concurrency limit. Een volledige sleutel staat 8 gelijktijdige verzoeken toe, een proefsleutel 2:

import asyncio, os
from openai import AsyncOpenAI

client = AsyncOpenAI(
    base_url="https://api.glmapi.cc/v1",
    api_key=os.environ["GLM_API_KEY"],
)
gate = asyncio.Semaphore(8)  # use 2 on a trial key

async def one(prompt):
    async with gate:
        r = await client.chat.completions.create(
            model="uncensored",
            messages=[{"role": "user", "content": prompt}],
        )
        return r.choices[0].message.content

async def main():
    prompts = [f"Write a one-line tagline for product {i}." for i in range(40)]
    results = await asyncio.gather(*(one(p) for p in prompts))
    print(len(results))

asyncio.run(main())

De semaphore behandelt concurrency maar niet de limiet van 300 verzoeken per minuut. Voor lange batches voeg je een kleine vertraging of een token-bucket limiter toe. Bij $0,15 per 1M input en $0,50 per 1M output tokens kosten 40 slogan-verzoeken van ongeveer 60 invoertokens en 25 uitvoertokens $0,00036 + $0,0005 = $0,00086 in totaal, dus de limieten, niet de prijs, bepalen het batchontwerp. Zie de prijspagina voor het opwaarderen van tegoed.

Rate limits, retries en headers

Boven een limiet retourneert de API HTTP 429. De foutcode is rate_limited voor te veel verzoeken per minuut en concurrency voor te veel in één keer. Tijdelijke overbelasting geeft ook 429 met een Retry-After: 5 header. De SDK probeert sommige fouten zelf opnieuw en je kunt max_retries op de client instellen, maar expliciete afhandeling is duidelijker:

import time
from openai import RateLimitError

def ask(prompt, tries=4):
    for attempt in range(tries):
        try:
            raw = client.chat.completions.with_raw_response.create(
                model="uncensored",
                messages=[{"role": "user", "content": prompt}],
            )
            print("balance:", raw.headers.get("X-Balance-USD"))
            return raw.parse().choices[0].message.content
        except RateLimitError as e:
            wait = float(e.response.headers.get("Retry-After", 2 ** attempt))
            time.sleep(wait)
    raise RuntimeError("still rate limited")

Hier geeft with_raw_response toegang tot X-Balance-USD, X-Request-Id en de rate-limit headers. Log de request-ID bij elke fout die je wilt melden. Plaats de sleutel nooit in een notebook dat je commit of in front-end code.

Testen zonder tegoed uit te geven en veelgemaakte fouten

Het grootste deel van een Python integratie is plumbing, en plumbing hoeft geen live model. Houd de netwerkaanroep achter één functie, en je tests kunnen deze vervangen. Alleen met de standaard library:

from unittest.mock import MagicMock

def reply_text(client, prompt):
    r = client.chat.completions.create(
        model="uncensored",
        messages=[{"role": "user", "content": prompt}],
    )
    return r.choices[0].message.content.strip()

def test_reply_text_strips_whitespace():
    fake = MagicMock()
    fake.chat.completions.create.return_value.choices = [
        MagicMock(message=MagicMock(content="  hello  "))
    ]
    assert reply_text(fake, "hi") == "hello"

Voer een handvol echte aanroepen alleen aan de randen uit: één smoke test per deploy, plus een af en toe uitgevoerde run van je opgeslagen promptset. Een enkele smoke test met ongeveer 100 tokens invoer en 20 uitvoer kost $0,000015 + $0,00001 = $0,000025, wat niets is, en mislukte verzoeken worden helemaal niet aangerekend. Het $0,50 proeftegoed is veel meer dan een testsuite nodig heeft.

Voor promptwerk: zet seed en temperature vast zodat je twee versies van een system prompt op gelijke voorwaarden kunt vergelijken. Het veranderen van zowel de prompt als de samplinginstellingen tussen runs maakt de resultaten onleesbaar. Sla de output van elke run op met de request ID van de X-Request-Id header, en je kunt een verrassend antwoord later traceren.

Veelgemaakte Python-fouten

Deze veroorzaken de meeste supportvragen van Python-gebruikers:

  • De sleutel alleen doorgeven als OPENAI_API_KEY. Als je base_url in code instelt maar de sleutelvariabele vergeet, stuurt de SDK een lege of verkeerde sleutel. Geef api_key expliciet door.
  • /v1 vergeten. De base URL eindigt op /v1. De SDK voegt /chat/completions zelf toe.
  • Een modelnaam van een andere service gebruiken. Alleen uncensored is hier geldig.
  • Geen limiet aan gelijktijdige verzoeken. asyncio.gather over duizend coroutines zonder semaphore leidt tot een muur van concurrency-fouten.
  • Een gestreamde reactie behandelen als één string. Voeg de deltas samen en sla stukken over met een lege choices-lijst.
  • De contextlimiet negeren. Geschiedenis plus completion moeten passen in 100.000 tokens. Knip oude beurten voordat je het bereikt.

Voor het opwaarderen van tegoed en het huidige tarievenoverzicht zie je de prijspagina. De service is alleen tekst, dus er zijn geen afbeeldings-, audio- of embedding-aanroepen om aan te sluiten, en prompts worden niet gebruikt voor training.

Vragen en antwoorden

Welk Python-pakket heb ik nodig?

Het standaard openai-pakket, versie 1 of hoger. Stel base_url in op https://api.glmapi.cc/v1 en het model op uncensored.

Werkt streaming?

Ja. Geef stream=True door en doorloop de chunks. De stream eindigt met gebruiksinformatie en neemt een concurrency slot in tot het klaar is.

Hoeveel verzoeken kan ik parallel uitvoeren?

Een volledige sleutel staat 8 gelijktijdige verzoeken en 300 per minuut toe. Een proefsleutel staat 2 en 60 toe.

Kan ik in plaats daarvan LangChain of LiteLLM gebruiken?

Beide kunnen communiceren met een OpenAI-compatibel endpoint als je ze de base URL, de sleutel en de modelnaam uncensored geeft. Controleer de documentatie van elk project voor de exacte parameternamen.

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.

API-sleutel ophalenDocumentatie lezen