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.
- {livellm-1.1.1 → livellm-1.3.0}/.gitignore +2 -1
- livellm-1.3.0/PKG-INFO +634 -0
- livellm-1.3.0/README.md +608 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/__init__.py +6 -2
- livellm-1.3.0/livellm/livellm.py +867 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/__init__.py +5 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/agent/agent.py +3 -4
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/audio/speak.py +13 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/audio/transcribe.py +7 -8
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/fallback.py +3 -3
- livellm-1.3.0/livellm/models/transcription.py +32 -0
- livellm-1.3.0/livellm/models/ws.py +28 -0
- livellm-1.3.0/livellm/transcripton.py +114 -0
- {livellm-1.1.1 → livellm-1.3.0}/pyproject.toml +2 -1
- livellm-1.1.1/PKG-INFO +0 -625
- livellm-1.1.1/README.md +0 -600
- livellm-1.1.1/livellm/livellm.py +0 -265
- {livellm-1.1.1 → livellm-1.3.0}/LICENSE +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/agent/__init__.py +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/agent/chat.py +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/agent/tools.py +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/audio/__init__.py +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/models/common.py +0 -0
- {livellm-1.1.1 → livellm-1.3.0}/livellm/py.typed +0 -0
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
|
+
[](https://www.python.org/downloads/)
|
|
30
|
+
[](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.
|