askai-python 0.4.0__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.0 → askai_python-0.5.0}/PKG-INFO +67 -6
- askai_python-0.5.0/README.md +371 -0
- askai_python-0.5.0/ask_ai/__init__.py +79 -0
- askai_python-0.5.0/ask_ai/base.py +706 -0
- askai_python-0.5.0/ask_ai/capabilities.py +17 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/cli.py +5 -19
- {askai_python-0.4.0 → 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.0 → askai_python-0.5.0}/ask_ai/providers/anthropic_provider.py +31 -16
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/providers/azure_provider.py +4 -2
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/providers/google_provider.py +27 -14
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/providers/groq_provider.py +22 -14
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/providers/openai_provider.py +80 -53
- askai_python-0.5.0/ask_ai/providers/openrouter_provider.py +142 -0
- 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.0 → askai_python-0.5.0}/ask_ai/utils.py +3 -4
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/PKG-INFO +67 -6
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/SOURCES.txt +18 -1
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/requires.txt +6 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/docs/PYPI_README.md +61 -5
- {askai_python-0.4.0 → 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.0/README.md +0 -164
- askai_python-0.4.0/ask_ai/__init__.py +0 -51
- askai_python-0.4.0/ask_ai/base.py +0 -302
- askai_python-0.4.0/ask_ai/exceptions.py +0 -42
- askai_python-0.4.0/ask_ai/media.py +0 -90
- askai_python-0.4.0/ask_ai/providers/openrouter_provider.py +0 -66
- askai_python-0.4.0/ask_ai/requirements.txt +0 -5
- askai_python-0.4.0/tests/test_core.py +0 -38
- {askai_python-0.4.0 → askai_python-0.5.0}/LICENSE +0 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/__main__.py +0 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/ask_ai/providers/__init__.py +4 -4
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/dependency_links.txt +0 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/entry_points.txt +0 -0
- {askai_python-0.4.0 → askai_python-0.5.0}/askai_python.egg-info/top_level.txt +0 -0
- {askai_python-0.4.0 → 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 🚀
|
|
@@ -143,6 +148,54 @@ response = ai.ask(
|
|
|
143
148
|
)
|
|
144
149
|
```
|
|
145
150
|
|
|
151
|
+
## 🎨 Media Generation (Image & Audio)
|
|
152
|
+
|
|
153
|
+
Generate images or speech with compatible providers:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from ask_ai import OpenAI
|
|
157
|
+
|
|
158
|
+
ai = OpenAI()
|
|
159
|
+
|
|
160
|
+
# Generate an Image (DALL-E)
|
|
161
|
+
img_response = ai.ask("A majestic lion in a neon city", output_type="image")
|
|
162
|
+
img_response.save("lion.png")
|
|
163
|
+
|
|
164
|
+
# Generate Speech (TTS)
|
|
165
|
+
audio_response = ai.ask("Hello, this is a voice.", output_type="audio")
|
|
166
|
+
audio_response.save("welcome.mp3")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 🔗 Provider Fallback
|
|
170
|
+
|
|
171
|
+
Never experience downtime by supplying fallback providers:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from ask_ai import OpenAI, Groq
|
|
175
|
+
|
|
176
|
+
ai = OpenAI()
|
|
177
|
+
response = ai.ask("Explain quantum physics", providers=[ai, Groq])
|
|
178
|
+
print(response.text)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 📋 Structured Output (Pydantic Support)
|
|
182
|
+
|
|
183
|
+
Force LLMs to strictly respond matching a Pydantic schema:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from pydantic import BaseModel
|
|
187
|
+
from ask_ai import OpenAI
|
|
188
|
+
|
|
189
|
+
class User(BaseModel):
|
|
190
|
+
name: str
|
|
191
|
+
age: int
|
|
192
|
+
|
|
193
|
+
ai = OpenAI()
|
|
194
|
+
response = ai.ask("Extract name: Alice is 30.", response_model=User)
|
|
195
|
+
user = response.pydantic
|
|
196
|
+
print(user.name) # "Alice"
|
|
197
|
+
```
|
|
198
|
+
|
|
146
199
|
## 🔌 Supported Providers
|
|
147
200
|
|
|
148
201
|
| Provider | Class | Capabilities |
|
|
@@ -158,11 +211,19 @@ response = ai.ask(
|
|
|
158
211
|
|
|
159
212
|
## 🗺️ Roadmap
|
|
160
213
|
|
|
161
|
-
|
|
162
|
-
- [x]
|
|
163
|
-
- [ ]
|
|
164
|
-
- [ ]
|
|
165
|
-
- [ ]
|
|
214
|
+
### 🚀 Roadmap 2.0 (Active)
|
|
215
|
+
- [x] Provider Fallback chaining & Pydantic Structured Output support
|
|
216
|
+
- [ ] Multi-Modal Vision input (image to text) for all key providers
|
|
217
|
+
- [ ] Native support for tools/function calling execution
|
|
218
|
+
- [ ] Memory buffer / session-based conversation management
|
|
219
|
+
|
|
220
|
+
### 🏁 Roadmap 1.0 (Completed)
|
|
221
|
+
- ~~[x] Baseline multiple providers~~
|
|
222
|
+
- ~~[x] Automated Retry & Timeout controls~~
|
|
223
|
+
- ~~[x] Async API (`await ask_async`)~~
|
|
224
|
+
- ~~[x] Streaming Support (`ask_stream` / `ask_stream_async`)~~
|
|
225
|
+
- ~~[x] Provider Fallback (`fallback=[Groq()]`)~~
|
|
226
|
+
- ~~[x] Structured Outputs (Pydantic Support)~~
|
|
166
227
|
|
|
167
228
|
---
|
|
168
229
|
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# ask-ai
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
🌍 <b>Readme:</b>
|
|
5
|
+
<a href="README.md"><img src="https://flagcdn.com/20x15/us.png" alt="English"> English</a> ·
|
|
6
|
+
<a href="docs/README_fa.md"><img src="https://flagcdn.com/20x15/ir.png" alt="Persian"> فارسی</a> ·
|
|
7
|
+
<a href="docs/README_zh.md"><img src="https://flagcdn.com/20x15/cn.png" alt="Chinese"> 中文</a> ·
|
|
8
|
+
<a href="docs/README_tr.md"><img src="https://flagcdn.com/20x15/tr.png" alt="Turkish"> Türkçe</a> ·
|
|
9
|
+
<a href="docs/README_ar.md"><img src="https://flagcdn.com/20x15/sa.png" alt="Arabic"> العربية</a> ·
|
|
10
|
+
<a href="docs/README_ru.md"><img src="https://flagcdn.com/20x15/ru.png" alt="Russian"> Русский</a> ·
|
|
11
|
+
<a href="docs/README_es.md"><img src="https://flagcdn.com/20x15/es.png" alt="Spanish"> Español</a> ·
|
|
12
|
+
<a href="docs/README_ja.md"><img src="https://flagcdn.com/20x15/jp.png" alt="Japanese"> 日本語</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="assets/banner.svg" alt="ask-ai animated banner" width="100%">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<b>A minimal Python SDK to switch between LLM providers in one line.</b><br/>
|
|
22
|
+
No frameworks. No servers. No overengineering.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/askai-python/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](https://www.python.org/downloads/)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ⚡ Quick Start (5 seconds)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install askai-python
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from ask_ai import OpenAI, Groq
|
|
42
|
+
|
|
43
|
+
# Auto-detects OPENAI_API_KEY from environment
|
|
44
|
+
OpenAI().ask("Explain black holes like I'm 5").text
|
|
45
|
+
|
|
46
|
+
# Switch provider effortlessly
|
|
47
|
+
Groq().ask("Explain black holes like I'm 5").text
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🧐 Why ask-ai?
|
|
53
|
+
|
|
54
|
+
- **One function**: Just call `.ask()`
|
|
55
|
+
- **Multiple providers**: OpenAI, Anthropic, Google Gemini, Groq, Azure, OpenRouter
|
|
56
|
+
- **Zero config**: Keys are pulled from the environment automatically
|
|
57
|
+
- **SDK-first, not a framework**: It stays out of your way.
|
|
58
|
+
|
|
59
|
+
## 🚫 What this project is NOT
|
|
60
|
+
|
|
61
|
+
> ❌ Not an AI framework
|
|
62
|
+
> ❌ Not an API gateway
|
|
63
|
+
> ❌ Not an agent memory system
|
|
64
|
+
|
|
65
|
+
It does one thing perfectly: **Simplifying the API call to LLMs.**
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🛠️ Advanced Usage
|
|
70
|
+
|
|
71
|
+
### 🧰 Developer QoL Utilities (Auto-Parsing)
|
|
72
|
+
|
|
73
|
+
<p align="center">
|
|
74
|
+
<img src="assets/features.svg" alt="ask-ai features parsing banner" width="100%">
|
|
75
|
+
</p>
|
|
76
|
+
Stop writing Regex to clean up model outputs! `ask-ai` comes with built-in text processing flags:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from ask_ai import OpenAI
|
|
80
|
+
ai = OpenAI()
|
|
81
|
+
|
|
82
|
+
# 1. Clean Markdown (Removes ```json and ``` tags)
|
|
83
|
+
# Perfect for extracting raw data from models that wrap everything in markdown
|
|
84
|
+
clean_text = ai.ask("Write JSON", clean=True).text
|
|
85
|
+
|
|
86
|
+
# 2. Extract Code (Returns ONLY the code block, ignores conversational filler)
|
|
87
|
+
# Great for automation pipelines
|
|
88
|
+
code = ai.ask("Write a python ping script", code=True).text
|
|
89
|
+
|
|
90
|
+
# 3. Strip Tags (Removes <think> blocks and HTML)
|
|
91
|
+
# Essential for reasoning models like DeepSeek-R1
|
|
92
|
+
answer_only = ai.ask("What is 1+1?", strip=True).text
|
|
93
|
+
|
|
94
|
+
# 4. Enforce & Parse JSON (Directly returns a Parsed Python Dictionary)
|
|
95
|
+
# Adds JSON instructions to the prompt and safely runs json.loads()
|
|
96
|
+
data_dict = ai.ask("Extract user info", json=True).json
|
|
97
|
+
print(data_dict['name'])
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 🔄 Built-in Retries & Timeout (Resiliency)
|
|
101
|
+
|
|
102
|
+
<p align="center">
|
|
103
|
+
<img src="assets/resiliency.svg" alt="ask-ai resiliency banner" width="100%">
|
|
104
|
+
</p>
|
|
105
|
+
Handle rate limits (`429`) and network drops automatically:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from ask_ai import OpenAI
|
|
109
|
+
ai = OpenAI()
|
|
110
|
+
|
|
111
|
+
# Will retry up to 3 times on RateLimit/Network errors, with generic timeout
|
|
112
|
+
response = ai.ask("Write a script", retry=3, timeout=10)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### System Configuration
|
|
116
|
+
Set system prompts and temperature directly:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
ai.advanced(
|
|
120
|
+
temperature=0.7,
|
|
121
|
+
prompt="You are a senior DevOps engineer."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
print(ai.ask("How do I optimize a Dockerfile?").text)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 🚀 Async Support (FastAPI / Aiogram)
|
|
130
|
+
|
|
131
|
+
`ask-ai` works perfectly in asynchronous environments:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import asyncio
|
|
135
|
+
from ask_ai import OpenAI
|
|
136
|
+
|
|
137
|
+
async def main():
|
|
138
|
+
ai = OpenAI()
|
|
139
|
+
# Runs non-blocking in your event loop
|
|
140
|
+
response = await ai.ask_async("What is the capital of France?")
|
|
141
|
+
print(response.text)
|
|
142
|
+
|
|
143
|
+
asyncio.run(main())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### 🌊 Streaming Support (Sync & Async)
|
|
149
|
+
|
|
150
|
+
Stream responses token-by-token easily:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from ask_ai import OpenAI
|
|
154
|
+
|
|
155
|
+
ai = OpenAI()
|
|
156
|
+
|
|
157
|
+
# Sync Streaming
|
|
158
|
+
for chunk in ai.ask_stream("Write a short story about a blue dragon"):
|
|
159
|
+
print(chunk, end="", flush=True)
|
|
160
|
+
|
|
161
|
+
# Async Streaming
|
|
162
|
+
async def stream_example():
|
|
163
|
+
async for chunk in ai.ask_stream_async("Write a short story"):
|
|
164
|
+
print(chunk, end="", flush=True)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### 🎨 Media Generation (Image & Audio)
|
|
170
|
+
|
|
171
|
+
Generate images or speech with compatible providers:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from ask_ai import OpenAI
|
|
175
|
+
|
|
176
|
+
ai = OpenAI()
|
|
177
|
+
|
|
178
|
+
# Generate an Image (DALL-E)
|
|
179
|
+
img_response = ai.ask("A majestic lion in a neon city", output_type="image")
|
|
180
|
+
img_response.save("lion.png")
|
|
181
|
+
|
|
182
|
+
# Generate Speech (TTS)
|
|
183
|
+
audio_response = ai.ask("Hello, this is a generated voice.", output_type="audio")
|
|
184
|
+
audio_response.save("welcome.mp3")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### 🔗 Provider Fallback
|
|
190
|
+
|
|
191
|
+
Never experience downtime by supplying alternative fallback providers:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from ask_ai import OpenAI, Groq
|
|
195
|
+
|
|
196
|
+
ai = OpenAI()
|
|
197
|
+
|
|
198
|
+
# If OpenAI fails or rate-limits, it automatically switches to Groq
|
|
199
|
+
response = ai.ask("Explain quantum physics", providers=[ai, Groq])
|
|
200
|
+
print(response.text)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### 📋 Structured Output (Pydantic Support)
|
|
206
|
+
|
|
207
|
+
Force LLMs to strictly respond matching a Pydantic schema:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from pydantic import BaseModel
|
|
211
|
+
from ask_ai import OpenAI
|
|
212
|
+
|
|
213
|
+
class User(BaseModel):
|
|
214
|
+
name: str
|
|
215
|
+
age: int
|
|
216
|
+
|
|
217
|
+
ai = OpenAI()
|
|
218
|
+
response = ai.ask("Extract name: Alice is 30 years old.", response_model=User)
|
|
219
|
+
|
|
220
|
+
user = response.pydantic
|
|
221
|
+
print(user.name) # "Alice"
|
|
222
|
+
print(user.age) # 30
|
|
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
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 🗺️ Roadmap
|
|
348
|
+
|
|
349
|
+
### 🚀 Roadmap 2.0 (Active)
|
|
350
|
+
- [x] Provider Fallback chaining & Pydantic Structured Output support
|
|
351
|
+
- [x] Multi-Modal Vision input (image to text) for all key providers
|
|
352
|
+
- [ ] Native support for tools/function calling execution
|
|
353
|
+
- [x] Optional in-memory session-based conversation management
|
|
354
|
+
|
|
355
|
+
### 🏁 Roadmap 1.0 (Completed)
|
|
356
|
+
- ~~[x] OpenAI, Groq, Gemini, Anthropic, Azure, OpenRouter support~~
|
|
357
|
+
- ~~[x] Text to Image (DALL-E) and Text to Audio~~
|
|
358
|
+
- ~~[x] Automated Retry & Timeout controls~~
|
|
359
|
+
- ~~[x] Async Support (`await ask_async`)~~
|
|
360
|
+
- ~~[x] Streaming Support (`ask_stream` / `ask_stream_async`)~~
|
|
361
|
+
- ~~[x] Provider Fallback (`ask(..., providers=[OpenAI, Groq])`)~~
|
|
362
|
+
- ~~[x] Structured Output (Pydantic Support)~~
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## 🔗 Important Links
|
|
367
|
+
|
|
368
|
+
- **PyPI**: [askai-python](https://pypi.org/project/askai-python/)
|
|
369
|
+
- **GitHub Repository**: [Hosseinghorbani0/ask-ai](https://github.com/Hosseinghorbani0/ask-ai) (Star us! ⭐)
|
|
370
|
+
- **Official Website**: [hosseinghorbani0.ir](https://hosseinghorbani0.ir/)
|
|
371
|
+
- **Bug Tracker**: [Report an Issue](https://github.com/Hosseinghorbani0/ask-ai/issues)
|
|
@@ -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
|
+
]
|