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 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.1"
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.1
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
- - 🔌 **Provider-agnostic** - Works with OpenAI, Ollama, or your custom provider
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 casual_llm import LLMProvider, ChatMessage, AssistantMessage, Tool, Usage
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** | 2 (pydantic, ollama) | 100+ | 50+ |
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=2nbt-Q-o17RKc4R_rr5rqJ2FB4rjdCGm2lLSbwcF6DE,2495
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.1.dist-info/licenses/LICENSE,sha256=-PvmTd5xNNGaePz8xckUDBswdIrPWi4L6m7EsyKLmb8,1072
23
- casual_llm-0.4.1.dist-info/METADATA,sha256=FgCfKs84Dh1ybuGJEu7aA1OFVcc7xGKvLzGrCWmy6Rs,9964
24
- casual_llm-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- casual_llm-0.4.1.dist-info/top_level.txt,sha256=KKXzOGm04MbK756RPCswL3zeJ6Z-DvaCWy48Mch3HAo,11
26
- casual_llm-0.4.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5