Skip to content

API Reference

CygnisAI Python SDK ~~~~~~~~~~~~~~~~~~~

Simple, fast access to CygnisAI language models.

Quick-start::

import cygnisai_sdk_python as cygnis

cygnis.configure(api_key="YOUR_KEY")
model = cygnis.GenerativeModel("alpha2")
response = model.generate_content("Give me a Python tip.")
print(response.text)

Streaming::

stream = model.generate_content("Tell me a story.", stream=True)
async for token in stream:
    print(token, end="", flush=True)

AuthenticationError

Bases: CygnisAIError

Raised on HTTP 401 / 403.

Source code in cygnisai_sdk_python/client.py
45
46
class AuthenticationError(CygnisAIError):
    """Raised on HTTP 401 / 403."""

ChatRequest

Bases: BaseModel

Payload sent to POST /v3/chat.

Source code in cygnisai_sdk_python/models.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class ChatRequest(BaseModel):
    """Payload sent to POST /v3/chat."""

    model: str = Field(..., min_length=1, description="Model name, e.g. 'alpha2'.")
    prompt: str = Field(..., min_length=1, description="User prompt / question.")
    messages: Optional[List[Message]] = Field(
        None, description="Conversation history (optional)."
    )
    stream: bool = Field(False, description="Enable SSE streaming.")

    @field_validator("messages", mode="before")
    @classmethod
    def _messages_not_empty_list(cls, v: Any) -> Any:
        if isinstance(v, list) and len(v) == 0:
            return None
        return v

ChatResponse

Bases: BaseModel

Successful non-streaming response from POST /v3/chat.

Source code in cygnisai_sdk_python/models.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class ChatResponse(BaseModel):
    """Successful non-streaming response from POST /v3/chat."""

    id: uuid.UUID = Field(..., description="Unique response identifier.")
    response: str = Field(..., description="Full model reply.")
    latency_ms: int = Field(..., ge=0, description="Round-trip latency in milliseconds.")
    redacted: bool = Field(..., description="Whether the reply was moderated.")
    usage: UsageInfo = Field(..., description="Token-usage statistics.")

    @field_validator("usage", mode="before")
    @classmethod
    def _coerce_usage(cls, v: Any) -> Any:
        if isinstance(v, dict):
            return UsageInfo(**v)
        return v

CygnisAIClient

Async HTTP client for the CygnisAI API.

Can be used as an async context manager::

async with CygnisAIClient(api_key="...") as client:
    response = await client.chat(request)

Or managed manually::

client = CygnisAIClient(api_key="...")
try:
    ...
finally:
    await client.close()
