casual-llm 0.4.1__py3-none-any.whl → 0.4.2__py3-none-any.whl
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.
- casual_llm/__init__.py +1 -1
- {casual_llm-0.4.1.dist-info → casual_llm-0.4.2.dist-info}/METADATA +191 -9
- {casual_llm-0.4.1.dist-info → casual_llm-0.4.2.dist-info}/RECORD +6 -6
- {casual_llm-0.4.1.dist-info → casual_llm-0.4.2.dist-info}/WHEEL +1 -1
- {casual_llm-0.4.1.dist-info → casual_llm-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {casual_llm-0.4.1.dist-info → casual_llm-0.4.2.dist-info}/top_level.txt +0 -0
casual_llm/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ A simple, protocol-based library for working with different LLM providers
|
|
|
7
7
|
Part of the casual-* ecosystem of lightweight AI tools.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = "0.4.
|
|
10
|
+
__version__ = "0.4.2"
|
|
11
11
|
|
|
12
12
|
# Model configuration
|
|
13
13
|
from casual_llm.config import ModelConfig, Provider
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: casual-llm
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Lightweight LLM provider abstraction with standardized message models
|
|
5
5
|
Author-email: Alex Stansfield <alex@casualgenius.com>
|
|
6
6
|
License: MIT
|
|
@@ -43,13 +43,15 @@ Part of the [casual-*](https://github.com/AlexStansfield/casual-mcp) ecosystem o
|
|
|
43
43
|
## Features
|
|
44
44
|
|
|
45
45
|
- 🎯 **Protocol-based** - Uses `typing.Protocol`, no inheritance required
|
|
46
|
-
- 🔌 **
|
|
47
|
-
- 📦 **Lightweight** - Minimal dependencies (pydantic, ollama)
|
|
46
|
+
- 🔌 **Multi-provider** - Works with OpenAI, Anthropic (Claude), Ollama, or your custom provider
|
|
47
|
+
- 📦 **Lightweight** - Minimal dependencies (pydantic, ollama, httpx)
|
|
48
48
|
- 🔄 **Async-first** - Built for modern async Python
|
|
49
49
|
- 🛡️ **Type-safe** - Full type hints with py.typed marker
|
|
50
50
|
- 📊 **OpenAI-compatible** - Standard message format used across the industry
|
|
51
51
|
- 🔧 **Tool calling** - First-class support for function/tool calling
|
|
52
52
|
- 📈 **Usage tracking** - Track token usage for cost monitoring
|
|
53
|
+
- 🖼️ **Vision support** - Send images to vision-capable models
|
|
54
|
+
- ⚡ **Streaming** - Stream responses in real-time with `AsyncIterator`
|
|
53
55
|
|
|
54
56
|
## Installation
|
|
55
57
|
|
|
@@ -60,11 +62,18 @@ uv add casual-llm
|
|
|
60
62
|
# With OpenAI support
|
|
61
63
|
uv add casual-llm[openai]
|
|
62
64
|
|
|
65
|
+
# With Anthropic (Claude) support
|
|
66
|
+
uv add casual-llm[anthropic]
|
|
67
|
+
|
|
68
|
+
# With all providers
|
|
69
|
+
uv add casual-llm[openai,anthropic]
|
|
70
|
+
|
|
63
71
|
# Development dependencies
|
|
64
72
|
uv add casual-llm[dev]
|
|
65
73
|
|
|
66
74
|
# Or using pip
|
|
67
75
|
pip install casual-llm
|
|
76
|
+
pip install casual-llm[openai,anthropic]
|
|
68
77
|
```
|
|
69
78
|
|
|
70
79
|
## Quick Start
|
|
@@ -122,6 +131,32 @@ if usage:
|
|
|
122
131
|
print(f"Total tokens: {usage.total_tokens}")
|
|
123
132
|
```
|
|
124
133
|
|
|
134
|
+
### Using Anthropic (Claude)
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from casual_llm import create_provider, ModelConfig, Provider, UserMessage
|
|
138
|
+
|
|
139
|
+
# Create Anthropic provider
|
|
140
|
+
config = ModelConfig(
|
|
141
|
+
name="claude-3-5-sonnet-20241022",
|
|
142
|
+
provider=Provider.ANTHROPIC,
|
|
143
|
+
api_key="sk-ant-...", # or set ANTHROPIC_API_KEY env var
|
|
144
|
+
temperature=0.7
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
provider = create_provider(config)
|
|
148
|
+
|
|
149
|
+
# Generate response
|
|
150
|
+
messages = [UserMessage(content="Explain quantum computing in one sentence.")]
|
|
151
|
+
response = await provider.chat(messages, response_format="text")
|
|
152
|
+
print(response.content)
|
|
153
|
+
|
|
154
|
+
# Check token usage
|
|
155
|
+
usage = provider.get_usage()
|
|
156
|
+
if usage:
|
|
157
|
+
print(f"Total tokens: {usage.total_tokens}")
|
|
158
|
+
```
|
|
159
|
+
|
|
125
160
|
### Using OpenAI-Compatible APIs (OpenRouter, LM Studio, etc.)
|
|
126
161
|
|
|
127
162
|
```python
|
|
@@ -136,6 +171,107 @@ config = ModelConfig(
|
|
|
136
171
|
provider = create_provider(config)
|
|
137
172
|
```
|
|
138
173
|
|
|
174
|
+
### Vision Support
|
|
175
|
+
|
|
176
|
+
Send images to vision-capable models (GPT-4o, Claude 3.5 Sonnet, llava):
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from casual_llm import (
|
|
180
|
+
create_provider,
|
|
181
|
+
ModelConfig,
|
|
182
|
+
Provider,
|
|
183
|
+
UserMessage,
|
|
184
|
+
TextContent,
|
|
185
|
+
ImageContent,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Works with OpenAI, Anthropic, and Ollama
|
|
189
|
+
config = ModelConfig(
|
|
190
|
+
name="gpt-4o", # or "claude-3-5-sonnet-20241022" or "llava"
|
|
191
|
+
provider=Provider.OPENAI,
|
|
192
|
+
api_key="sk-...",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
provider = create_provider(config)
|
|
196
|
+
|
|
197
|
+
# Send an image URL
|
|
198
|
+
messages = [
|
|
199
|
+
UserMessage(
|
|
200
|
+
content=[
|
|
201
|
+
TextContent(text="What's in this image?"),
|
|
202
|
+
ImageContent(source="https://example.com/image.jpg"),
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
response = await provider.chat(messages)
|
|
208
|
+
print(response.content) # "I see a cat sitting on a windowsill..."
|
|
209
|
+
|
|
210
|
+
# Or send a base64-encoded image
|
|
211
|
+
import base64
|
|
212
|
+
|
|
213
|
+
with open("image.jpg", "rb") as f:
|
|
214
|
+
image_data = base64.b64encode(f.read()).decode("ascii")
|
|
215
|
+
|
|
216
|
+
messages = [
|
|
217
|
+
UserMessage(
|
|
218
|
+
content=[
|
|
219
|
+
TextContent(text="Describe this image"),
|
|
220
|
+
ImageContent(
|
|
221
|
+
source={"type": "base64", "data": image_data},
|
|
222
|
+
media_type="image/jpeg",
|
|
223
|
+
),
|
|
224
|
+
]
|
|
225
|
+
)
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
response = await provider.chat(messages)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Streaming Responses
|
|
232
|
+
|
|
233
|
+
Stream responses in real-time for better UX:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from casual_llm import create_provider, ModelConfig, Provider, UserMessage
|
|
237
|
+
|
|
238
|
+
config = ModelConfig(
|
|
239
|
+
name="gpt-4o", # Works with all providers
|
|
240
|
+
provider=Provider.OPENAI,
|
|
241
|
+
api_key="sk-...",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
provider = create_provider(config)
|
|
245
|
+
|
|
246
|
+
messages = [UserMessage(content="Write a short poem about coding.")]
|
|
247
|
+
|
|
248
|
+
# Stream the response
|
|
249
|
+
async for chunk in provider.stream(messages):
|
|
250
|
+
if chunk.content:
|
|
251
|
+
print(chunk.content, end="", flush=True)
|
|
252
|
+
|
|
253
|
+
print() # New line after streaming
|
|
254
|
+
|
|
255
|
+
# Check usage after streaming
|
|
256
|
+
usage = provider.get_usage()
|
|
257
|
+
if usage:
|
|
258
|
+
print(f"\nTokens used: {usage.total_tokens}")
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Examples
|
|
262
|
+
|
|
263
|
+
Looking for more examples? Check out the [`examples/`](examples) directory for comprehensive demonstrations of all features:
|
|
264
|
+
|
|
265
|
+
- **[`basic_ollama.py`](examples/basic_ollama.py)** - Get started with Ollama (local LLMs)
|
|
266
|
+
- **[`basic_openai.py`](examples/basic_openai.py)** - Use OpenAI API and compatible services
|
|
267
|
+
- **[`basic_anthropic.py`](examples/basic_anthropic.py)** - Work with Claude models
|
|
268
|
+
- **[`vision_example.py`](examples/vision_example.py)** - Send images to vision-capable models
|
|
269
|
+
- **[`stream_example.py`](examples/stream_example.py)** - Stream responses in real-time
|
|
270
|
+
- **[`tool_calling.py`](examples/tool_calling.py)** - Complete tool/function calling workflow
|
|
271
|
+
- **[`message_formatting.py`](examples/message_formatting.py)** - All message types and structures
|
|
272
|
+
|
|
273
|
+
See the **[Examples README](examples/README.md)** for detailed descriptions, requirements, and usage instructions for each example.
|
|
274
|
+
|
|
139
275
|
## Message Models
|
|
140
276
|
|
|
141
277
|
casual-llm provides OpenAI-compatible message models that work with any provider:
|
|
@@ -148,14 +284,24 @@ from casual_llm import (
|
|
|
148
284
|
ToolResultMessage,
|
|
149
285
|
AssistantToolCall,
|
|
150
286
|
ChatMessage, # Type alias for any message type
|
|
287
|
+
TextContent, # For multimodal messages
|
|
288
|
+
ImageContent, # For vision support
|
|
151
289
|
)
|
|
152
290
|
|
|
153
291
|
# System message (sets behavior)
|
|
154
292
|
system_msg = SystemMessage(content="You are a helpful assistant.")
|
|
155
293
|
|
|
156
|
-
# User message
|
|
294
|
+
# User message (simple text)
|
|
157
295
|
user_msg = UserMessage(content="Hello!")
|
|
158
296
|
|
|
297
|
+
# User message (multimodal - text + image)
|
|
298
|
+
vision_msg = UserMessage(
|
|
299
|
+
content=[
|
|
300
|
+
TextContent(text="What's in this image?"),
|
|
301
|
+
ImageContent(source="https://example.com/image.jpg"),
|
|
302
|
+
]
|
|
303
|
+
)
|
|
304
|
+
|
|
159
305
|
# Assistant message (with optional tool calls)
|
|
160
306
|
assistant_msg = AssistantMessage(
|
|
161
307
|
content="I'll help you with that.",
|
|
@@ -186,7 +332,15 @@ messages: list[ChatMessage] = [system_msg, user_msg, assistant_msg, tool_msg]
|
|
|
186
332
|
Implement the `LLMProvider` protocol to add your own provider:
|
|
187
333
|
|
|
188
334
|
```python
|
|
189
|
-
from
|
|
335
|
+
from typing import Literal, AsyncIterator
|
|
336
|
+
from casual_llm import (
|
|
337
|
+
LLMProvider,
|
|
338
|
+
ChatMessage,
|
|
339
|
+
AssistantMessage,
|
|
340
|
+
StreamChunk,
|
|
341
|
+
Tool,
|
|
342
|
+
Usage,
|
|
343
|
+
)
|
|
190
344
|
|
|
191
345
|
class MyCustomProvider:
|
|
192
346
|
"""Custom LLM provider implementation."""
|
|
@@ -194,13 +348,26 @@ class MyCustomProvider:
|
|
|
194
348
|
async def chat(
|
|
195
349
|
self,
|
|
196
350
|
messages: list[ChatMessage],
|
|
197
|
-
response_format: Literal["json", "text"] = "text",
|
|
351
|
+
response_format: Literal["json", "text"] | type[BaseModel] = "text",
|
|
198
352
|
max_tokens: int | None = None,
|
|
199
353
|
tools: list[Tool] | None = None,
|
|
354
|
+
temperature: float | None = None,
|
|
200
355
|
) -> AssistantMessage:
|
|
201
356
|
# Your implementation here
|
|
202
357
|
...
|
|
203
358
|
|
|
359
|
+
async def stream(
|
|
360
|
+
self,
|
|
361
|
+
messages: list[ChatMessage],
|
|
362
|
+
response_format: Literal["json", "text"] | type[BaseModel] = "text",
|
|
363
|
+
max_tokens: int | None = None,
|
|
364
|
+
tools: list[Tool] | None = None,
|
|
365
|
+
temperature: float | None = None,
|
|
366
|
+
) -> AsyncIterator[StreamChunk]:
|
|
367
|
+
# Your streaming implementation here
|
|
368
|
+
...
|
|
369
|
+
yield StreamChunk(content="chunk", finish_reason=None)
|
|
370
|
+
|
|
204
371
|
def get_usage(self) -> Usage | None:
|
|
205
372
|
"""Return token usage from last call."""
|
|
206
373
|
return self._last_usage
|
|
@@ -246,10 +413,13 @@ Both OpenAI and Ollama providers support usage tracking.
|
|
|
246
413
|
|
|
247
414
|
| Feature | casual-llm | LangChain | litellm |
|
|
248
415
|
|---------|-----------|-----------|---------|
|
|
249
|
-
| **Dependencies** |
|
|
416
|
+
| **Dependencies** | 3 (pydantic, ollama, httpx) | 100+ | 50+ |
|
|
250
417
|
| **Protocol-based** | ✅ | ❌ | ❌ |
|
|
251
418
|
| **Type-safe** | ✅ Full typing | Partial | Partial |
|
|
252
419
|
| **Message models** | ✅ Included | ❌ Separate | ❌ |
|
|
420
|
+
| **Vision support** | ✅ All providers | ✅ | ✅ |
|
|
421
|
+
| **Streaming** | ✅ All providers | ✅ | ✅ |
|
|
422
|
+
| **Providers** | OpenAI, Anthropic, Ollama | Many | Many |
|
|
253
423
|
| **Learning curve** | ⚡ Minutes | 📚 Hours | 📖 Medium |
|
|
254
424
|
| **OpenAI compatible** | ✅ | ✅ | ✅ |
|
|
255
425
|
|
|
@@ -282,11 +452,21 @@ class LLMProvider(Protocol):
|
|
|
282
452
|
async def chat(
|
|
283
453
|
self,
|
|
284
454
|
messages: list[ChatMessage],
|
|
285
|
-
response_format: Literal["json", "text"] = "text",
|
|
455
|
+
response_format: Literal["json", "text"] | type[BaseModel] = "text",
|
|
286
456
|
max_tokens: int | None = None,
|
|
287
457
|
tools: list[Tool] | None = None,
|
|
458
|
+
temperature: float | None = None,
|
|
288
459
|
) -> AssistantMessage: ...
|
|
289
460
|
|
|
461
|
+
async def stream(
|
|
462
|
+
self,
|
|
463
|
+
messages: list[ChatMessage],
|
|
464
|
+
response_format: Literal["json", "text"] | type[BaseModel] = "text",
|
|
465
|
+
max_tokens: int | None = None,
|
|
466
|
+
tools: list[Tool] | None = None,
|
|
467
|
+
temperature: float | None = None,
|
|
468
|
+
) -> AsyncIterator[StreamChunk]: ...
|
|
469
|
+
|
|
290
470
|
def get_usage(self) -> Usage | None: ...
|
|
291
471
|
```
|
|
292
472
|
|
|
@@ -321,11 +501,13 @@ class Usage(BaseModel):
|
|
|
321
501
|
|
|
322
502
|
All message models are Pydantic `BaseModel` instances with full validation:
|
|
323
503
|
|
|
324
|
-
- `UserMessage(content: str | None)`
|
|
504
|
+
- `UserMessage(content: str | list[TextContent | ImageContent] | None)` - Supports simple text or multimodal content
|
|
325
505
|
- `AssistantMessage(content: str | None, tool_calls: list[AssistantToolCall] | None = None)`
|
|
326
506
|
- `SystemMessage(content: str)`
|
|
327
507
|
- `ToolResultMessage(name: str, tool_call_id: str, content: str)`
|
|
328
508
|
- `ChatMessage` - Type alias for any message type
|
|
509
|
+
- `TextContent(text: str)` - Text block for multimodal messages
|
|
510
|
+
- `ImageContent(source: str | dict, media_type: str | None = None)` - Image block for vision support
|
|
329
511
|
|
|
330
512
|
## Contributing
|
|
331
513
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
casual_llm/__init__.py,sha256=
|
|
1
|
+
casual_llm/__init__.py,sha256=Q33PqWR5dedzjkbTdk8ctVRuFWDd-EISJKOCMWj1GG4,2495
|
|
2
2
|
casual_llm/config.py,sha256=9h6zzMYKvR_Ia5OAPlA8uynIvRVccRrLARWc2_uJn6s,1836
|
|
3
3
|
casual_llm/messages.py,sha256=x593dOc2K1Yoj_nbqPP6oewKBSlwVZTUeWKpmJM2WlA,2881
|
|
4
4
|
casual_llm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -19,8 +19,8 @@ casual_llm/tool_converters/ollama.py,sha256=jaSco33-Px_KPUIzqIQC3BqkXG8180fyMeet
|
|
|
19
19
|
casual_llm/tool_converters/openai.py,sha256=n4Yi3rN19CwhwevwNb_XC4RagFrS3HohkUsu5A7b6Hw,1880
|
|
20
20
|
casual_llm/utils/__init__.py,sha256=E-XvHlYDjMidAW--CRVJHwvNnUJkiL88G9mVq1w71o0,142
|
|
21
21
|
casual_llm/utils/image.py,sha256=nlE7CoPSfc0cFUrFMzyVefJ8nAN3FAHYDicAIA4YK8I,5465
|
|
22
|
-
casual_llm-0.4.
|
|
23
|
-
casual_llm-0.4.
|
|
24
|
-
casual_llm-0.4.
|
|
25
|
-
casual_llm-0.4.
|
|
26
|
-
casual_llm-0.4.
|
|
22
|
+
casual_llm-0.4.2.dist-info/licenses/LICENSE,sha256=-PvmTd5xNNGaePz8xckUDBswdIrPWi4L6m7EsyKLmb8,1072
|
|
23
|
+
casual_llm-0.4.2.dist-info/METADATA,sha256=7hY2vq8413TFZJ4X4UC8SJ1j_PbkNG3R_I2i-_zr0bg,15456
|
|
24
|
+
casual_llm-0.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
25
|
+
casual_llm-0.4.2.dist-info/top_level.txt,sha256=KKXzOGm04MbK756RPCswL3zeJ6Z-DvaCWy48Mch3HAo,11
|
|
26
|
+
casual_llm-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|