livellm 1.1.1__tar.gz → 1.3.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.
@@ -2,4 +2,5 @@
2
2
  __pycache__
3
3
  .pytest_cache
4
4
 
5
- coverage
5
+ .coverage
6
+ test.py
livellm-1.3.0/PKG-INFO ADDED
@@ -0,0 +1,634 @@
1
+ Metadata-Version: 2.4
2
+ Name: livellm
3
+ Version: 1.3.0
4
+ Summary: Python client for the LiveLLM Server
5
+ Project-URL: Homepage, https://github.com/qalby-tech/livellm-client-py
6
+ Project-URL: Repository, https://github.com/qalby-tech/livellm-client-py
7
+ Author: Kamil Saliamov
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: httpx>=0.27.0
19
+ Requires-Dist: pydantic>=2.0.0
20
+ Requires-Dist: websockets>=15.0.1
21
+ Provides-Extra: testing
22
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'testing'
23
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'testing'
24
+ Requires-Dist: pytest>=8.4.2; extra == 'testing'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # LiveLLM Python Client
28
+
29
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
31
+
32
+ Python client library for the LiveLLM Server - a unified proxy for AI agent, audio, and transcription services.
33
+
34
+ ## Features
35
+
36
+ - 🚀 **Async-first** - Built on httpx and websockets for high-performance operations
37
+ - 🔒 **Type-safe** - Full type hints and Pydantic validation
38
+ - 🎯 **Multi-provider** - OpenAI, Google, Anthropic, Groq, ElevenLabs
39
+ - 🔄 **Streaming** - Real-time streaming for agent and audio
40
+ - 🛠️ **Flexible API** - Use request objects or keyword arguments
41
+ - 🎙️ **Audio services** - Text-to-speech and transcription
42
+ - 🎤 **Real-Time Transcription** - WebSocket-based live audio transcription with bidirectional streaming
43
+ - ⚡ **Fallback strategies** - Sequential and parallel handling
44
+ - 🧹 **Auto cleanup** - Context managers and garbage collection
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install livellm
50
+ ```
51
+
52
+ Or with development dependencies:
53
+
54
+ ```bash
55
+ pip install livellm[testing]
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```python
61
+ import asyncio
62
+ from livellm import LivellmClient
63
+ from livellm.models import Settings, ProviderKind, TextMessage, MessageRole
64
+
65
+ async def main():
66
+ # Initialize with automatic provider setup
67
+ async with LivellmClient(
68
+ base_url="http://localhost:8000",
69
+ configs=[
70
+ Settings(
71
+ uid="openai",
72
+ provider=ProviderKind.OPENAI,
73
+ api_key="your-api-key"
74
+ )
75
+ ]
76
+ ) as client:
77
+ # Simple keyword arguments style (gen_config as kwargs)
78
+ response = await client.agent_run(
79
+ provider_uid="openai",
80
+ model="gpt-4",
81
+ messages=[TextMessage(role="user", content="Hello!")],
82
+ temperature=0.7
83
+ )
84
+ print(response.output)
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ ### Client Initialization
92
+
93
+ ```python
94
+ from livellm import LivellmClient
95
+ from livellm.models import Settings, ProviderKind
96
+
97
+ # Basic
98
+ client = LivellmClient(base_url="http://localhost:8000")
99
+
100
+ # With timeout and pre-configured providers
101
+ client = LivellmClient(
102
+ base_url="http://localhost:8000",
103
+ timeout=30.0,
104
+ configs=[
105
+ Settings(
106
+ uid="openai",
107
+ provider=ProviderKind.OPENAI,
108
+ api_key="sk-...",
109
+ base_url="https://api.openai.com/v1" # Optional
110
+ ),
111
+ Settings(
112
+ uid="anthropic",
113
+ provider=ProviderKind.ANTHROPIC,
114
+ api_key="sk-ant-...",
115
+ blacklist_models=["claude-instant-1"] # Optional
116
+ )
117
+ ]
118
+ )
119
+ ```
120
+
121
+ ### Supported Providers
122
+
123
+ `OPENAI` • `GOOGLE` • `ANTHROPIC` • `GROQ` • `ELEVENLABS`
124
+
125
+ ```python
126
+ # Add provider dynamically
127
+ await client.update_config(Settings(
128
+ uid="my-provider",
129
+ provider=ProviderKind.OPENAI,
130
+ api_key="your-api-key"
131
+ ))
132
+
133
+ # List and delete
134
+ configs = await client.get_configs()
135
+ await client.delete_config("my-provider")
136
+ ```
137
+
138
+ ## Usage Examples
139
+
140
+ ### Agent Services
141
+
142
+ #### Two Ways to Call Methods
143
+
144
+ All methods support **two calling styles**:
145
+
146
+ **Style 1: Keyword arguments** (kwargs become `gen_config`)
147
+ ```python
148
+ response = await client.agent_run(
149
+ provider_uid="openai",
150
+ model="gpt-4",
151
+ messages=[TextMessage(role="user", content="Hello!")],
152
+ temperature=0.7,
153
+ max_tokens=500
154
+ )
155
+ ```
156
+
157
+ **Style 2: Request objects**
158
+ ```python
159
+ from livellm.models import AgentRequest
160
+
161
+ response = await client.agent_run(
162
+ AgentRequest(
163
+ provider_uid="openai",
164
+ model="gpt-4",
165
+ messages=[TextMessage(role="user", content="Hello!")],
166
+ gen_config={"temperature": 0.7, "max_tokens": 500}
167
+ )
168
+ )
169
+ ```
170
+
171
+ #### Basic Agent Run
172
+
173
+ ```python
174
+ from livellm.models import TextMessage
175
+
176
+ # Using kwargs (recommended for simplicity)
177
+ response = await client.agent_run(
178
+ provider_uid="openai",
179
+ model="gpt-4",
180
+ messages=[
181
+ TextMessage(role="system", content="You are helpful."),
182
+ TextMessage(role="user", content="Explain quantum computing")
183
+ ],
184
+ temperature=0.7,
185
+ max_tokens=500
186
+ )
187
+ print(f"Output: {response.output}")
188
+ print(f"Tokens: {response.usage.input_tokens} in, {response.usage.output_tokens} out")
189
+ ```
190
+
191
+ #### Streaming Agent Response
192
+
193
+ ```python
194
+ # Streaming also supports both styles
195
+ stream = client.agent_run_stream(
196
+ provider_uid="openai",
197
+ model="gpt-4",
198
+ messages=[TextMessage(role="user", content="Tell me a story")],
199
+ temperature=0.8
200
+ )
201
+
202
+ async for chunk in stream:
203
+ print(chunk.output, end="", flush=True)
204
+ ```
205
+
206
+ #### Agent with Vision (Binary Messages)
207
+
208
+ ```python
209
+ import base64
210
+ from livellm.models import BinaryMessage
211
+
212
+ with open("image.jpg", "rb") as f:
213
+ image_data = base64.b64encode(f.read()).decode("utf-8")
214
+
215
+ response = await client.agent_run(
216
+ provider_uid="openai",
217
+ model="gpt-4-vision",
218
+ messages=[
219
+ BinaryMessage(
220
+ role="user",
221
+ content=image_data,
222
+ mime_type="image/jpeg",
223
+ caption="What's in this image?"
224
+ )
225
+ ]
226
+ )
227
+ ```
228
+
229
+ #### Agent with Tools
230
+
231
+ ```python
232
+ from livellm.models import WebSearchInput, MCPStreamableServerInput, ToolKind
233
+
234
+ # Web search tool
235
+ response = await client.agent_run(
236
+ provider_uid="openai",
237
+ model="gpt-4",
238
+ messages=[TextMessage(role="user", content="Latest AI news?")],
239
+ tools=[WebSearchInput(
240
+ kind=ToolKind.WEB_SEARCH,
241
+ search_context_size="high" # low, medium, or high
242
+ )]
243
+ )
244
+
245
+ # MCP server tool
246
+ response = await client.agent_run(
247
+ provider_uid="openai",
248
+ model="gpt-4",
249
+ messages=[TextMessage(role="user", content="Run custom tool")],
250
+ tools=[MCPStreamableServerInput(
251
+ kind=ToolKind.MCP_STREAMABLE_SERVER,
252
+ url="http://mcp-server:8080",
253
+ prefix="mcp_",
254
+ timeout=15
255
+ )]
256
+ )
257
+ ```
258
+
259
+ ### Audio Services
260
+
261
+ #### Text-to-Speech
262
+
263
+ ```python
264
+ from livellm.models import SpeakMimeType
265
+
266
+ # Non-streaming
267
+ audio = await client.speak(
268
+ provider_uid="openai",
269
+ model="tts-1",
270
+ text="Hello, world!",
271
+ voice="alloy",
272
+ mime_type=SpeakMimeType.MP3,
273
+ sample_rate=24000,
274
+ speed=1.0 # kwargs become gen_config
275
+ )
276
+ with open("output.mp3", "wb") as f:
277
+ f.write(audio)
278
+
279
+ # Streaming
280
+ audio = bytes()
281
+ async for chunk in client.speak_stream(
282
+ provider_uid="openai",
283
+ model="tts-1",
284
+ text="Hello, world!",
285
+ voice="alloy",
286
+ mime_type=SpeakMimeType.PCM,
287
+ sample_rate=24000
288
+ ):
289
+ audio += chunk
290
+
291
+ # Save PCM as WAV
292
+ import wave
293
+ with wave.open("output.wav", "wb") as wf:
294
+ wf.setnchannels(1)
295
+ wf.setsampwidth(2)
296
+ wf.setframerate(24000)
297
+ wf.writeframes(audio)
298
+ ```
299
+
300
+ #### Transcription
301
+
302
+ ```python
303
+ # Method 1: Multipart upload (kwargs style)
304
+ with open("audio.wav", "rb") as f:
305
+ audio_bytes = f.read()
306
+
307
+ transcription = await client.transcribe(
308
+ provider_uid="openai",
309
+ file=("audio.wav", audio_bytes, "audio/wav"),
310
+ model="whisper-1",
311
+ language="en", # Optional
312
+ temperature=0.0 # kwargs become gen_config
313
+ )
314
+ print(f"Text: {transcription.text}")
315
+ print(f"Language: {transcription.language}")
316
+
317
+ # Method 2: JSON request object (base64-encoded)
318
+ import base64
319
+ from livellm.models import TranscribeRequest
320
+
321
+ audio_b64 = base64.b64encode(audio_bytes).decode("utf-8")
322
+ transcription = await client.transcribe(
323
+ TranscribeRequest(
324
+ provider_uid="openai",
325
+ file=("audio.wav", audio_b64, "audio/wav"),
326
+ model="whisper-1"
327
+ )
328
+ )
329
+ ```
330
+
331
+ ### Real-Time Transcription (WebSocket)
332
+
333
+ The realtime transcription API is available either **directly** via `TranscriptionWsClient` or **through** `LivellmClient.realtime.transcription`.
334
+
335
+ #### Using `TranscriptionWsClient` directly
336
+
337
+ ```python
338
+ import asyncio
339
+ from livellm import TranscriptionWsClient
340
+ from livellm.models import (
341
+ TranscriptionInitWsRequest,
342
+ TranscriptionAudioChunkWsRequest,
343
+ SpeakMimeType,
344
+ )
345
+
346
+ async def transcribe_live_direct():
347
+ base_url = "ws://localhost:8000" # WebSocket base URL
348
+
349
+ async with TranscriptionWsClient(base_url, timeout=30) as client:
350
+ # Define audio source (file, microphone, stream, etc.)
351
+ async def audio_source():
352
+ with open("audio.pcm", "rb") as f:
353
+ while chunk := f.read(4096):
354
+ yield TranscriptionAudioChunkWsRequest(audio=chunk)
355
+ await asyncio.sleep(0.1) # Simulate real-time
356
+
357
+ # Initialize transcription session
358
+ init_request = TranscriptionInitWsRequest(
359
+ provider_uid="openai",
360
+ model="gpt-4o-mini-transcribe",
361
+ language="en", # or "auto" for detection
362
+ input_sample_rate=24000,
363
+ input_audio_format=SpeakMimeType.PCM,
364
+ gen_config={},
365
+ )
366
+
367
+ # Stream audio and receive transcriptions
368
+ async for response in client.start_session(init_request, audio_source()):
369
+ print(f"Transcription: {response.transcription}")
370
+ if response.is_end:
371
+ print("Transcription complete!")
372
+ break
373
+
374
+ asyncio.run(transcribe_live_direct())
375
+ ```
376
+
377
+ #### Using `LivellmClient.realtime.transcription` (and running agents while listening)
378
+
379
+ ```python
380
+ import asyncio
381
+ from livellm import LivellmClient
382
+ from livellm.models import (
383
+ TextMessage,
384
+ TranscriptionInitWsRequest,
385
+ TranscriptionAudioChunkWsRequest,
386
+ SpeakMimeType,
387
+ )
388
+
389
+ async def transcribe_and_chat():
390
+ # Central HTTP client; .realtime and .transcription expose WebSocket APIs
391
+ client = LivellmClient(base_url="http://localhost:8000", timeout=30)
392
+
393
+ async with client.realtime as realtime:
394
+ async with realtime.transcription as t_client:
395
+ async def audio_source():
396
+ with open("audio.pcm", "rb") as f:
397
+ while chunk := f.read(4096):
398
+ yield TranscriptionAudioChunkWsRequest(audio=chunk)
399
+ await asyncio.sleep(0.1)
400
+
401
+ init_request = TranscriptionInitWsRequest(
402
+ provider_uid="openai",
403
+ model="gpt-4o-mini-transcribe",
404
+ language="en",
405
+ input_sample_rate=24000,
406
+ input_audio_format=SpeakMimeType.PCM,
407
+ gen_config={},
408
+ )
409
+
410
+ # Listen for transcriptions and, for each chunk, run an agent request
411
+ async for resp in t_client.start_session(init_request, audio_source()):
412
+ print("User said:", resp.transcription)
413
+
414
+ # You can call agent_run (or speak, etc.) while the transcription stream is active
415
+ agent_response = await realtime.agent_run(
416
+ provider_uid="openai",
417
+ model="gpt-4",
418
+ messages=[
419
+ TextMessage(role="user", content=resp.transcription),
420
+ ],
421
+ temperature=0.7,
422
+ )
423
+ print("Agent:", agent_response.output)
424
+
425
+ if resp.is_end:
426
+ print("Transcription session complete")
427
+ break
428
+
429
+ asyncio.run(transcribe_and_chat())
430
+ ```
431
+
432
+ **Supported Audio Formats:**
433
+ - **PCM**: 16-bit uncompressed (recommended)
434
+ - **μ-law**: 8-bit telephony format (North America/Japan)
435
+ - **A-law**: 8-bit telephony format (Europe/rest of world)
436
+
437
+ **Use Cases:**
438
+ - 🎙️ Voice assistants and chatbots
439
+ - 📝 Live captioning and subtitles
440
+ - 🎤 Meeting transcription
441
+ - 🗣️ Voice commands and control
442
+
443
+ **See also:**
444
+ - [TRANSCRIPTION_CLIENT.md](TRANSCRIPTION_CLIENT.md) - Complete transcription guide
445
+ - [example_transcription.py](example_transcription.py) - Python examples
446
+ - [example_transcription_browser.html](example_transcription_browser.html) - Browser demo
447
+
448
+ ### Fallback Strategies
449
+
450
+ Handle failures automatically with sequential or parallel fallback:
451
+
452
+ ```python
453
+ from livellm.models import AgentRequest, AgentFallbackRequest, FallbackStrategy, TextMessage
454
+
455
+ messages = [TextMessage(role="user", content="Hello!")]
456
+
457
+ # Sequential: try each in order until one succeeds
458
+ response = await client.agent_run(
459
+ AgentFallbackRequest(
460
+ strategy=FallbackStrategy.SEQUENTIAL,
461
+ requests=[
462
+ AgentRequest(provider_uid="primary", model="gpt-4", messages=messages, tools=[]),
463
+ AgentRequest(provider_uid="backup", model="claude-3", messages=messages, tools=[])
464
+ ],
465
+ timeout_per_request=30
466
+ )
467
+ )
468
+
469
+ # Parallel: try all simultaneously, use first success
470
+ response = await client.agent_run(
471
+ AgentFallbackRequest(
472
+ strategy=FallbackStrategy.PARALLEL,
473
+ requests=[
474
+ AgentRequest(provider_uid="p1", model="gpt-4", messages=messages, tools=[]),
475
+ AgentRequest(provider_uid="p2", model="claude-3", messages=messages, tools=[]),
476
+ AgentRequest(provider_uid="p3", model="gemini-pro", messages=messages, tools=[])
477
+ ],
478
+ timeout_per_request=10
479
+ )
480
+ )
481
+
482
+ # Also works for audio
483
+ from livellm.models import AudioFallbackRequest, SpeakRequest
484
+
485
+ audio = await client.speak(
486
+ AudioFallbackRequest(
487
+ strategy=FallbackStrategy.SEQUENTIAL,
488
+ requests=[
489
+ SpeakRequest(provider_uid="elevenlabs", model="turbo", text="Hi",
490
+ voice="rachel", mime_type=SpeakMimeType.MP3, sample_rate=44100),
491
+ SpeakRequest(provider_uid="openai", model="tts-1", text="Hi",
492
+ voice="alloy", mime_type=SpeakMimeType.MP3, sample_rate=44100)
493
+ ]
494
+ )
495
+ )
496
+ ```
497
+
498
+ ## Resource Management
499
+
500
+ **Recommended**: Use context managers for automatic cleanup.
501
+
502
+ ```python
503
+ # ✅ Best: Context manager (auto cleanup)
504
+ async with LivellmClient(base_url="http://localhost:8000") as client:
505
+ response = await client.ping()
506
+ # Configs deleted, connection closed automatically
507
+
508
+ # ✅ Good: Manual cleanup
509
+ client = LivellmClient(base_url="http://localhost:8000")
510
+ try:
511
+ response = await client.ping()
512
+ finally:
513
+ await client.cleanup()
514
+
515
+ # ⚠️ OK: Garbage collection (shows warning if configs exist)
516
+ client = LivellmClient(base_url="http://localhost:8000")
517
+ response = await client.ping()
518
+ # Cleaned up when object is destroyed
519
+ ```
520
+
521
+ ## API Reference
522
+
523
+ ### Client Methods
524
+
525
+ **Configuration**
526
+ - `ping()` - Health check
527
+ - `update_config(config)` / `update_configs(configs)` - Add/update providers
528
+ - `get_configs()` - List all configurations
529
+ - `delete_config(uid)` - Remove provider
530
+
531
+ **Agent**
532
+ - `agent_run(request | **kwargs)` - Run agent (blocking)
533
+ - `agent_run_stream(request | **kwargs)` - Run agent (streaming)
534
+
535
+ **Audio**
536
+ - `speak(request | **kwargs)` - Text-to-speech (blocking)
537
+ - `speak_stream(request | **kwargs)` - Text-to-speech (streaming)
538
+ - `transcribe(request | **kwargs)` - Speech-to-text
539
+
540
+ **Real-Time Transcription (TranscriptionWsClient)**
541
+ - `connect()` - Establish WebSocket connection
542
+ - `disconnect()` - Close WebSocket connection
543
+ - `start_session(init_request, audio_source)` - Start bidirectional streaming transcription
544
+ - `async with client:` - Auto connection management (recommended)
545
+
546
+ **Cleanup**
547
+ - `cleanup()` - Release resources
548
+ - `async with client:` - Auto cleanup (recommended)
549
+
550
+ ### Key Models
551
+
552
+ **Core**
553
+ - `Settings(uid, provider, api_key, base_url?, blacklist_models?)` - Provider config
554
+ - `ProviderKind` - `OPENAI` | `GOOGLE` | `ANTHROPIC` | `GROQ` | `ELEVENLABS`
555
+
556
+ **Messages**
557
+ - `TextMessage(role, content)` - Text message
558
+ - `BinaryMessage(role, content, mime_type, caption?)` - Image/audio message
559
+ - `MessageRole` - `USER` | `MODEL` | `SYSTEM` (or use strings: `"user"`, `"model"`, `"system"`)
560
+
561
+ **Requests**
562
+ - `AgentRequest(provider_uid, model, messages, tools?, gen_config?)`
563
+ - `SpeakRequest(provider_uid, model, text, voice, mime_type, sample_rate, gen_config?)`
564
+ - `TranscribeRequest(provider_uid, file, model, language?, gen_config?)`
565
+ - `TranscriptionInitWsRequest(provider_uid, model, language?, input_sample_rate?, input_audio_format?, gen_config?)`
566
+ - `TranscriptionAudioChunkWsRequest(audio)` - Audio chunk for streaming
567
+
568
+ **Tools**
569
+ - `WebSearchInput(kind=ToolKind.WEB_SEARCH, search_context_size)`
570
+ - `MCPStreamableServerInput(kind=ToolKind.MCP_STREAMABLE_SERVER, url, prefix?, timeout?)`
571
+
572
+ **Fallback**
573
+ - `AgentFallbackRequest(strategy, requests, timeout_per_request?)`
574
+ - `AudioFallbackRequest(strategy, requests, timeout_per_request?)`
575
+ - `FallbackStrategy` - `SEQUENTIAL` | `PARALLEL`
576
+
577
+ **Responses**
578
+ - `AgentResponse(output, usage{input_tokens, output_tokens}, ...)`
579
+ - `TranscribeResponse(text, language)`
580
+ - `TranscriptionWsResponse(transcription, is_end)` - Real-time transcription result
581
+
582
+ ## Error Handling
583
+
584
+ ```python
585
+ import httpx
586
+
587
+ try:
588
+ response = await client.agent_run(
589
+ provider_uid="openai",
590
+ model="gpt-4",
591
+ messages=[TextMessage(role="user", content="Hi")]
592
+ )
593
+ except httpx.HTTPStatusError as e:
594
+ print(f"HTTP {e.response.status_code}: {e.response.text}")
595
+ except httpx.RequestError as e:
596
+ print(f"Request failed: {e}")
597
+ ```
598
+
599
+ ## Development
600
+
601
+ ```bash
602
+ # Install with dev dependencies
603
+ pip install -e ".[testing]"
604
+
605
+ # Run tests
606
+ pytest tests/
607
+
608
+ # Type checking
609
+ mypy livellm
610
+ ```
611
+
612
+ ## Requirements
613
+
614
+ - Python 3.10+
615
+ - httpx >= 0.27.0
616
+ - pydantic >= 2.0.0
617
+ - websockets >= 15.0.1
618
+
619
+ ## Documentation
620
+
621
+ - [README.md](README.md) - Main documentation (you are here)
622
+ - [TRANSCRIPTION_CLIENT.md](TRANSCRIPTION_CLIENT.md) - Complete real-time transcription guide
623
+ - [CLIENT_EXAMPLES.md](CLIENT_EXAMPLES.md) - Usage examples for all features
624
+ - [example_transcription.py](example_transcription.py) - Python transcription examples
625
+ - [example_transcription_browser.html](example_transcription_browser.html) - Browser demo
626
+
627
+ ## Links
628
+
629
+ - [GitHub Repository](https://github.com/qalby-tech/livellm-client-py)
630
+ - [Issue Tracker](https://github.com/qalby-tech/livellm-client-py/issues)
631
+
632
+ ## License
633
+
634
+ MIT License - see LICENSE file for details.