Source code in cygnisai_sdk_python/client.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
class CygnisAIClient:
    """
    Async HTTP client for the CygnisAI API.

    Can be used as an async context manager::

        async with CygnisAIClient(api_key="...") as client:
            response = await client.chat(request)

    Or managed manually::

        client = CygnisAIClient(api_key="...")
        try:
            ...
        finally:
            await client.close()
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
        **httpx_kwargs: Any,
    ) -> None:
        if not api_key:
            raise ValueError("api_key must not be empty.")

        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.max_retries = max(0, max_retries)

        default_headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": "cygnisai-python-sdk/1.0.0",
        }
        headers = {**default_headers, **httpx_kwargs.pop("headers", {})}

        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=timeout,
            headers=headers,
            **httpx_kwargs,
        )

    # ------------------------------------------------------------------
    # Context manager support
    # ------------------------------------------------------------------

    async def __aenter__(self) -> "CygnisAIClient":
        return self

    async def __aexit__(self, *_: Any) -> None:
        await self.close()

    async def close(self) -> None:
        """Release the underlying HTTP connection pool."""
        if not self._client.is_closed:
            await self._client.aclose()

    # ------------------------------------------------------------------
    # Internal: retry logic
    # ------------------------------------------------------------------

    async def _post_with_retry(
        self,
        path: str,
        payload: dict[str, Any],
    ) -> httpx.Response:
        """POST *path* with exponential-backoff retries on transient errors."""
        import asyncio

        last_exc: Exception | None = None
        for attempt in range(self.max_retries + 1):
            try:
                resp = await self._client.post(path, json=payload)
                if resp.status_code not in _RETRY_STATUS_CODES or attempt == self.max_retries:
                    return resp
                wait = 2 ** attempt
                logger.warning(
                    "CygnisAI: HTTP %s on attempt %d/%d — retrying in %ds",
                    resp.status_code, attempt + 1, self.max_retries + 1, wait,
                )
                await asyncio.sleep(wait)
            except httpx.RequestError as exc:
                last_exc = exc
                if attempt == self.max_retries:
                    break
                wait = 2 ** attempt
                logger.warning(
                    "CygnisAI: Network error on attempt %d/%d — retrying in %ds: %s",
                    attempt + 1, self.max_retries + 1, wait, exc,
                )
                await asyncio.sleep(wait)

        raise NetworkError(
            f"Request failed after {self.max_retries + 1} attempts: {last_exc}",
            error_details=str(last_exc),
        )

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    async def chat(self, request: ChatRequest) -> ChatResponse:
        """
        Send a chat request and return the complete response.

        Args:
            request: A :class:`~cygnisai_sdk_python.models.ChatRequest` instance.

        Returns:
            :class:`~cygnisai_sdk_python.models.ChatResponse`

        Raises:
            AuthenticationError: On 401/403.
            RateLimitError: On 429.
            ServerError: On 5xx.
            NetworkError: On connection failure.
            ResponseValidationError: If the response body doesn't match the schema.
            CygnisAIError: For any other API error.
        """
        request.stream = False
        payload = request.model_dump(exclude_none=True)

        try:
            raw = await self._post_with_retry("/v3/chat", payload)
        except NetworkError:
            raise

        if raw.status_code != 200:
            try:
                body = raw.json()
                msg = _extract_error_message(body)
            except Exception:
                body = {}
                msg = raw.text
            raise _map_status_error(
                raw.status_code,
                f"API error (HTTP {raw.status_code}): {msg}",
                body,
            )

        try:
            return ChatResponse(**raw.json())
        except (ValidationError, Exception) as exc:
            raise ResponseValidationError(
                f"Failed to parse API response: {exc}",
                error_details=str(exc),
            ) from exc

    async def chat_stream(
        self, request: ChatRequest
    ) -> AsyncGenerator[str, None]:
        """
        Send a chat request and yield tokens as they arrive (SSE).

        Args:
            request: A :class:`~cygnisai_sdk_python.models.ChatRequest` instance.

        Yields:
            str – individual text tokens from the model.

        Raises:
            CygnisAIError (and subclasses): as documented on :meth:`chat`.
        """
        request.stream = True
        payload = request.model_dump(exclude_none=True)

        async with self._client.stream("POST", "/v3/chat", json=payload) as response:
            if response.status_code != 200:
                error_bytes = await response.aread()
                try:
                    body = json.loads(error_bytes.decode())
                    msg = _extract_error_message(body)
                except Exception:
                    body = {}
                    msg = error_bytes.decode()
                raise _map_status_error(
                    response.status_code,
                    f"API error (HTTP {response.status_code}): {msg}",
                    body,
                )

            async for raw_line in response.aiter_lines():
                line = raw_line.strip()
                if not line:
                    continue

                if line.startswith("data:"):
                    content = line[5:].strip()

                    if content == "[DONE]":
                        return

                    try:
                        data = json.loads(content)
                    except json.JSONDecodeError:
                        # Yield raw text if it isn't valid JSON
                        yield content
                        continue

                    if not isinstance(data, dict):
                        yield str(data)
                        continue

                    # Surface server-side errors embedded in the stream
                    if "error" in data or "detail" in data:
                        err_key = "error" if "error" in data else "detail"
                        err_val = data[err_key]
                        if isinstance(err_val, list):
                            msgs = [
                                f"{e.get('loc', ['?'])[-1]}: {e.get('msg', '')}"
                                for e in err_val
                                if isinstance(e, dict)
                            ]
                            err_val = "; ".join(msgs)
                        raise CygnisAIError(
                            f"Server error in stream: {err_val}",
                            error_details=data,
                        )

                    token = data.get("response") or data.get("text") or ""
                    if token:
                        yield token

chat(request) async

Send a chat request and return the complete response.

Parameters:

Name Type Description Default
request ChatRequest

A :class:~cygnisai_sdk_python.models.ChatRequest instance.

required

Returns:

Type Description
ChatResponse

class:~cygnisai_sdk_python.models.ChatResponse

Raises:

Type Description
AuthenticationError

On 401/403.

RateLimitError

On 429.

ServerError

On 5xx.

NetworkError

On connection failure.

ResponseValidationError

If the response body doesn't match the schema.

CygnisAIError

For any other API error.

Source code in cygnisai_sdk_python/client.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
async def chat(self, request: ChatRequest) -> ChatResponse:
    """
    Send a chat request and return the complete response.

    Args:
        request: A :class:`~cygnisai_sdk_python.models.ChatRequest` instance.

    Returns:
        :class:`~cygnisai_sdk_python.models.ChatResponse`

    Raises:
        AuthenticationError: On 401/403.
        RateLimitError: On 429.
        ServerError: On 5xx.
        NetworkError: On connection failure.
        ResponseValidationError: If the response body doesn't match the schema.
        CygnisAIError: For any other API error.
    """
    request.stream = False
    payload = request.model_dump(exclude_none=True)

    try:
        raw = await self._post_with_retry("/v3/chat", payload)
    except NetworkError:
        raise

    if raw.status_code != 200:
        try:
            body = raw.json()
            msg = _extract_error_message(body)
        except Exception:
            body = {}
            msg = raw.text
        raise _map_status_error(
            raw.status_code,
            f"API error (HTTP {raw.status_code}): {msg}",
            body,
        )

    try:
        return ChatResponse(**raw.json())
    except (ValidationError, Exception) as exc:
        raise ResponseValidationError(
            f"Failed to parse API response: {exc}",
            error_details=str(exc),
        ) from exc

chat_stream(request) async

Send a chat request and yield tokens as they arrive (SSE).

Parameters:

Name Type Description Default
request ChatRequest

A :class:~cygnisai_sdk_python.models.ChatRequest instance.

required

Yields:

Type Description
AsyncGenerator[str, None]

str – individual text tokens from the model.

Raises:

Type Description
CygnisAIError (and subclasses)

as documented on :meth:chat.

Source code in cygnisai_sdk_python/client.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
async def chat_stream(
    self, request: ChatRequest
) -> AsyncGenerator[str, None]:
    """
    Send a chat request and yield tokens as they arrive (SSE).

    Args:
        request: A :class:`~cygnisai_sdk_python.models.ChatRequest` instance.

    Yields:
        str – individual text tokens from the model.

    Raises:
        CygnisAIError (and subclasses): as documented on :meth:`chat`.
    """
    request.stream = True
    payload = request.model_dump(exclude_none=True)

    async with self._client.stream("POST", "/v3/chat", json=payload) as response:
        if response.status_code != 200:
            error_bytes = await response.aread()
            try:
                body = json.loads(error_bytes.decode())
                msg = _extract_error_message(body)
            except Exception:
                body = {}
                msg = error_bytes.decode()
            raise _map_status_error(
                response.status_code,
                f"API error (HTTP {response.status_code}): {msg}",
                body,
            )

        async for raw_line in response.aiter_lines():
            line = raw_line.strip()
            if not line:
                continue

            if line.startswith("data:"):
                content = line[5:].strip()

                if content == "[DONE]":
                    return

                try:
                    data = json.loads(content)
                except json.JSONDecodeError:
                    # Yield raw text if it isn't valid JSON
                    yield content
                    continue

                if not isinstance(data, dict):
                    yield str(data)
                    continue

                # Surface server-side errors embedded in the stream
                if "error" in data or "detail" in data:
                    err_key = "error" if "error" in data else "detail"
                    err_val = data[err_key]
                    if isinstance(err_val, list):
                        msgs = [
                            f"{e.get('loc', ['?'])[-1]}: {e.get('msg', '')}"
                            for e in err_val
                            if isinstance(e, dict)
                        ]
                        err_val = "; ".join(msgs)
                    raise CygnisAIError(
                        f"Server error in stream: {err_val}",
                        error_details=data,
                    )

                token = data.get("response") or data.get("text") or ""
                if token:
                    yield token

close() async

Release the underlying HTTP connection pool.

Source code in cygnisai_sdk_python/client.py
161
162
163
164
async def close(self) -> None:
    """Release the underlying HTTP connection pool."""
    if not self._client.is_closed:
        await self._client.aclose()

CygnisAIError

Bases: Exception

Base exception for all CygnisAI SDK errors.

Source code in cygnisai_sdk_python/client.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class CygnisAIError(Exception):
    """Base exception for all CygnisAI SDK errors."""

    def __init__(
        self,
        message: str,
        status_code: Optional[int] = None,
        error_details: Optional[Any] = None,
    ) -> None:
        super().__init__(message)
        self.message = message
        self.status_code = status_code
        self.error_details = error_details

    def __repr__(self) -> str:  # pragma: no cover
        return (
            f"{self.__class__.__name__}("
            f"message={self.message!r}, "
            f"status_code={self.status_code!r})"
        )

ErrorResponse

Bases: BaseModel

Standardised error envelope returned by the API.

Source code in cygnisai_sdk_python/models.py
 97
 98
 99
100
101
102
103
104
class ErrorResponse(BaseModel):
    """Standardised error envelope returned by the API."""

    code: str = Field(..., description="Machine-readable error code.")
    message: str = Field(..., description="Human-readable error message.")
    details: Optional[List[ErrorDetail]] = Field(
        None, description="Validation details when applicable."
    )

GenerativeModel

High-level interface for a CygnisAI language model.

Uses the global client configured via :func:configure.

Example::

model = GenerativeModel("alpha2")
response = model.generate_content("Hello!")
print(response.text)
Source code in cygnisai_sdk_python/__init__.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class GenerativeModel:
    """
    High-level interface for a CygnisAI language model.

    Uses the global client configured via :func:`configure`.

    Example::

        model = GenerativeModel("alpha2")
        response = model.generate_content("Hello!")
        print(response.text)
    """

    def __init__(self, model_name: str) -> None:
        if not model_name:
            raise ValueError("model_name must not be empty.")
        self.model_name = model_name

    def _get_client(self) -> CygnisAIClient:
        if _global_client is None:
            raise CygnisAIError(
                "CygnisAI client is not configured. "
                "Call cygnis.configure(api_key='YOUR_KEY') first."
            )
        return _global_client

    def generate_content(
        self,
        prompt: str,
        messages: Optional[List[Message]] = None,
        stream: bool = False,
    ) -> Any:
        """
        Generate text from a prompt.

        Args:
            prompt: The question or instruction for the model.
            messages: Optional conversation history.
            stream: If ``True``, returns a :class:`GenerativeStreamResponse`
                    that you can ``async for`` over.

        Returns:
            :class:`GenerativeResponse` (blocking) or
            :class:`GenerativeStreamResponse` (streaming).
        """
        client = self._get_client()
        request = ChatRequest(
            model=self.model_name,
            prompt=prompt,
            messages=messages,
            stream=stream,
        )

        if stream:
            return GenerativeStreamResponse(client.chat_stream(request=request))

        # Synchronous execution via asyncio.run — works in scripts & notebooks
        async def _run() -> ChatResponse:
            return await client.chat(request=request)

        try:
            loop = asyncio.get_running_loop()
        except RuntimeError:
            loop = None

        if loop and loop.is_running():
            # Inside an async context (e.g. Jupyter) — caller must await
            raise RuntimeError(
                "generate_content() cannot be called synchronously inside a running "
                "event loop. Use `await model.async_generate_content(...)` instead, "
                "or call it from a regular (non-async) context."
            )

        full_response = asyncio.run(_run())
        return GenerativeResponse(text=full_response.response, full_response=full_response)

    async def async_generate_content(
        self,
        prompt: str,
        messages: Optional[List[Message]] = None,
        stream: bool = False,
    ) -> Any:
        """
        Async version of :meth:`generate_content` for use inside ``async`` code.

        Example::

            response = await model.async_generate_content("Hello!")
        """
        client = self._get_client()
        request = ChatRequest(
            model=self.model_name,
            prompt=prompt,
            messages=messages,
            stream=stream,
        )

        if stream:
            return GenerativeStreamResponse(client.chat_stream(request=request))

        full_response = await client.chat(request=request)
        return GenerativeResponse(text=full_response.response, full_response=full_response)

async_generate_content(prompt, messages=None, stream=False) async

Async version of :meth:generate_content for use inside async code.

Example::

response = await model.async_generate_content("Hello!")
Source code in cygnisai_sdk_python/__init__.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
async def async_generate_content(
    self,
    prompt: str,
    messages: Optional[List[Message]] = None,
    stream: bool = False,
) -> Any:
    """
    Async version of :meth:`generate_content` for use inside ``async`` code.

    Example::

        response = await model.async_generate_content("Hello!")
    """
    client = self._get_client()
    request = ChatRequest(
        model=self.model_name,
        prompt=prompt,
        messages=messages,
        stream=stream,
    )

    if stream:
        return GenerativeStreamResponse(client.chat_stream(request=request))

    full_response = await client.chat(request=request)
    return GenerativeResponse(text=full_response.response, full_response=full_response)

generate_content(prompt, messages=None, stream=False)

Generate text from a prompt.

Parameters:

Name Type Description Default
prompt str

The question or instruction for the model.

required
messages Optional[List[Message]]

Optional conversation history.

None
stream bool

If True, returns a :class:GenerativeStreamResponse that you can async for over.

False

Returns:

Type Description
Any

class:GenerativeResponse (blocking) or

Any

class:GenerativeStreamResponse (streaming).

Source code in cygnisai_sdk_python/__init__.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def generate_content(
    self,
    prompt: str,
    messages: Optional[List[Message]] = None,
    stream: bool = False,
) -> Any:
    """
    Generate text from a prompt.

    Args:
        prompt: The question or instruction for the model.
        messages: Optional conversation history.
        stream: If ``True``, returns a :class:`GenerativeStreamResponse`
                that you can ``async for`` over.

    Returns:
        :class:`GenerativeResponse` (blocking) or
        :class:`GenerativeStreamResponse` (streaming).
    """
    client = self._get_client()
    request = ChatRequest(
        model=self.model_name,
        prompt=prompt,
        messages=messages,
        stream=stream,
    )

    if stream:
        return GenerativeStreamResponse(client.chat_stream(request=request))

    # Synchronous execution via asyncio.run — works in scripts & notebooks
    async def _run() -> ChatResponse:
        return await client.chat(request=request)

    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = None

    if loop and loop.is_running():
        # Inside an async context (e.g. Jupyter) — caller must await
        raise RuntimeError(
            "generate_content() cannot be called synchronously inside a running "
            "event loop. Use `await model.async_generate_content(...)` instead, "
            "or call it from a regular (non-async) context."
        )

    full_response = asyncio.run(_run())
    return GenerativeResponse(text=full_response.response, full_response=full_response)

GenerativeResponse

Wraps a completed :class:~cygnisai_sdk_python.models.ChatResponse.

Source code in cygnisai_sdk_python/__init__.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class GenerativeResponse:
    """Wraps a completed :class:`~cygnisai_sdk_python.models.ChatResponse`."""

    def __init__(self, text: str, full_response: Optional[ChatResponse] = None) -> None:
        self.text = text
        self._full_response = full_response

    def __str__(self) -> str:
        return self.text

    def __repr__(self) -> str:
        snippet = self.text[:60].replace("\n", " ")
        return f"GenerativeResponse(text={snippet!r})"

    @property
    def full_response(self) -> Optional[ChatResponse]:
        """The raw :class:`~cygnisai_sdk_python.models.ChatResponse`, if available."""
        return self._full_response

    @property
    def usage(self) -> Optional[UsageInfo]:
        """Token-usage statistics, if available."""
        return self._full_response.usage if self._full_response else None

    @property
    def latency_ms(self) -> Optional[int]:
        """Round-trip latency reported by the API."""
        return self._full_response.latency_ms if self._full_response else None

full_response property

The raw :class:~cygnisai_sdk_python.models.ChatResponse, if available.

latency_ms property

Round-trip latency reported by the API.

usage property

Token-usage statistics, if available.

GenerativeStreamResponse

Wraps an async token generator from :meth:CygnisAIClient.chat_stream.

Source code in cygnisai_sdk_python/__init__.py
90
91
92
93
94
95
96
97
class GenerativeStreamResponse:
    """Wraps an async token generator from :meth:`CygnisAIClient.chat_stream`."""

    def __init__(self, async_generator: AsyncGenerator[str, None]) -> None:
        self._async_generator = async_generator

    def __aiter__(self):
        return self._async_generator.__aiter__()

Message

Bases: BaseModel

A single turn in a conversation.

Source code in cygnisai_sdk_python/models.py
30
31
32
33
34
35
36
class Message(BaseModel):
    """A single turn in a conversation."""

    role: Role = Field(..., description="Author role: 'user', 'assistant', or 'system'.")
    content: str = Field(..., min_length=1, description="Text content of the message.")

    model_config = {"use_enum_values": True}

NetworkError

Bases: CygnisAIError

Raised when the request never reaches the server.

Source code in cygnisai_sdk_python/client.py
57
58
class NetworkError(CygnisAIError):
    """Raised when the request never reaches the server."""

RateLimitError

Bases: CygnisAIError

Raised on HTTP 429.

Source code in cygnisai_sdk_python/client.py
49
50
class RateLimitError(CygnisAIError):
    """Raised on HTTP 429."""

ResponseValidationError

Bases: CygnisAIError

Raised when the API response doesn't match the expected schema.

Source code in cygnisai_sdk_python/client.py
61
62
class ResponseValidationError(CygnisAIError):
    """Raised when the API response doesn't match the expected schema."""

