vikky 0.1.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.
vikky-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VSP AI & Robotics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
vikky-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: vikky
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Vikky Platform: chat, vision, speech, embeddings, images and more with one key.
5
+ Keywords: vikky,vikkyverse,ai,llm,sdk,api
6
+ Author: VSP AI & Robotics
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Typing :: Typed
15
+ Requires-Dist: openai>=1.55.3
16
+ Requires-Python: >=3.9
17
+ Project-URL: Homepage, https://vikkyverse.com
18
+ Project-URL: Documentation, https://vikkyverse.com/docs
19
+ Project-URL: Get an API key, https://vikkyverse.com/platform
20
+ Description-Content-Type: text/markdown
21
+
22
+ # vikky
23
+
24
+ Python client for **Vikky**, VSP's AI API gateway. Vikky speaks the OpenAI API,
25
+ so this package is a thin wrapper over the official `openai` package: anything
26
+ in the OpenAI Python docs works here too.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install vikky
32
+ ```
33
+
34
+ In Colab or Jupyter, use `%pip install vikky`.
35
+
36
+ ## Get a key
37
+
38
+ Sign in to the Vikky console at https://vikkyverse.com/platform and create an API key. Keep it secret: anyone with
39
+ the key spends your quota.
40
+
41
+ ## Set VIKKY_API_KEY
42
+
43
+ On your laptop:
44
+
45
+ ```bash
46
+ export VIKKY_API_KEY="your-key"
47
+ ```
48
+
49
+ In Google Colab: click the key icon in the left sidebar, add a secret named
50
+ `VIKKY_API_KEY`, turn on "Notebook access", then run:
51
+
52
+ ```python
53
+ import os
54
+ from google.colab import userdata
55
+
56
+ os.environ["VIKKY_API_KEY"] = userdata.get("VIKKY_API_KEY")
57
+ ```
58
+
59
+ Never paste the key into a notebook cell you might share.
60
+
61
+ ## First call (JSON out)
62
+
63
+ ```python
64
+ import json
65
+ from vikky import Vikky
66
+
67
+ client = Vikky() # reads VIKKY_API_KEY
68
+
69
+ resp = client.chat.completions.create(
70
+ model="vikky-chat",
71
+ messages=[
72
+ {"role": "system", "content": "Reply in JSON."},
73
+ {"role": "user", "content": 'List 3 planets as {"planets": [...]}'},
74
+ ],
75
+ response_format={"type": "json_object"},
76
+ )
77
+ data = json.loads(resp.choices[0].message.content)
78
+ print(data["planets"])
79
+ ```
80
+
81
+ JSON mode works best when your messages say "JSON" and show the shape you want.
82
+
83
+ ## Streaming
84
+
85
+ ```python
86
+ stream = client.chat.completions.create(
87
+ model="vikky-chat",
88
+ messages=[{"role": "user", "content": "Explain PID control in 3 lines."}],
89
+ stream=True,
90
+ )
91
+ for chunk in stream:
92
+ if chunk.choices:
93
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
94
+ ```
95
+
96
+ ## Models
97
+
98
+ | Model | What it does | Call it with |
99
+ |---|---|---|
100
+ | `vikky-chat` | chat, JSON output, tool calling | `chat.completions.create`, or `responses.create` |
101
+ | `vikky-vision` | chat that also takes images | `chat.completions.create` with an `image_url` part |
102
+ | `vikky-embed` | embeddings | `embeddings.create` |
103
+ | `vikky-transcribe` | audio to text | `audio.transcriptions.create` |
104
+ | `vikky-speech` | text to audio | `audio.speech.create` |
105
+ | `vikky-image` | image generation and editing | `images.generate`, `images.edit` |
106
+ | `vikky-video` | video generation, async | `videos.create_and_poll` |
107
+ | `vikky-rerank` | rank documents against a query | `rerank` |
108
+ | `vikky-ocr` | text out of a document | `ocr` |
109
+ | `vikky-moderate` | content moderation | `moderations.create` |
110
+
111
+ Every row except the last two is a method the `openai` package already has, so
112
+ the OpenAI Python docs apply unchanged. `rerank` and `ocr` are not OpenAI
113
+ routes: they are the only two methods this package adds, and they return a
114
+ plain dict instead of a typed object.
115
+
116
+ ## Tool calling
117
+
118
+ ```python
119
+ resp = client.chat.completions.create(
120
+ model="vikky-chat",
121
+ messages=[{"role": "user", "content": "Weather in Hyderabad?"}],
122
+ tools=[{"type": "function", "function": {
123
+ "name": "get_weather",
124
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}},
125
+ }}],
126
+ )
127
+ call = resp.choices[0].message.tool_calls[0]
128
+ print(call.function.name, call.function.arguments)
129
+ ```
130
+
131
+ ## Responses API
132
+
133
+ ```python
134
+ resp = client.responses.create(model="vikky-chat", input="Say hello.")
135
+ print(resp.output_text)
136
+ ```
137
+
138
+ ## Vision
139
+
140
+ ```python
141
+ resp = client.chat.completions.create(
142
+ model="vikky-vision",
143
+ messages=[{"role": "user", "content": [
144
+ {"type": "text", "text": "What is in this picture?"},
145
+ {"type": "image_url", "image_url": {"url": "https://example.com/arm.jpg"}},
146
+ ]}],
147
+ )
148
+ print(resp.choices[0].message.content)
149
+ ```
150
+
151
+ For a local image, pass a data URL as the `url`:
152
+
153
+ ```python
154
+ import base64, pathlib
155
+
156
+ raw = base64.b64encode(pathlib.Path("arm.png").read_bytes()).decode()
157
+ url = f"data:image/png;base64,{raw}"
158
+ ```
159
+
160
+ ## Embeddings
161
+
162
+ ```python
163
+ resp = client.embeddings.create(model="vikky-embed", input=["robot arm", "pizza"])
164
+ arm, pizza = (d.embedding for d in resp.data)
165
+ print(len(arm), len(pizza))
166
+ ```
167
+
168
+ Pass a list to embed a batch in one call. Results come back in the order you
169
+ sent them, and `d.index` tells you which input each vector belongs to.
170
+
171
+ ## Audio to text
172
+
173
+ ```python
174
+ with open("meeting.m4a", "rb") as f:
175
+ resp = client.audio.transcriptions.create(model="vikky-transcribe", file=f)
176
+ print(resp.text)
177
+ ```
178
+
179
+ ## Text to audio
180
+
181
+ ```python
182
+ resp = client.audio.speech.create(
183
+ model="vikky-speech",
184
+ voice="alloy",
185
+ input="The arm is homed and ready.",
186
+ )
187
+ resp.write_to_file("ready.mp3")
188
+ ```
189
+
190
+ ## Images
191
+
192
+ ```python
193
+ resp = client.images.generate(model="vikky-image", prompt="a blue robot arm on a bench", n=1)
194
+ item = resp.data[0]
195
+ print(item.url or "returned as base64") # either one, see "Saving generated media"
196
+
197
+ with open("arm.png", "rb") as f:
198
+ edited = client.images.edit(model="vikky-image", image=f, prompt="make the bench wooden")
199
+ ```
200
+
201
+ ## Video
202
+
203
+ Video generation takes minutes, so it is a job, not a call. Submit it, wait for
204
+ it, then download it. Keep the id `create` gave you and download with that one.
205
+
206
+ ```python
207
+ job = client.videos.create(model="vikky-video", prompt="a robot arm picking up a cube")
208
+ done = client.videos.poll(job.id, poll_interval_ms=5000)
209
+ assert done.status == "completed", done.error
210
+ client.videos.download_content(job.id).write_to_file("clip.mp4")
211
+ ```
212
+
213
+ `client.videos.retrieve(job.id)` is the single-shot version of `poll`, if you
214
+ want to show `video.progress` in your own loop. Do not use
215
+ `videos.create_and_poll`: it only returns the polled job, and Vikky's polled id
216
+ cannot be downloaded from.
217
+
218
+ ## Saving generated media
219
+
220
+ An image comes back as either a URL or base64, depending on what you asked
221
+ for. Audio and video come back as a binary response with `write_to_file`.
222
+
223
+ ```python
224
+ import base64, pathlib, urllib.request
225
+
226
+ item = client.images.generate(model="vikky-image", prompt="a blue cube").data[0]
227
+ if item.b64_json:
228
+ pathlib.Path("cube.png").write_bytes(base64.b64decode(item.b64_json))
229
+ else:
230
+ with urllib.request.urlopen(item.url) as r:
231
+ pathlib.Path("cube.png").write_bytes(r.read())
232
+ ```
233
+
234
+ Ask for `response_format="b64_json"` and you never have to fetch a URL at all.
235
+
236
+ **A generated file's URL is temporary.** Download it in the same run that
237
+ created it. Do not store the URL in a database or a notebook output and expect
238
+ it to still work tomorrow.
239
+
240
+ ## Rerank
241
+
242
+ ```python
243
+ resp = client.rerank(
244
+ query="how do I reset the arm?",
245
+ documents=[
246
+ "Press the red button to reset the arm.",
247
+ "Our office is in Hyderabad.",
248
+ ],
249
+ top_n=1,
250
+ )
251
+ for r in resp["results"]:
252
+ print(r["index"], r["relevance_score"])
253
+ ```
254
+
255
+ Results come back best first. `r["index"]` points back into the `documents`
256
+ list you sent.
257
+
258
+ ## OCR
259
+
260
+ ```python
261
+ resp = client.ocr(document={"type": "document_url", "document_url": "https://example.com/invoice.pdf"})
262
+ for page in resp["pages"]:
263
+ print(page["markdown"])
264
+ ```
265
+
266
+ For an image instead of a PDF, send
267
+ `{"type": "image_url", "image_url": "https://..."}`. A data URL works too.
268
+
269
+ ## Moderation
270
+
271
+ ```python
272
+ resp = client.moderations.create(model="vikky-moderate", input="Some user text.")
273
+ result = resp.results[0]
274
+ print(result.flagged, [name for name, hit in result.categories if hit])
275
+ ```
276
+
277
+ ## Async
278
+
279
+ ```python
280
+ import asyncio
281
+ from vikky import AsyncVikky
282
+
283
+ async def main():
284
+ client = AsyncVikky()
285
+ resp = await client.chat.completions.create(
286
+ model="vikky-chat",
287
+ messages=[{"role": "user", "content": "Say hello."}],
288
+ )
289
+ print(resp.choices[0].message.content)
290
+
291
+ asyncio.run(main()) # in Colab or Jupyter, use: await main()
292
+ ```
293
+
294
+ Every model above works on `AsyncVikky`, `rerank` and `ocr` included: same
295
+ arguments, awaited.
296
+
297
+ ## Environment variables
298
+
299
+ | Variable | Required | Default |
300
+ |---|---|---|
301
+ | `VIKKY_API_KEY` | yes | none. `OPENAI_API_KEY` is never used. |
302
+ | `VIKKY_BASE_URL` | no | `https://api.vikkyverse.com/v1` |
303
+
304
+ Arguments win over environment: `Vikky(api_key=..., base_url=...)`. Every other
305
+ argument (`timeout`, `max_retries`, ...) goes straight to `openai.OpenAI`.
306
+ A missing key raises `vikky.VikkyError`.
307
+
308
+ ## If Vikky is down
309
+
310
+ Write lab code so the model name comes from the environment:
311
+
312
+ ```python
313
+ import os
314
+ from vikky import Vikky
315
+
316
+ MODEL = os.environ.get("VIKKY_MODEL", "vikky-chat")
317
+ client = Vikky()
318
+ resp = client.chat.completions.create(model=MODEL, messages=[...])
319
+ ```
320
+
321
+ Then a trainer can set `VIKKY_BASE_URL`, `VIKKY_API_KEY` and `VIKKY_MODEL` to
322
+ any other OpenAI-compatible endpoint, and the notebook runs unchanged.
323
+
324
+ ## License
325
+
326
+ MIT
vikky-0.1.0/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # vikky
2
+
3
+ Python client for **Vikky**, VSP's AI API gateway. Vikky speaks the OpenAI API,
4
+ so this package is a thin wrapper over the official `openai` package: anything
5
+ in the OpenAI Python docs works here too.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install vikky
11
+ ```
12
+
13
+ In Colab or Jupyter, use `%pip install vikky`.
14
+
15
+ ## Get a key
16
+
17
+ Sign in to the Vikky console at https://vikkyverse.com/platform and create an API key. Keep it secret: anyone with
18
+ the key spends your quota.
19
+
20
+ ## Set VIKKY_API_KEY
21
+
22
+ On your laptop:
23
+
24
+ ```bash
25
+ export VIKKY_API_KEY="your-key"
26
+ ```
27
+
28
+ In Google Colab: click the key icon in the left sidebar, add a secret named
29
+ `VIKKY_API_KEY`, turn on "Notebook access", then run:
30
+
31
+ ```python
32
+ import os
33
+ from google.colab import userdata
34
+
35
+ os.environ["VIKKY_API_KEY"] = userdata.get("VIKKY_API_KEY")
36
+ ```
37
+
38
+ Never paste the key into a notebook cell you might share.
39
+
40
+ ## First call (JSON out)
41
+
42
+ ```python
43
+ import json
44
+ from vikky import Vikky
45
+
46
+ client = Vikky() # reads VIKKY_API_KEY
47
+
48
+ resp = client.chat.completions.create(
49
+ model="vikky-chat",
50
+ messages=[
51
+ {"role": "system", "content": "Reply in JSON."},
52
+ {"role": "user", "content": 'List 3 planets as {"planets": [...]}'},
53
+ ],
54
+ response_format={"type": "json_object"},
55
+ )
56
+ data = json.loads(resp.choices[0].message.content)
57
+ print(data["planets"])
58
+ ```
59
+
60
+ JSON mode works best when your messages say "JSON" and show the shape you want.
61
+
62
+ ## Streaming
63
+
64
+ ```python
65
+ stream = client.chat.completions.create(
66
+ model="vikky-chat",
67
+ messages=[{"role": "user", "content": "Explain PID control in 3 lines."}],
68
+ stream=True,
69
+ )
70
+ for chunk in stream:
71
+ if chunk.choices:
72
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
73
+ ```
74
+
75
+ ## Models
76
+
77
+ | Model | What it does | Call it with |
78
+ |---|---|---|
79
+ | `vikky-chat` | chat, JSON output, tool calling | `chat.completions.create`, or `responses.create` |
80
+ | `vikky-vision` | chat that also takes images | `chat.completions.create` with an `image_url` part |
81
+ | `vikky-embed` | embeddings | `embeddings.create` |
82
+ | `vikky-transcribe` | audio to text | `audio.transcriptions.create` |
83
+ | `vikky-speech` | text to audio | `audio.speech.create` |
84
+ | `vikky-image` | image generation and editing | `images.generate`, `images.edit` |
85
+ | `vikky-video` | video generation, async | `videos.create_and_poll` |
86
+ | `vikky-rerank` | rank documents against a query | `rerank` |
87
+ | `vikky-ocr` | text out of a document | `ocr` |
88
+ | `vikky-moderate` | content moderation | `moderations.create` |
89
+
90
+ Every row except the last two is a method the `openai` package already has, so
91
+ the OpenAI Python docs apply unchanged. `rerank` and `ocr` are not OpenAI
92
+ routes: they are the only two methods this package adds, and they return a
93
+ plain dict instead of a typed object.
94
+
95
+ ## Tool calling
96
+
97
+ ```python
98
+ resp = client.chat.completions.create(
99
+ model="vikky-chat",
100
+ messages=[{"role": "user", "content": "Weather in Hyderabad?"}],
101
+ tools=[{"type": "function", "function": {
102
+ "name": "get_weather",
103
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}},
104
+ }}],
105
+ )
106
+ call = resp.choices[0].message.tool_calls[0]
107
+ print(call.function.name, call.function.arguments)
108
+ ```
109
+
110
+ ## Responses API
111
+
112
+ ```python
113
+ resp = client.responses.create(model="vikky-chat", input="Say hello.")
114
+ print(resp.output_text)
115
+ ```
116
+
117
+ ## Vision
118
+
119
+ ```python
120
+ resp = client.chat.completions.create(
121
+ model="vikky-vision",
122
+ messages=[{"role": "user", "content": [
123
+ {"type": "text", "text": "What is in this picture?"},
124
+ {"type": "image_url", "image_url": {"url": "https://example.com/arm.jpg"}},
125
+ ]}],
126
+ )
127
+ print(resp.choices[0].message.content)
128
+ ```
129
+
130
+ For a local image, pass a data URL as the `url`:
131
+
132
+ ```python
133
+ import base64, pathlib
134
+
135
+ raw = base64.b64encode(pathlib.Path("arm.png").read_bytes()).decode()
136
+ url = f"data:image/png;base64,{raw}"
137
+ ```
138
+
139
+ ## Embeddings
140
+
141
+ ```python
142
+ resp = client.embeddings.create(model="vikky-embed", input=["robot arm", "pizza"])
143
+ arm, pizza = (d.embedding for d in resp.data)
144
+ print(len(arm), len(pizza))
145
+ ```
146
+
147
+ Pass a list to embed a batch in one call. Results come back in the order you
148
+ sent them, and `d.index` tells you which input each vector belongs to.
149
+
150
+ ## Audio to text
151
+
152
+ ```python
153
+ with open("meeting.m4a", "rb") as f:
154
+ resp = client.audio.transcriptions.create(model="vikky-transcribe", file=f)
155
+ print(resp.text)
156
+ ```
157
+
158
+ ## Text to audio
159
+
160
+ ```python
161
+ resp = client.audio.speech.create(
162
+ model="vikky-speech",
163
+ voice="alloy",
164
+ input="The arm is homed and ready.",
165
+ )
166
+ resp.write_to_file("ready.mp3")
167
+ ```
168
+
169
+ ## Images
170
+
171
+ ```python
172
+ resp = client.images.generate(model="vikky-image", prompt="a blue robot arm on a bench", n=1)
173
+ item = resp.data[0]
174
+ print(item.url or "returned as base64") # either one, see "Saving generated media"
175
+
176
+ with open("arm.png", "rb") as f:
177
+ edited = client.images.edit(model="vikky-image", image=f, prompt="make the bench wooden")
178
+ ```
179
+
180
+ ## Video
181
+
182
+ Video generation takes minutes, so it is a job, not a call. Submit it, wait for
183
+ it, then download it. Keep the id `create` gave you and download with that one.
184
+
185
+ ```python
186
+ job = client.videos.create(model="vikky-video", prompt="a robot arm picking up a cube")
187
+ done = client.videos.poll(job.id, poll_interval_ms=5000)
188
+ assert done.status == "completed", done.error
189
+ client.videos.download_content(job.id).write_to_file("clip.mp4")
190
+ ```
191
+
192
+ `client.videos.retrieve(job.id)` is the single-shot version of `poll`, if you
193
+ want to show `video.progress` in your own loop. Do not use
194
+ `videos.create_and_poll`: it only returns the polled job, and Vikky's polled id
195
+ cannot be downloaded from.
196
+
197
+ ## Saving generated media
198
+
199
+ An image comes back as either a URL or base64, depending on what you asked
200
+ for. Audio and video come back as a binary response with `write_to_file`.
201
+
202
+ ```python
203
+ import base64, pathlib, urllib.request
204
+
205
+ item = client.images.generate(model="vikky-image", prompt="a blue cube").data[0]
206
+ if item.b64_json:
207
+ pathlib.Path("cube.png").write_bytes(base64.b64decode(item.b64_json))
208
+ else:
209
+ with urllib.request.urlopen(item.url) as r:
210
+ pathlib.Path("cube.png").write_bytes(r.read())
211
+ ```
212
+
213
+ Ask for `response_format="b64_json"` and you never have to fetch a URL at all.
214
+
215
+ **A generated file's URL is temporary.** Download it in the same run that
216
+ created it. Do not store the URL in a database or a notebook output and expect
217
+ it to still work tomorrow.
218
+
219
+ ## Rerank
220
+
221
+ ```python
222
+ resp = client.rerank(
223
+ query="how do I reset the arm?",
224
+ documents=[
225
+ "Press the red button to reset the arm.",
226
+ "Our office is in Hyderabad.",
227
+ ],
228
+ top_n=1,
229
+ )
230
+ for r in resp["results"]:
231
+ print(r["index"], r["relevance_score"])
232
+ ```
233
+
234
+ Results come back best first. `r["index"]` points back into the `documents`
235
+ list you sent.
236
+
237
+ ## OCR
238
+
239
+ ```python
240
+ resp = client.ocr(document={"type": "document_url", "document_url": "https://example.com/invoice.pdf"})
241
+ for page in resp["pages"]:
242
+ print(page["markdown"])
243
+ ```
244
+
245
+ For an image instead of a PDF, send
246
+ `{"type": "image_url", "image_url": "https://..."}`. A data URL works too.
247
+
248
+ ## Moderation
249
+
250
+ ```python
251
+ resp = client.moderations.create(model="vikky-moderate", input="Some user text.")
252
+ result = resp.results[0]
253
+ print(result.flagged, [name for name, hit in result.categories if hit])
254
+ ```
255
+
256
+ ## Async
257
+
258
+ ```python
259
+ import asyncio
260
+ from vikky import AsyncVikky
261
+
262
+ async def main():
263
+ client = AsyncVikky()
264
+ resp = await client.chat.completions.create(
265
+ model="vikky-chat",
266
+ messages=[{"role": "user", "content": "Say hello."}],
267
+ )
268
+ print(resp.choices[0].message.content)
269
+
270
+ asyncio.run(main()) # in Colab or Jupyter, use: await main()
271
+ ```
272
+
273
+ Every model above works on `AsyncVikky`, `rerank` and `ocr` included: same
274
+ arguments, awaited.
275
+
276
+ ## Environment variables
277
+
278
+ | Variable | Required | Default |
279
+ |---|---|---|
280
+ | `VIKKY_API_KEY` | yes | none. `OPENAI_API_KEY` is never used. |
281
+ | `VIKKY_BASE_URL` | no | `https://api.vikkyverse.com/v1` |
282
+
283
+ Arguments win over environment: `Vikky(api_key=..., base_url=...)`. Every other
284
+ argument (`timeout`, `max_retries`, ...) goes straight to `openai.OpenAI`.
285
+ A missing key raises `vikky.VikkyError`.
286
+
287
+ ## If Vikky is down
288
+
289
+ Write lab code so the model name comes from the environment:
290
+
291
+ ```python
292
+ import os
293
+ from vikky import Vikky
294
+
295
+ MODEL = os.environ.get("VIKKY_MODEL", "vikky-chat")
296
+ client = Vikky()
297
+ resp = client.chat.completions.create(model=MODEL, messages=[...])
298
+ ```
299
+
300
+ Then a trainer can set `VIKKY_BASE_URL`, `VIKKY_API_KEY` and `VIKKY_MODEL` to
301
+ any other OpenAI-compatible endpoint, and the notebook runs unchanged.
302
+
303
+ ## License
304
+
305
+ MIT
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "vikky"
3
+ version = "0.1.0"
4
+ description = "Python SDK for Vikky Platform: chat, vision, speech, embeddings, images and more with one key."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "VSP AI & Robotics" }]
9
+ requires-python = ">=3.9"
10
+ # 1.55.3: first openai release that works with httpx 0.28 (older ones crash on `proxies`).
11
+ dependencies = ["openai>=1.55.3"]
12
+ keywords = ["vikky", "vikkyverse", "ai", "llm", "sdk", "api"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ "Typing :: Typed",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://vikkyverse.com"
24
+ Documentation = "https://vikkyverse.com/docs"
25
+ "Get an API key" = "https://vikkyverse.com/platform"
26
+
27
+ [dependency-groups]
28
+ dev = ["pytest>=8"]
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.11.14,<0.12.0"]
32
+ build-backend = "uv_build"
@@ -0,0 +1,98 @@
1
+ """Python client for Vikky, VSP's OpenAI-compatible AI API gateway.
2
+
3
+ A thin wrapper over the official ``openai`` package: only the API key and
4
+ base URL defaults differ. Everything else is plain ``openai``.
5
+
6
+ Chat, vision, embeddings, audio, images, video and moderation all go through
7
+ the methods ``openai`` already has. The two exceptions are ``rerank`` and
8
+ ``ocr``: Vikky serves those, the OpenAI API does not, so they are the only
9
+ methods this package adds.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from importlib.metadata import version
16
+ from typing import TYPE_CHECKING, Any, Dict, cast
17
+
18
+ import openai
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Sequence
22
+
23
+ __version__ = version("vikky")
24
+ __all__ = ["DEFAULT_BASE_URL", "AsyncVikky", "Vikky", "VikkyError", "__version__"]
25
+
26
+ DEFAULT_BASE_URL = "https://api.vikkyverse.com/v1"
27
+
28
+
29
+ class VikkyError(openai.OpenAIError):
30
+ """Raised when a Vikky client cannot be configured."""
31
+
32
+
33
+ def _resolve(api_key, base_url):
34
+ # Never fall back to OPENAI_API_KEY: a student's OpenAI key must not go to Vikky.
35
+ api_key = api_key or os.environ.get("VIKKY_API_KEY")
36
+ if not api_key:
37
+ raise VikkyError("No Vikky API key. Set VIKKY_API_KEY or pass api_key=...")
38
+ return api_key, base_url or os.environ.get("VIKKY_BASE_URL") or DEFAULT_BASE_URL
39
+
40
+
41
+ # /rerank and /ocr are not OpenAI routes, so openai has no method for them and no
42
+ # response model to parse them into. cast_to=object is openai's way of saying "give
43
+ # me the parsed JSON": these two return a plain dict, not a pydantic object.
44
+ # Everything else Vikky serves is a real OpenAI route and is not wrapped here.
45
+
46
+
47
+ class Vikky(openai.OpenAI):
48
+ """``openai.OpenAI`` pointed at Vikky."""
49
+
50
+ def __init__(self, *, api_key: str | None = None, base_url: str | None = None, **kwargs):
51
+ api_key, base_url = _resolve(api_key, base_url)
52
+ super().__init__(api_key=api_key, base_url=base_url, **kwargs)
53
+
54
+ def rerank(
55
+ self,
56
+ *,
57
+ query: str,
58
+ documents: Sequence[str],
59
+ model: str = "vikky-rerank",
60
+ **kwargs: Any,
61
+ ) -> Dict[str, Any]:
62
+ """Score documents against a query. ``POST /v1/rerank``, Cohere-shaped."""
63
+ body = {"model": model, "query": query, "documents": list(documents), **kwargs}
64
+ return cast("Dict[str, Any]", self.post("/rerank", body=body, cast_to=object))
65
+
66
+ def ocr(
67
+ self, *, document: Dict[str, Any], model: str = "vikky-ocr", **kwargs: Any
68
+ ) -> Dict[str, Any]:
69
+ """Read text out of a document or image. ``POST /v1/ocr``."""
70
+ body = {"model": model, "document": document, **kwargs}
71
+ return cast("Dict[str, Any]", self.post("/ocr", body=body, cast_to=object))
72
+
73
+
74
+ class AsyncVikky(openai.AsyncOpenAI):
75
+ """``openai.AsyncOpenAI`` pointed at Vikky."""
76
+
77
+ def __init__(self, *, api_key: str | None = None, base_url: str | None = None, **kwargs):
78
+ api_key, base_url = _resolve(api_key, base_url)
79
+ super().__init__(api_key=api_key, base_url=base_url, **kwargs)
80
+
81
+ async def rerank(
82
+ self,
83
+ *,
84
+ query: str,
85
+ documents: Sequence[str],
86
+ model: str = "vikky-rerank",
87
+ **kwargs: Any,
88
+ ) -> Dict[str, Any]:
89
+ """Score documents against a query. ``POST /v1/rerank``, Cohere-shaped."""
90
+ body = {"model": model, "query": query, "documents": list(documents), **kwargs}
91
+ return cast("Dict[str, Any]", await self.post("/rerank", body=body, cast_to=object))
92
+
93
+ async def ocr(
94
+ self, *, document: Dict[str, Any], model: str = "vikky-ocr", **kwargs: Any
95
+ ) -> Dict[str, Any]:
96
+ """Read text out of a document or image. ``POST /v1/ocr``."""
97
+ body = {"model": model, "document": document, **kwargs}
98
+ return cast("Dict[str, Any]", await self.post("/ocr", body=body, cast_to=object))
File without changes