The official openai package, pointed at Upfyn. Everything here runs as written.
Setting up the client#
client.py
import osfrom openai import OpenAIclient = OpenAI(base_url="https://ai.upfyn.com/v1",api_key=os.environ["UPFYN_API_KEY"],timeout=60.0, # generous: a reasoning turn can take a whilemax_retries=0, # retry yourself, so you control which codes are retried)
Turn the SDK's own retries off
max_retries=0 looks backwards but it is the right default here: the SDK will happily retry a 402, which can never succeed. Handle retries yourself so you only retry 429 and 5xx — see Errors.A basic call#
One turn
response = client.chat.completions.create(model="upfyn-yuva",messages=[{"role": "system", "content": "You answer in one short paragraph."},{"role": "user", "content": "Why is my invoice higher this month?"},],max_tokens=400,)print(response.choices[0].message.content)print("cost:", response.model_extra.get("credits_used"))
credits_used is an Upfyn field, so the typed model hides it — model_extra is where the SDK keeps everything it did not expect.
Streaming#
Collecting as you print
stream = client.chat.completions.create(model="upfyn-yuva",messages=[{"role": "user", "content": "Draft a status update."}],stream=True,stream_options={"include_usage": True},)pieces = []for chunk in stream:if chunk.choices and chunk.choices[0].delta.content:piece = chunk.choices[0].delta.contentpieces.append(piece)print(piece, end="", flush=True)text = "".join(pieces)
- Guard on
chunk.choices— the final usage chunk has an empty list. - Accumulate into a list and join once; repeated string concatenation in a hot loop is the slow way.
Concurrency#
AsyncOpenAI with a semaphore
import asynciofrom openai import AsyncOpenAIclient = AsyncOpenAI(base_url="https://ai.upfyn.com/v1", api_key=KEY)gate = asyncio.Semaphore(8) # stay under the per-minute limitasync def ask(question: str) -> str:async with gate:response = await client.chat.completions.create(model="upfyn-bala",messages=[{"role": "user", "content": question}],)return response.choices[0].message.contentasync def main():answers = await asyncio.gather(*(ask(q) for q in questions))
Your rate limit is the real ceiling
A bareasyncio.gather over a hundred prompts hits 60 requests per minute immediately and starts collecting 429s. Bound the fan-out.Errors#
Distinguishing what to retry
from openai import APIStatusError, APIConnectionErrortry:response = client.chat.completions.create(...)except APIStatusError as err:if err.status_code == 402:raise OutOfCredits(err.response.json()["error"]["message"])if err.status_code == 429:wait = float(err.response.headers.get("Retry-After") or 5)...raiseexcept APIConnectionError:... # network, not the API — safe to retry
Upfyn-only parameters
extra_body
client.chat.completions.create(model="upfyn-yuva",messages=messages,extra_body={"persist_session": True,"chat_ref": "ticket-8821","upfyn_tag": "support",},)
