Chat
Chat is the core of Pendra: send a list of messages, get a reply. It
powers assistants, summarisation, classification, code generation, and
anything else you'd reach for a language model to do. Pendra is
OpenAI-compatible, so if you've written against the OpenAI Chat
Completions API before, the shapes are identical — point your client at
https://api.pendra.ai with a pdr_sk_… key and
you're running.
Send a message
Every request carries a model and an array of
messages. Each message has a role —
system (instructions), user (the prompt), or
assistant (the model's previous turns) — and
content. Pass the whole conversation back on each call;
Pendra is stateless and doesn't remember prior turns for you.
from pendra import Pendra
client = Pendra() # reads PENDRA_API_KEY
response = client.chat.completions.create(
model="qwen3.6:27b",
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Explain GPUs in one sentence."},
],
)
print(response.choices[0].message.content)
A GPU is a specialised processor built for the massively parallel arithmetic that graphics and, more recently, AI workloads depend on.
Stream the reply
Set stream: true to receive tokens as they're generated
rather than waiting for the whole reply. Pendra returns
Server-Sent Events
in OpenAI's format, flushed immediately so text appears as it's produced.
Streaming is the right default for any interactive UI, since text appears
as it's produced. (Non-streaming requests are fine for long generations
too — Pendra keeps the connection alive for you up to ~30 minutes.)
stream = client.chat.completions.create(
model="qwen3.6:27b",
stream=True,
messages=[{"role": "user", "content": "Write a haiku."}],
)
for event in stream:
if not event.choices: # usage / timing chunk — no text
continue
print(event.choices[0].delta.content or "", end="")
Shape the output
The usual sampling controls all work:
temperature, top_p, top_k,
min_p, max_tokens, stop,
seed, and the frequency_penalty /
presence_penalty / repeat_penalty repetition
controls. Lower temperature for predictable, factual answers;
raise it for more variety. At temperature: 0 the model always
picks its most likely next token, the most focused and repeatable setting.
Where a model's authors publish recommended sampling settings, Pendra uses
them for any parameter you don't set. Anything you send overrides them.
You can see each model's settings as sampling_defaults in
the models list. Models without a
recommendation use a temperature of 1.0, with the penalties
off.
Because those recommendations can be updated, set the parameters you care about explicitly when you need output that stays consistent over time — for example in an evaluation or a regression test:
# Pin the sampling settings so results don't shift if the
# model's recommended defaults change
response = client.chat.completions.create(
model="qwen3.6:27b",
temperature=0.2,
top_p=0.9,
seed=42,
messages=[{"role": "user", "content": "Summarise this ticket in one line."}],
)
Even so, output isn't bit-for-bit reproducible: the same request can vary slightly between runs, because generation runs on GPU hardware where tiny numerical differences occasionally change a token. If you're running a high-recall job — extracting every defect from a document, say, where a miss is costly — send the request twice and take the union of the results. A second pass reliably catches the few items a single pass happens to miss:
# High-recall extraction: two passes, then union the findings
def extract(document):
response = client.chat.completions.create(
model="qwen3.6:27b",
temperature=0,
messages=[
{"role": "system", "content": "Extract every defect. Reply as JSON."},
{"role": "user", "content": document},
],
)
return parse(response.choices[0].message.content)
runs = [extract(document) for _ in range(2)]
findings = {item["id"]: item for run in runs for item in run}
# 'findings' now holds every item either pass found
Go further
- Thinking — reasoning models that show their working.
- Tool calling — let the model call your functions.
- Structured outputs — constrain replies to valid JSON.
- Vision — send images alongside text.
- Text and CSV files — attach a
.txtor.csvto a request.