askai-python 0.4.1__tar.gz → 0.5.0__tar.gz
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.
- {askai_python-0.4.1/askai_python.egg-info → askai_python-0.5.0}/PKG-INFO +6 -1
- {askai_python-0.4.1 → askai_python-0.5.0}/README.md +124 -3
- askai_python-0.5.0/ask_ai/__init__.py +79 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/base.py +298 -36
- askai_python-0.5.0/ask_ai/capabilities.py +17 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/cli.py +5 -19
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/config.py +21 -11
- askai_python-0.5.0/ask_ai/events.py +41 -0
- askai_python-0.5.0/ask_ai/exceptions.py +113 -0
- askai_python-0.5.0/ask_ai/media.py +477 -0
- askai_python-0.5.0/ask_ai/media_security.py +105 -0
- askai_python-0.5.0/ask_ai/observability.py +53 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/anthropic_provider.py +31 -16
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/azure_provider.py +4 -2
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/google_provider.py +27 -14
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/groq_provider.py +22 -14
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/openai_provider.py +80 -53
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/openrouter_provider.py +31 -12
- askai_python-0.5.0/ask_ai/requirements.txt +6 -0
- askai_python-0.5.0/ask_ai/router.py +107 -0
- askai_python-0.5.0/ask_ai/session.py +114 -0
- askai_python-0.5.0/ask_ai/structured.py +88 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/utils.py +3 -4
- {askai_python-0.4.1 → askai_python-0.5.0/askai_python.egg-info}/PKG-INFO +6 -1
- {askai_python-0.4.1 → askai_python-0.5.0}/askai_python.egg-info/SOURCES.txt +18 -1
- {askai_python-0.4.1 → askai_python-0.5.0}/askai_python.egg-info/requires.txt +6 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/pyproject.toml +4 -1
- askai_python-0.5.0/tests/test_capabilities.py +17 -0
- askai_python-0.5.0/tests/test_core.py +266 -0
- askai_python-0.5.0/tests/test_gitignore.py +12 -0
- askai_python-0.5.0/tests/test_media_security.py +193 -0
- askai_python-0.5.0/tests/test_observability.py +74 -0
- askai_python-0.5.0/tests/test_openrouter_release.py +70 -0
- askai_python-0.5.0/tests/test_python39_compat.py +13 -0
- askai_python-0.5.0/tests/test_router.py +60 -0
- askai_python-0.5.0/tests/test_session.py +81 -0
- askai_python-0.5.0/tests/test_streaming_events.py +79 -0
- askai_python-0.5.0/tests/test_structured.py +91 -0
- askai_python-0.4.1/ask_ai/__init__.py +0 -51
- askai_python-0.4.1/ask_ai/exceptions.py +0 -42
- askai_python-0.4.1/ask_ai/media.py +0 -90
- askai_python-0.4.1/ask_ai/requirements.txt +0 -5
- askai_python-0.4.1/tests/test_core.py +0 -38
- {askai_python-0.4.1 → askai_python-0.5.0}/LICENSE +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/__main__.py +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/ask_ai/providers/__init__.py +4 -4
- {askai_python-0.4.1 → askai_python-0.5.0}/askai_python.egg-info/dependency_links.txt +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/askai_python.egg-info/entry_points.txt +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/askai_python.egg-info/top_level.txt +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/docs/PYPI_README.md +0 -0
- {askai_python-0.4.1 → askai_python-0.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: askai-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: AI Made Stupid Simple. Unified API for OpenAI, Groq, Google, Anthropic, and more.
|
|
5
5
|
Author-email: Hossein Ghorbani <hosseingh1068@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -36,6 +36,9 @@ Provides-Extra: media
|
|
|
36
36
|
Requires-Dist: Pillow>=9.0.0; extra == "media"
|
|
37
37
|
Provides-Extra: httpx
|
|
38
38
|
Requires-Dist: httpx>=0.24.0; extra == "httpx"
|
|
39
|
+
Provides-Extra: structured
|
|
40
|
+
Requires-Dist: pydantic>=1.10; extra == "structured"
|
|
41
|
+
Requires-Dist: jsonschema>=4.0; extra == "structured"
|
|
39
42
|
Provides-Extra: all
|
|
40
43
|
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
41
44
|
Requires-Dist: anthropic>=0.20.0; extra == "all"
|
|
@@ -43,6 +46,8 @@ Requires-Dist: google-generativeai>=0.5.0; extra == "all"
|
|
|
43
46
|
Requires-Dist: groq>=0.4.0; extra == "all"
|
|
44
47
|
Requires-Dist: Pillow>=9.0.0; extra == "all"
|
|
45
48
|
Requires-Dist: httpx>=0.24.0; extra == "all"
|
|
49
|
+
Requires-Dist: pydantic>=1.10; extra == "all"
|
|
50
|
+
Requires-Dist: jsonschema>=4.0; extra == "all"
|
|
46
51
|
Dynamic: license-file
|
|
47
52
|
|
|
48
53
|
# askai-python 🚀
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
No frameworks. No servers. No overengineering.
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
|
+
|
|
25
26
|
[](https://pypi.org/project/askai-python/)
|
|
26
27
|
[](https://opensource.org/licenses/MIT)
|
|
27
28
|
[](https://www.python.org/downloads/)
|
|
@@ -159,7 +160,7 @@ for chunk in ai.ask_stream("Write a short story about a blue dragon"):
|
|
|
159
160
|
|
|
160
161
|
# Async Streaming
|
|
161
162
|
async def stream_example():
|
|
162
|
-
async for chunk in
|
|
163
|
+
async for chunk in ai.ask_stream_async("Write a short story"):
|
|
163
164
|
print(chunk, end="", flush=True)
|
|
164
165
|
```
|
|
165
166
|
|
|
@@ -221,15 +222,135 @@ print(user.name) # "Alice"
|
|
|
221
222
|
print(user.age) # 30
|
|
222
223
|
```
|
|
223
224
|
|
|
225
|
+
### ✅ Validated Structured Responses
|
|
226
|
+
|
|
227
|
+
Structured responses support Pydantic models, dataclasses, `TypedDict`, and JSON
|
|
228
|
+
Schema dictionaries. The raw provider text remains available separately:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from dataclasses import dataclass
|
|
232
|
+
from ask_ai import OpenAI
|
|
233
|
+
|
|
234
|
+
@dataclass
|
|
235
|
+
class User:
|
|
236
|
+
name: str
|
|
237
|
+
age: int
|
|
238
|
+
|
|
239
|
+
response = OpenAI().ask(
|
|
240
|
+
"Extract: Alice is 30.",
|
|
241
|
+
response_model=User,
|
|
242
|
+
validation_retries=2,
|
|
243
|
+
)
|
|
244
|
+
print(response.raw)
|
|
245
|
+
print(response.parsed.name)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Streaming Events
|
|
249
|
+
|
|
250
|
+
Use typed events for text, completion, and errors while keeping the legacy
|
|
251
|
+
`ask_stream()` API unchanged:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from ask_ai import Completed, OpenAI, TextDelta
|
|
255
|
+
|
|
256
|
+
for event in OpenAI().ask_stream_events("Tell me a story"):
|
|
257
|
+
if isinstance(event, TextDelta):
|
|
258
|
+
print(event.text, end="")
|
|
259
|
+
elif isinstance(event, Completed):
|
|
260
|
+
print("\nDone")
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 📊 Observability
|
|
264
|
+
|
|
265
|
+
Attach optional middleware to receive provider, model, latency, and retry
|
|
266
|
+
metadata. Provider usage extraction and failure-reason reporting are not
|
|
267
|
+
supported yet. Estimated cost is only calculated when a response already
|
|
268
|
+
contains usage data; the bundled provider adapters do not currently supply it:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
from ask_ai import Observability, OpenAI
|
|
272
|
+
|
|
273
|
+
ai = OpenAI().use(Observability(on_event=print))
|
|
274
|
+
response = ai.ask("Explain tracing")
|
|
275
|
+
print(response.metadata.latency_ms)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Provider Capabilities
|
|
279
|
+
|
|
280
|
+
Inspect advisory capability metadata before selecting a provider:
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from ask_ai import OpenAI
|
|
284
|
+
|
|
285
|
+
capabilities = OpenAI().capabilities()
|
|
286
|
+
if capabilities.structured_output:
|
|
287
|
+
print("Structured output is available")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Smart Router
|
|
291
|
+
|
|
292
|
+
Route requests across providers using deterministic `cheapest`, `fastest`, or
|
|
293
|
+
`most_reliable` policies. Supply static `profiles` for meaningful ordering:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from ask_ai import Groq, OpenAI, Router
|
|
297
|
+
|
|
298
|
+
openai = OpenAI() # Requires OPENAI_API_KEY
|
|
299
|
+
groq = Groq() # Requires GROQ_API_KEY
|
|
300
|
+
router = Router(
|
|
301
|
+
[openai, groq],
|
|
302
|
+
policy="fastest",
|
|
303
|
+
profiles={
|
|
304
|
+
"OpenAIProvider": {"latency_ms": 100},
|
|
305
|
+
"GroqProvider": {"latency_ms": 50},
|
|
306
|
+
},
|
|
307
|
+
)
|
|
308
|
+
response = router.ask("Explain routing")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 🔒 Secure Media Downloads
|
|
312
|
+
|
|
313
|
+
Provider media downloads can be constrained by HTTPS, host, detected MIME, size,
|
|
314
|
+
and timeout policy. DNS results resolving to private, loopback, link-local, or
|
|
315
|
+
reserved IP addresses are rejected by default. `allowed_hosts` is optional;
|
|
316
|
+
set it for an explicit hostname allowlist. Only opt in to `allow_private=True`
|
|
317
|
+
when downloading from trusted private-network hosts. Redirects are rejected:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
from ask_ai import MediaPolicy, download_media
|
|
321
|
+
|
|
322
|
+
data = download_media(
|
|
323
|
+
"https://example.com/image.png",
|
|
324
|
+
MediaPolicy(
|
|
325
|
+
max_bytes=10 * 1024 * 1024,
|
|
326
|
+
allowed_mime_types={"image/png"},
|
|
327
|
+
allowed_hosts={"example.com"},
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Conversation Sessions
|
|
333
|
+
|
|
334
|
+
Create optional in-memory conversation state without changing the normal
|
|
335
|
+
provider API:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
session = OpenAI().session(max_messages=20)
|
|
339
|
+
session.ask("My name is Ada.")
|
|
340
|
+
print(session.ask("What is my name?").text)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Session requests do not support `response_model` validation yet.
|
|
344
|
+
|
|
224
345
|
---
|
|
225
346
|
|
|
226
347
|
## 🗺️ Roadmap
|
|
227
348
|
|
|
228
349
|
### 🚀 Roadmap 2.0 (Active)
|
|
229
350
|
- [x] Provider Fallback chaining & Pydantic Structured Output support
|
|
230
|
-
- [
|
|
351
|
+
- [x] Multi-Modal Vision input (image to text) for all key providers
|
|
231
352
|
- [ ] Native support for tools/function calling execution
|
|
232
|
-
- [
|
|
353
|
+
- [x] Optional in-memory session-based conversation management
|
|
233
354
|
|
|
234
355
|
### 🏁 Roadmap 1.0 (Completed)
|
|
235
356
|
- ~~[x] OpenAI, Groq, Gemini, Anthropic, Azure, OpenRouter support~~
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ask-ai: A minimal Python SDK to switch between LLM providers in one line.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.5.0"
|
|
6
|
+
|
|
7
|
+
from .base import Response
|
|
8
|
+
from .capabilities import Capabilities
|
|
9
|
+
from .config import AdvancedConfig
|
|
10
|
+
from .events import Completed, Error, TextDelta, ToolCall, UsageUpdate
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
AskAIConfigError,
|
|
13
|
+
AskAIError,
|
|
14
|
+
AskAINetworkError,
|
|
15
|
+
AskAIParsingError,
|
|
16
|
+
AskAIRateLimitError,
|
|
17
|
+
MediaSecurityError,
|
|
18
|
+
MediaTypeNotSupportedError,
|
|
19
|
+
ProviderAuthError,
|
|
20
|
+
ProviderError,
|
|
21
|
+
ProviderInvalidRequestError,
|
|
22
|
+
ProviderRateLimitError,
|
|
23
|
+
ProviderTimeoutError,
|
|
24
|
+
ProviderUnavailableError,
|
|
25
|
+
StreamNotSupportedError,
|
|
26
|
+
StructuredValidationError,
|
|
27
|
+
)
|
|
28
|
+
from .media import AudioObject, ImageObject
|
|
29
|
+
from .media_security import MediaPolicy, download_media
|
|
30
|
+
from .observability import Observability, ResponseMetadata, Usage
|
|
31
|
+
from .providers import Anthropic, Azure, Google, Groq, OpenAI, OpenRouter
|
|
32
|
+
from .router import Router
|
|
33
|
+
from .session import ConversationSession
|
|
34
|
+
from .structured import parse_structured
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Providers
|
|
38
|
+
"OpenAI",
|
|
39
|
+
"Anthropic",
|
|
40
|
+
"Google",
|
|
41
|
+
"Groq",
|
|
42
|
+
"Azure",
|
|
43
|
+
"OpenRouter",
|
|
44
|
+
# Core
|
|
45
|
+
"AdvancedConfig",
|
|
46
|
+
"Response",
|
|
47
|
+
"ImageObject",
|
|
48
|
+
"AudioObject",
|
|
49
|
+
# Exceptions
|
|
50
|
+
"AskAIError",
|
|
51
|
+
"AskAIConfigError",
|
|
52
|
+
"AskAINetworkError",
|
|
53
|
+
"AskAIRateLimitError",
|
|
54
|
+
"AskAIParsingError",
|
|
55
|
+
"ProviderError",
|
|
56
|
+
"ProviderAuthError",
|
|
57
|
+
"ProviderRateLimitError",
|
|
58
|
+
"ProviderTimeoutError",
|
|
59
|
+
"ProviderInvalidRequestError",
|
|
60
|
+
"ProviderUnavailableError",
|
|
61
|
+
"MediaTypeNotSupportedError",
|
|
62
|
+
"MediaSecurityError",
|
|
63
|
+
"StreamNotSupportedError",
|
|
64
|
+
"StructuredValidationError",
|
|
65
|
+
"parse_structured",
|
|
66
|
+
"TextDelta",
|
|
67
|
+
"ToolCall",
|
|
68
|
+
"UsageUpdate",
|
|
69
|
+
"Completed",
|
|
70
|
+
"Error",
|
|
71
|
+
"Observability",
|
|
72
|
+
"ResponseMetadata",
|
|
73
|
+
"Usage",
|
|
74
|
+
"Capabilities",
|
|
75
|
+
"Router",
|
|
76
|
+
"ConversationSession",
|
|
77
|
+
"MediaPolicy",
|
|
78
|
+
"download_media",
|
|
79
|
+
]
|