Role

Bases: str, Enum

Valid message roles.

Source code in cygnisai_sdk_python/models.py
19
20
21
22
23
class Role(str, Enum):
    """Valid message roles."""
    USER = "user"
    ASSISTANT = "assistant"
    SYSTEM = "system"

ServerError

Bases: CygnisAIError

Raised on HTTP 5xx.

Source code in cygnisai_sdk_python/client.py
53
54
class ServerError(CygnisAIError):
    """Raised on HTTP 5xx."""

UsageInfo

Bases: BaseModel

Token-usage breakdown returned by the API.

Source code in cygnisai_sdk_python/models.py
39
40
41
42
43
44
45
46
47
class UsageInfo(BaseModel):
    """Token-usage breakdown returned by the API."""

    prompt_tokens: Optional[int] = Field(None, ge=0)
    completion_tokens: Optional[int] = Field(None, ge=0)
    total_tokens: Optional[int] = Field(None, ge=0)

    # Keep extra fields from the API without failing validation
    model_config = {"extra": "allow"}

configure(api_key=None, base_url=None, timeout=30.0, max_retries=3)

Initialise the global CygnisAI client.

Call this once at startup before using :class:GenerativeModel.

Parameters:

Name Type Description Default
api_key Optional[str]

Your CygnisAI API key. Falls back to the CYGNIS_API_KEY environment variable if not provided.

