Telluvian

SDKs

Using the official OpenAI Python and TypeScript SDKs against Telluvian.

The API implements both the OpenAI Chat Completions and Responses interfaces, so the official OpenAI SDKs work unmodified. Point base_url at Telluvian and use your Telluvian key. There is no Telluvian SDK to install and nothing to wrap.

This page covers client.chat.completions. For client.responses and server-side conversation state, see Responses API.

Everything on this page was run against the API

The snippets below are executed against a server implementing the real response schema, not written from memory. Where an SDK needs something non-obvious — like extra_body in Python — it is called out.

Install

pip install openai

Client setup

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.telluvian.ai/v1",
    api_key=os.environ["TELLUVIAN_API_KEY"],
)

Note the /v1

The base URL includes /v1. The SDKs append paths like /chat/completions to whatever you give them, so omitting it produces a 404 that looks like a routing bug.

Reading scores off a response

scores and tokens are extensions to the OpenAI schema. The SDKs keep unrecognised response fields, so they are readable as ordinary attributes.

response = client.chat.completions.create(
    model="google/gemma-4-31B-it",
    messages=[{"role": "user", "content": "When was the Eiffel Tower built?"}],
)

print(response.choices[0].message.content)
# The Eiffel Tower opened in 1889.

# Extension fields — the SDK keeps them in `model_extra`, and attribute
# access works. Your type checker will not know about them.
print(response.tokens)   # ['The', ' Eiffel', ' Tower', ' opened', ' in', ' 1889', '.']
print(response.scores)   # {'hallucination': [0.01, 0.02, 0.02, 0.05, 0.03, 0.61, 0.01]}

Finding the flagged span

tokens[i] and scores["hallucination"][i] are aligned and the same length, which is what makes the scores usable — you can point at the exact claim rather than at the whole answer.

THRESHOLD = 0.5   # pick your own; see Reading the scores

flagged = [
    tok for tok, score
    in zip(response.tokens, response.scores["hallucination"])
    if score > THRESHOLD
]
print("".join(flagged))   # ' 1889'

Reassembling the text

"".join(tokens) equals choices[0].message.content exactly. You never need to re-tokenise to line the scores up with the text.

Streaming

Each content chunk carries one score per probe for that chunk's token.

stream = client.chat.completions.create(
    model="google/gemma-4-31B-it",
    messages=[{"role": "user", "content": "Tell me about Ada Lovelace."}],
    stream=True,
)

usage = None
for chunk in stream:
    # The final chunk carries usage and no content; role-only chunks carry
    # neither. Guard both rather than assuming every chunk has a delta.
    if chunk.usage is not None:
        usage = chunk.usage

    delta = chunk.choices[0].delta.content
    if not delta:
        continue

    scores = getattr(chunk, "scores", None)
    score = scores.get("hallucination") if scores else None
    print(delta, score)

print(usage)   # CompletionUsage(prompt_tokens=9, completion_tokens=7, ...)

Usage arrives without asking

Token counts are attached to the final chunk of every stream. You do not need stream_options: {include_usage: true} — it is always there, and it is what your request is billed from.

Turning scoring off

This is the one place the two SDKs differ.

# Python's SDK validates arguments against the OpenAI schema, so a probe
# extension must go through extra_body or it is rejected before sending.
response = client.chat.completions.create(
    model="google/gemma-4-31B-it",
    messages=[{"role": "user", "content": "Hello"}],
    extra_body={"include_scores": False},
)

print(response.scores)   # None

See include_scores for what this changes, and Pricing for what it saves.

Listing models

for model in client.models.list().data:
    extra = model.model_extra or {}
    print(model.id, extra.get("pricing"), extra.get("probe"))

Other OpenAI-compatible tooling

Anything that speaks Chat Completions and lets you set a base URL should work — LangChain, LlamaIndex, Vercel AI SDK, and so on. The caveat is always the same: tools that re-serialise responses into their own types will drop scores and tokens, because those are not part of the OpenAI schema. If a framework is losing your scores, call the endpoint directly for the scored path.