Python SDK
Because Serenity* Star API exposes OpenAI-compatible endpoints for your AIProxy agents, you can drive them with the official openai Python package. There is no Serenity-specific SDK to install, and once the client is configured everything else (streaming, tools, input files) works exactly as it does against OpenAI.
For more information about request and response shapes for the AIProxy OpenAI-Compatible endpoints, see the AIProxy reference.
Install
pip install openai
Configure the client
Two things differ from a plain OpenAI setup:
base_urlmust point at the AIProxy path. With it set, the SDK appends/chat/completionsand/responsesfor you.- A
User-Agentheader is required by the Serenity* Star API, and the OpenAI SDK does not send one. Set it yourself viadefault_headers, otherwise the request fails with a403 Forbidden.
Your Serenity API key goes in api_key; the bearer form is accepted, so it is sent as Authorization: Bearer YOUR_API_KEY.
from openai import OpenAI
client = OpenAI(
base_url="https://api.serenitystar.ai/api/v2/aiproxy",
api_key="YOUR_API_KEY",
default_headers={"User-Agent": "mySerenityClient/1.0"}, # required
)
The model identifier
The one other difference is the model field. Instead of a plain model name, AIProxy takes a colon-separated {agentCode}:{vendor}:{model} value that selects which agent to run and which underlying model to route to:
{agentCode}:{vendor}:{model}
For example, my-aiproxy-agent:OpenAI:gpt-5.6-luna runs the my-aiproxy-agent agent against OpenAI's gpt-5.6-luna. To route the same agent to a different vendor, just change the vendor and model parts, for example:
my-aiproxy-agent:Anthropic:claude-fable-5my-aiproxy-agent:GoogleVertex:gemini-3.5-flash
See The model identifier for the full rules.
Examples
Simple request
response = client.chat.completions.create(
model="my-aiproxy-agent:OpenAI:gpt-5.6-luna",
max_completion_tokens=5000,
messages=[
{"role": "user", "content": "Hello! Tell me a fun fact about Valencia."},
],
)
print(response.choices[0].message.content)
Function calling
Client tools are standard OpenAI function calling: the model asks for a function, you run it, and you send the result back for the model to finish its answer. Only the model identifier is specific to AIProxy.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name"},
},
"required": ["city"],
},
},
}
]
messages = [
{"role": "user", "content": "What is the weather like in Paris right now? Use the tool."},
]
# Step 1 — the model requests the tool.
first = client.chat.completions.create(
model="my-aiproxy-agent:Anthropic:claude-fable-5",
max_completion_tokens=512,
messages=messages,
tools=tools,
)
tool_call = first.choices[0].message.tool_calls[0]
# Step 2 — run the function and send the result back.
messages.append(first.choices[0].message)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": '{"city":"Paris","temperature_c":18,"condition":"light rain","humidity":72}',
}
)
second = client.chat.completions.create(
model="my-aiproxy-agent:Anthropic:claude-fable-5",
max_completion_tokens=512,
messages=messages,
tools=tools,
)
print(second.choices[0].message.content)
Input files
Attach images and documents as content parts in a user message. Each part can point at a file as a remote URL, an inline base64 data URI, or the file_id of a file already uploaded as Volatile Knowledge. See Input files for how each source is handled.
import base64
# Encode a local file as a base64 data URI.
def to_data_uri(path: str, mime: str) -> str:
with open(path, "rb") as f:
encoded = base64.b64encode(f.read()).decode()
return f"data:{mime};base64,{encoded}"
response = client.chat.completions.create(
model="my-aiproxy-agent:GoogleVertex:gemini-3.5-flash",
max_completion_tokens=5000,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Analyse all attached files."},
# Image by remote URL
{
"type": "image_url",
"image_url": {"url": "https://example.com/image.png"},
},
# Image as an inline base64 data URI
{
"type": "image_url",
"image_url": {"url": to_data_uri("diagram.png", "image/png")},
},
# Document already uploaded as Volatile Knowledge, by file_id
{
"type": "file",
"file": {"file_id": "3eb85cee-cfdb-bfd9-3291-3a22798665a2"},
},
# Document as an inline base64 data URI
{
"type": "file",
"file": {
"filename": "report.pdf",
"file_data": to_data_uri("report.pdf", "application/pdf"),
},
},
],
},
],
)
print(response.choices[0].message.content)
Execution metadata
To attribute an execution to a channel, user_identifier, or group_identifier, pass them through the SDK's extra_body parameter, which merges them into the top level of the request body. See Serenity* Star custom fields.
response = client.chat.completions.create(
model="my-aiproxy-agent:GoogleVertex:gemini-3.5-flash",
max_completion_tokens=5000,
messages=[
{"role": "user", "content": "Hello! Tell me a fun fact about Valencia."},
],
extra_body={
"channel": "my-channel",
"user_identifier": "user-123",
"group_identifier": "group-abc",
},
)
print(response.choices[0].message.content)