None
base_url Optional[str]

Override the default API base URL.

None
timeout float

HTTP timeout in seconds (default 30).

30.0
max_retries int

Number of automatic retries on transient errors (default 3).

3

Raises:

Type Description
ValueError

If no API key is found.

Source code in cygnisai_sdk_python/__init__.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def configure(
    api_key: Optional[str] = None,
    base_url: Optional[str] = None,
    timeout: float = 30.0,
    max_retries: int = 3,
) -> None:
    """
    Initialise the global CygnisAI client.

    Call this once at startup before using :class:`GenerativeModel`.

    Args:
        api_key: Your CygnisAI API key. Falls back to the ``CYGNIS_API_KEY``
                 environment variable if not provided.
        base_url: Override the default API base URL.
        timeout: HTTP timeout in seconds (default 30).
        max_retries: Number of automatic retries on transient errors (default 3).

    Raises:
        ValueError: If no API key is found.
    """
    global _global_client, _global_api_key, _global_base_url

    resolved_key = api_key or os.environ.get("CYGNIS_API_KEY")
    if not resolved_key:
        raise ValueError(
            "No API key provided. Pass api_key= or set the CYGNIS_API_KEY "
            "environment variable."
        )

    _global_api_key = resolved_key
    if base_url:
        _global_base_url = base_url

    # Cleanly close the previous client if one exists
    if _global_client is not None:
        try:
            asyncio.run(_global_client.close())
        except RuntimeError:
            pass  # Already inside a running loop — leak is acceptable here

    _global_client = CygnisAIClient(
        api_key=_global_api_key,
        base_url=_global_base_url,
        timeout=timeout,
        max_retries=max_retries,
    )