omniai-kit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,403 @@
1
+ """Chat completions resource for OmniAI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, Generator, List, Optional, Type, TypeVar, Union
7
+ from pydantic import BaseModel
8
+
9
+ from omniai.types.tasks import clean_json_text
10
+ from omniai.exceptions import OmniaiValidationError
11
+
12
+ if TYPE_CHECKING:
13
+ from omniai._base import BaseClient, AsyncBaseClient
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class CompletionsResource:
19
+ """Synchronous completions endpoint resource."""
20
+
21
+ def __init__(self, client: BaseClient):
22
+ self._client = client
23
+
24
+ def create(
25
+ self,
26
+ *,
27
+ model: str,
28
+ prompt: Optional[str] = None,
29
+ messages: Optional[List[Dict[str, str]]] = None,
30
+ response_model: Optional[Type[T]] = None,
31
+ tools: Optional[List[Any]] = None,
32
+ output_format: Optional[Union[str, Dict[str, Any]]] = None,
33
+ system_instruction: Optional[str] = None,
34
+ temperature: float = 0.1,
35
+ max_tokens: Optional[int] = None,
36
+ timeout: Optional[float] = None,
37
+ ) -> Union[str, T]:
38
+ # Early provider validation
39
+ if ":" in model:
40
+ provider = model.split(":", 1)[0].strip().lower()
41
+ if provider not in ("openai", "gemini", "anthropic", "vllm", "openrouter", "ollama", "custom", "local"):
42
+ from omniai.exceptions import OmniaiUnsupportedProviderError
43
+ raise OmniaiUnsupportedProviderError(
44
+ f"Unsupported model provider '{provider}'. Strictly supported providers are: openai, gemini, anthropic, vllm, openrouter, ollama, custom, local."
45
+ )
46
+
47
+ payload: Dict[str, Any] = {
48
+ "model": model,
49
+ "temperature": temperature,
50
+ }
51
+ if prompt is not None:
52
+ payload["prompt"] = prompt
53
+ if messages is not None:
54
+ payload["messages"] = messages
55
+ if system_instruction is not None:
56
+ payload["system_instruction"] = system_instruction
57
+ if max_tokens is not None:
58
+ payload["max_tokens"] = max_tokens
59
+ if tools:
60
+ payload["tools"] = tools
61
+ if output_format:
62
+ payload["output_format"] = output_format
63
+ elif response_model and issubclass(response_model, BaseModel):
64
+ payload["output_format"] = response_model.model_json_schema()
65
+
66
+ response = self._client.client.post(
67
+ "/v1/chat/completions",
68
+ json=payload,
69
+ timeout=timeout or self._client.timeout,
70
+ )
71
+ if response.status_code != 200:
72
+ self._client._handle_error_response(response)
73
+
74
+ data = response.json()
75
+ raw_result = data.get("result", "")
76
+
77
+ if response_model and issubclass(response_model, BaseModel):
78
+ if isinstance(raw_result, dict):
79
+ try:
80
+ return response_model.model_validate(raw_result)
81
+ except Exception as e:
82
+ raise OmniaiValidationError(
83
+ f"Failed validating response dictionary against {response_model.__name__}: {e}",
84
+ raw_output=str(raw_result),
85
+ ) from e
86
+ elif isinstance(raw_result, str):
87
+ cleaned = clean_json_text(raw_result)
88
+ try:
89
+ return response_model.model_validate_json(cleaned)
90
+ except Exception as e:
91
+ raise OmniaiValidationError(
92
+ f"Failed validating JSON string against {response_model.__name__}: {e}",
93
+ raw_output=raw_result,
94
+ ) from e
95
+
96
+ return raw_result
97
+
98
+ def stream(
99
+ self,
100
+ *,
101
+ model: str,
102
+ prompt: Optional[str] = None,
103
+ messages: Optional[List[Dict[str, str]]] = None,
104
+ system_instruction: Optional[str] = None,
105
+ temperature: float = 0.1,
106
+ max_tokens: Optional[int] = None,
107
+ timeout: Optional[float] = None,
108
+ ) -> Generator[str, None, None]:
109
+ """Streams a chat completion chunk-by-chunk using Server-Sent Events."""
110
+ payload: Dict[str, Any] = {
111
+ "model": model,
112
+ "temperature": temperature,
113
+ "stream": True,
114
+ }
115
+ if prompt is not None:
116
+ payload["prompt"] = prompt
117
+ if messages is not None:
118
+ payload["messages"] = messages
119
+ if system_instruction is not None:
120
+ payload["system_instruction"] = system_instruction
121
+ if max_tokens is not None:
122
+ payload["max_tokens"] = max_tokens
123
+
124
+ with self._client.client.stream(
125
+ "POST",
126
+ "/v1/chat/completions",
127
+ json=payload,
128
+ timeout=timeout or self._client.timeout,
129
+ ) as response:
130
+ if response.status_code != 200:
131
+ response.read()
132
+ self._client._handle_error_response(response)
133
+
134
+ for line in response.iter_lines():
135
+ if not line or not line.strip():
136
+ continue
137
+ line_str = line.strip()
138
+ if line_str.startswith("data: "):
139
+ content = line_str[6:].strip()
140
+ if content == "[DONE]":
141
+ break
142
+ try:
143
+ chunk = json.loads(content)
144
+ delta = (
145
+ chunk.get("choices", [{}])[0].get("delta", {}).get("content")
146
+ or chunk.get("content")
147
+ or chunk.get("delta")
148
+ or chunk.get("text", "")
149
+ )
150
+ if delta:
151
+ yield delta
152
+ except json.JSONDecodeError:
153
+ yield content
154
+
155
+
156
+ class AsyncCompletionsResource:
157
+ """Asynchronous completions endpoint resource."""
158
+
159
+ def __init__(self, client: AsyncBaseClient):
160
+ self._client = client
161
+
162
+ async def create(
163
+ self,
164
+ *,
165
+ model: str,
166
+ prompt: Optional[str] = None,
167
+ messages: Optional[List[Dict[str, str]]] = None,
168
+ response_model: Optional[Type[T]] = None,
169
+ tools: Optional[List[Any]] = None,
170
+ output_format: Optional[Union[str, Dict[str, Any]]] = None,
171
+ system_instruction: Optional[str] = None,
172
+ temperature: float = 0.1,
173
+ max_tokens: Optional[int] = None,
174
+ timeout: Optional[float] = None,
175
+ ) -> Union[str, T]:
176
+ # Early provider validation
177
+ if ":" in model:
178
+ provider = model.split(":", 1)[0].strip().lower()
179
+ if provider not in ("openai", "gemini", "anthropic", "vllm", "openrouter", "ollama", "custom", "local"):
180
+ from omniai.exceptions import OmniaiUnsupportedProviderError
181
+ raise OmniaiUnsupportedProviderError(
182
+ f"Unsupported model provider '{provider}'. Strictly supported providers are: openai, gemini, anthropic, vllm, openrouter, ollama, custom, local."
183
+ )
184
+
185
+ payload: Dict[str, Any] = {
186
+ "model": model,
187
+ "temperature": temperature,
188
+ }
189
+ if prompt is not None:
190
+ payload["prompt"] = prompt
191
+ if messages is not None:
192
+ payload["messages"] = messages
193
+ if system_instruction is not None:
194
+ payload["system_instruction"] = system_instruction
195
+ if max_tokens is not None:
196
+ payload["max_tokens"] = max_tokens
197
+ if tools:
198
+ payload["tools"] = tools
199
+ if output_format:
200
+ payload["output_format"] = output_format
201
+ elif response_model and issubclass(response_model, BaseModel):
202
+ payload["output_format"] = response_model.model_json_schema()
203
+
204
+ response = await self._client.client.post(
205
+ "/v1/chat/completions",
206
+ json=payload,
207
+ timeout=timeout or self._client.timeout,
208
+ )
209
+ if response.status_code != 200:
210
+ self._client._handle_error_response(response)
211
+
212
+ data = response.json()
213
+ raw_result = data.get("result", "")
214
+
215
+ if response_model and issubclass(response_model, BaseModel):
216
+ if isinstance(raw_result, dict):
217
+ try:
218
+ return response_model.model_validate(raw_result)
219
+ except Exception as e:
220
+ raise OmniaiValidationError(
221
+ f"Failed validating response dictionary against {response_model.__name__}: {e}",
222
+ raw_output=str(raw_result),
223
+ ) from e
224
+ elif isinstance(raw_result, str):
225
+ cleaned = clean_json_text(raw_result)
226
+ try:
227
+ return response_model.model_validate_json(cleaned)
228
+ except Exception as e:
229
+ raise OmniaiValidationError(
230
+ f"Failed validating JSON string against {response_model.__name__}: {e}",
231
+ raw_output=raw_result,
232
+ ) from e
233
+
234
+ return raw_result
235
+
236
+ async def stream(
237
+ self,
238
+ *,
239
+ model: str,
240
+ prompt: Optional[str] = None,
241
+ messages: Optional[List[Dict[str, str]]] = None,
242
+ system_instruction: Optional[str] = None,
243
+ temperature: float = 0.1,
244
+ max_tokens: Optional[int] = None,
245
+ timeout: Optional[float] = None,
246
+ ) -> AsyncGenerator[str, None]:
247
+ """Streams an async chat completion chunk-by-chunk using Server-Sent Events."""
248
+ payload: Dict[str, Any] = {
249
+ "model": model,
250
+ "temperature": temperature,
251
+ "stream": True,
252
+ }
253
+ if prompt is not None:
254
+ payload["prompt"] = prompt
255
+ if messages is not None:
256
+ payload["messages"] = messages
257
+ if system_instruction is not None:
258
+ payload["system_instruction"] = system_instruction
259
+ if max_tokens is not None:
260
+ payload["max_tokens"] = max_tokens
261
+
262
+ async with self._client.client.stream(
263
+ "POST",
264
+ "/v1/chat/completions",
265
+ json=payload,
266
+ timeout=timeout or self._client.timeout,
267
+ ) as response:
268
+ if response.status_code != 200:
269
+ await response.aread()
270
+ self._client._handle_error_response(response)
271
+
272
+ async for line in response.aiter_lines():
273
+ if not line or not line.strip():
274
+ continue
275
+ line_str = line.strip()
276
+ if line_str.startswith("data: "):
277
+ content = line_str[6:].strip()
278
+ if content == "[DONE]":
279
+ break
280
+ try:
281
+ chunk = json.loads(content)
282
+ delta = (
283
+ chunk.get("choices", [{}])[0].get("delta", {}).get("content")
284
+ or chunk.get("content")
285
+ or chunk.get("delta")
286
+ or chunk.get("text", "")
287
+ )
288
+ if delta:
289
+ yield delta
290
+ except json.JSONDecodeError:
291
+ yield content
292
+
293
+
294
+ class ChatResource:
295
+ """Synchronous chat resource with nested completions."""
296
+
297
+ def __init__(self, client: BaseClient):
298
+ self._client = client
299
+ self.completions = CompletionsResource(client)
300
+
301
+ def __call__(
302
+ self,
303
+ model: str,
304
+ prompt: Optional[str] = None,
305
+ messages: Optional[List[Dict[str, str]]] = None,
306
+ response_model: Optional[Type[T]] = None,
307
+ tools: Optional[List[Any]] = None,
308
+ output_format: Optional[Union[str, Dict[str, Any]]] = None,
309
+ system_instruction: Optional[str] = None,
310
+ temperature: float = 0.1,
311
+ max_tokens: Optional[int] = None,
312
+ timeout: Optional[float] = None,
313
+ ) -> Union[str, T]:
314
+ """Allows direct invocation: client.chat(...)"""
315
+ return self.completions.create(
316
+ model=model,
317
+ prompt=prompt,
318
+ messages=messages,
319
+ response_model=response_model,
320
+ tools=tools,
321
+ output_format=output_format,
322
+ system_instruction=system_instruction,
323
+ temperature=temperature,
324
+ max_tokens=max_tokens,
325
+ timeout=timeout,
326
+ )
327
+
328
+ def stream(
329
+ self,
330
+ model: str,
331
+ prompt: Optional[str] = None,
332
+ messages: Optional[List[Dict[str, str]]] = None,
333
+ system_instruction: Optional[str] = None,
334
+ temperature: float = 0.1,
335
+ max_tokens: Optional[int] = None,
336
+ timeout: Optional[float] = None,
337
+ ) -> Generator[str, None, None]:
338
+ """Allows direct streaming invocation: client.chat.stream(...)"""
339
+ return self.completions.stream(
340
+ model=model,
341
+ prompt=prompt,
342
+ messages=messages,
343
+ system_instruction=system_instruction,
344
+ temperature=temperature,
345
+ max_tokens=max_tokens,
346
+ timeout=timeout,
347
+ )
348
+
349
+
350
+ class AsyncChatResource:
351
+ """Asynchronous chat resource with nested completions."""
352
+
353
+ def __init__(self, client: AsyncBaseClient):
354
+ self._client = client
355
+ self.completions = AsyncCompletionsResource(client)
356
+
357
+ async def __call__(
358
+ self,
359
+ model: str,
360
+ prompt: Optional[str] = None,
361
+ messages: Optional[List[Dict[str, str]]] = None,
362
+ response_model: Optional[Type[T]] = None,
363
+ tools: Optional[List[Any]] = None,
364
+ output_format: Optional[Union[str, Dict[str, Any]]] = None,
365
+ system_instruction: Optional[str] = None,
366
+ temperature: float = 0.1,
367
+ max_tokens: Optional[int] = None,
368
+ timeout: Optional[float] = None,
369
+ ) -> Union[str, T]:
370
+ """Allows direct async invocation: await client.chat(...)"""
371
+ return await self.completions.create(
372
+ model=model,
373
+ prompt=prompt,
374
+ messages=messages,
375
+ response_model=response_model,
376
+ tools=tools,
377
+ output_format=output_format,
378
+ system_instruction=system_instruction,
379
+ temperature=temperature,
380
+ max_tokens=max_tokens,
381
+ timeout=timeout,
382
+ )
383
+
384
+ def stream(
385
+ self,
386
+ model: str,
387
+ prompt: Optional[str] = None,
388
+ messages: Optional[List[Dict[str, str]]] = None,
389
+ system_instruction: Optional[str] = None,
390
+ temperature: float = 0.1,
391
+ max_tokens: Optional[int] = None,
392
+ timeout: Optional[float] = None,
393
+ ) -> AsyncGenerator[str, None]:
394
+ """Allows direct async streaming invocation: async for chunk in client.chat.stream(...)"""
395
+ return self.completions.stream(
396
+ model=model,
397
+ prompt=prompt,
398
+ messages=messages,
399
+ system_instruction=system_instruction,
400
+ temperature=temperature,
401
+ max_tokens=max_tokens,
402
+ timeout=timeout,
403
+ )
@@ -0,0 +1,71 @@
1
+ """LiveKit multimodal audio/video sessions resource for OmniAI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Optional
6
+ from omniai.types.livekit import LiveSessionInfo
7
+
8
+ if TYPE_CHECKING:
9
+ from omniai._base import BaseClient, AsyncBaseClient
10
+
11
+
12
+ class LiveKitResource:
13
+ """Synchronous LiveKit multimodal resource."""
14
+
15
+ def __init__(self, client: BaseClient):
16
+ self._client = client
17
+
18
+ def create_session(
19
+ self,
20
+ *,
21
+ model: str = "gemini:gemini-2.5-flash",
22
+ voice: str = "Aoede",
23
+ video_input: bool = False,
24
+ timeout: Optional[float] = None,
25
+ ) -> LiveSessionInfo:
26
+ """Requests room and participant credentials for a LiveKit WebRTC session."""
27
+ response = self._client.client.post(
28
+ "/v1/live/token",
29
+ json={
30
+ "model": model,
31
+ "voice": voice,
32
+ "video_input": video_input,
33
+ },
34
+ timeout=timeout or self._client.timeout,
35
+ )
36
+ if response.status_code != 200:
37
+ self._client._handle_error_response(response)
38
+
39
+ data = response.json()
40
+ return LiveSessionInfo.model_validate(data)
41
+
42
+
43
+ class AsyncLiveKitResource:
44
+ """Asynchronous LiveKit multimodal resource."""
45
+
46
+ def __init__(self, client: AsyncBaseClient):
47
+ self._client = client
48
+
49
+ async def create_session(
50
+ self,
51
+ *,
52
+ model: str = "gemini:gemini-2.5-flash",
53
+ voice: str = "Aoede",
54
+ video_input: bool = False,
55
+ timeout: Optional[float] = None,
56
+ ) -> LiveSessionInfo:
57
+ """Requests room and participant credentials for a LiveKit WebRTC session asynchronously."""
58
+ response = await self._client.client.post(
59
+ "/v1/live/token",
60
+ json={
61
+ "model": model,
62
+ "voice": voice,
63
+ "video_input": video_input,
64
+ },
65
+ timeout=timeout or self._client.timeout,
66
+ )
67
+ if response.status_code != 200:
68
+ self._client._handle_error_response(response)
69
+
70
+ data = response.json()
71
+ return LiveSessionInfo.model_validate(data)
@@ -0,0 +1,63 @@
1
+ """Models catalog resource for OmniAI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, List, Optional
6
+ from omniai.types.models import ModelInfo, ModelList
7
+
8
+ if TYPE_CHECKING:
9
+ from omniai._base import BaseClient, AsyncBaseClient
10
+
11
+
12
+ class ModelsResource:
13
+ """Synchronous models resource."""
14
+
15
+ def __init__(self, client: BaseClient):
16
+ self._client = client
17
+
18
+ def list(self, timeout: Optional[float] = None) -> ModelList:
19
+ """Fetches list of all AI models supported by the OmniAI platform."""
20
+ response = self._client.client.get(
21
+ "/v1/models",
22
+ timeout=timeout or self._client.timeout,
23
+ )
24
+ if response.status_code != 200:
25
+ self._client._handle_error_response(response)
26
+ return ModelList.model_validate(response.json())
27
+
28
+ def retrieve(self, model_id: str, timeout: Optional[float] = None) -> ModelInfo:
29
+ """Retrieves metadata for a specific model."""
30
+ response = self._client.client.get(
31
+ f"/v1/models/{model_id}",
32
+ timeout=timeout or self._client.timeout,
33
+ )
34
+ if response.status_code != 200:
35
+ self._client._handle_error_response(response)
36
+ return ModelInfo.model_validate(response.json())
37
+
38
+
39
+ class AsyncModelsResource:
40
+ """Asynchronous models resource."""
41
+
42
+ def __init__(self, client: AsyncBaseClient):
43
+ self._client = client
44
+
45
+ async def list(self, timeout: Optional[float] = None) -> ModelList:
46
+ """Fetches list of all AI models asynchronously."""
47
+ response = await self._client.client.get(
48
+ "/v1/models",
49
+ timeout=timeout or self._client.timeout,
50
+ )
51
+ if response.status_code != 200:
52
+ self._client._handle_error_response(response)
53
+ return ModelList.model_validate(response.json())
54
+
55
+ async def retrieve(self, model_id: str, timeout: Optional[float] = None) -> ModelInfo:
56
+ """Retrieves metadata for a specific model asynchronously."""
57
+ response = await self._client.client.get(
58
+ f"/v1/models/{model_id}",
59
+ timeout=timeout or self._client.timeout,
60
+ )
61
+ if response.status_code != 200:
62
+ self._client._handle_error_response(response)
63
+ return ModelInfo.model_validate(response.json())