python-fastllm 0.0.1__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.
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-fastllm
3
+ Version: 0.0.1
4
+ Author-email: Kerem Turgutlu <keremturgutlu@gmail.com>
5
+ License: Apache-2.0
6
+ Project-URL: Repository, https://github.com/AnswerDotAI/fastllm
7
+ Keywords: nbdev
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: fastspec
13
+ Requires-Dist: toolslm
14
+
15
+ # fastllm
16
+
17
+
18
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
19
+
20
+ ## Install
21
+
22
+ Clone and install locally into your `aai-ws` env
23
+
24
+ ## Setup
25
+
26
+ ``` python
27
+ from fastllm.types import Msg, Part, PartType, Completion
28
+ from fastllm.acomplete import acomplete, mk_tool_res_msg
29
+ import asyncio, json
30
+
31
+ # Helpers
32
+ def user(text): return Msg(role='user', content=[Part(type=PartType.text, text=text)])
33
+
34
+ async def stream(msgs, model, **kw):
35
+ "Stream a response, printing text/thinking as it arrives. Returns the final Completion."
36
+ cnt, max_think = 0, 10
37
+ async for o in await acomplete(msgs, model, stream=True, **kw):
38
+ if not isinstance(o, Completion):
39
+ if o.get('thinking') and cnt < max_think: print('🧠', end='', flush=True)
40
+ if txt := o.get('text'): print(txt, end='', flush=True)
41
+ cnt += 1
42
+ print()
43
+ return o
44
+ ```
45
+
46
+ ``` python
47
+ mtok = 1024
48
+ ```
49
+
50
+ ## Chat β€” One Interface, Every Provider
51
+
52
+ The same [`acomplete`](./acomplete.html#acomplete) call works with
53
+ Claude, GPT, Gemini, and Kimi. Just change the model name:
54
+
55
+ ``` python
56
+ models = [
57
+ ('claude-sonnet-4-20250514', {}),
58
+ ('gpt-4o-mini', {}),
59
+ ('models/gemini-3-flash-preview', {}),
60
+ ('accounts/fireworks/models/kimi-k2p5', dict(vendor_name='fireworks_ai'))
61
+ ]
62
+ for name, kw in models:
63
+ r = await acomplete([user("Say 'hello' in French.")], model=name, max_tokens=mtok, **kw)
64
+ print(f"{name:>30s} β†’ {r.message.content[0].text.strip()}")
65
+ ```
66
+
67
+ claude-sonnet-4-20250514 β†’ Bonjour!
68
+ gpt-4o-mini β†’ "Hello" in French is "Bonjour."
69
+ models/gemini-3-flash-preview β†’ Bonjour.
70
+ accounts/fireworks/models/kimi-k2p5 β†’ The user wants me to say "hello" in French. The word for "hello" in French is "Bonjour".
71
+
72
+ This is a simple, straightforward request. I should provide the translation and perhaps a bit of context (like when it's used), but the main thing is to say "Bonjour".
73
+
74
+ I should not overcomplicate this. Just provide the French word for hello.
75
+
76
+ ## Multi-Turn β€” Swap Providers Mid-Conversation
77
+
78
+ Build a conversation with one provider, then seamlessly continue it with
79
+ another. `fastllm` translates between every provider’s native format
80
+ automatically:
81
+
82
+ ``` python
83
+ # Turn 1: Claude starts the conversation
84
+ msgs = [user("Name the 3 largest planets in our solar system. One sentence.")]
85
+ print("Claude: ", end='')
86
+ r1 = await stream(msgs, model='claude-sonnet-4-20250514', max_tokens=mtok)
87
+ ```
88
+
89
+ Claude: The three largest planets in our solar system are Jupiter, Saturn, and Neptune.
90
+
91
+ ``` python
92
+ # Turn 2: Switch to GPT β€” just change the model string
93
+ msgs += [r1.message, user("Which one has the most moons?")]
94
+ print("GPT: ", end='')
95
+ r2 = await stream(msgs, model='gpt-4o-mini', max_tokens=mtok)
96
+ ```
97
+
98
+ GPT: As of now, Saturn has the most moons, surpassing Jupiter.
99
+
100
+ ``` python
101
+ # Turn 3: Switch to Gemini β€” same msgs, different provider
102
+ msgs += [r2.message, user("Summarize our conversation in one sentence.")]
103
+ print("Gemini: ", end='')
104
+ r3 = await stream(msgs, model='models/gemini-3-flash-preview', max_tokens=mtok)
105
+ ```
106
+
107
+ Gemini: We identified the largest planets in our solar system and confirmed that Saturn currently has the most moons.
108
+
109
+ ``` python
110
+ # Turn 4: Switch to Kimi β€” works the same way
111
+ msgs += [r3.message, user("Thanks! What's one surprising fact about Saturn?")]
112
+ print("Kimi: ", end='')
113
+ r4 = await stream(msgs, model='accounts/fireworks/models/kimi-k2p5', vendor_name='fireworks_ai', max_tokens=mtok)
114
+ ```
115
+
116
+ Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠Saturn is the only planet less dense than water, meaning it would theoretically float in a giant bathtub.
117
+
118
+ ## System Prompts
119
+
120
+ Pass a system prompt to any provider β€” `fastllm` maps it to each API’s
121
+ native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini
122
+ `system_instruction`):
123
+
124
+ ``` python
125
+ sys = "You are a pirate chef. Always respond in pirate speak and mention food."
126
+
127
+ print("Claude: ", end='')
128
+ r = await stream([user("What should I do today?")], model='claude-sonnet-4-20250514', system=sys, max_tokens=mtok)
129
+
130
+ print("Gemini: ", end='')
131
+ r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
132
+ ```
133
+
134
+ Claude: Ahoy there, me hearty! *tips pirate hat*
135
+
136
+ Ye be askin' what to do today, eh? Well, as a seasoned sea cook, I'd say ye should start by raidin' yer galley for some fine grub! Perhaps whip up some hearty hardtack biscuits or a steamin' bowl of seafarer's stew with potatoes and salted pork, arrr!
137
+
138
+ If ye be feelin' adventurous, why not venture out to the market like ye be searchin' for buried treasure? Hunt down the finest fish, the ripest tropical fruits, and maybe some exotic spices from distant shores!
139
+
140
+ And if the weather be fair, consider cookin' outdoors on the deck - er, I mean yer patio! Fire up the grill and roast some catch of the day with a side of ship's biscuits. Nothing beats the taste of food cooked under the open sky, savvy?
141
+
142
+ Whatever ye choose to do, make sure ye don't sail on an empty stomach, ye scallywag! A well-fed pirate be a happy pirate!
143
+
144
+ *waves wooden spoon in the air*
145
+
146
+ Now off with ye, and may yer day be filled with delicious adventures! Arrr! πŸ΄β€β˜ οΈ
147
+ Gemini: Ahoy there, ye scurvy dog! If ye be lookin' for a way to spend the tide, I’ve got a recipe for adventureβ€”and a belly full of grub!
148
+
149
+ First, haul yer carcass down to the galley! A true buccaneer starts the day by breakin' his fast with a bowl of **Salmagundi**β€”that be a fine mess of chopped meats, pickled herrings, and whatever greens we haven't fed to the parrots yet.
150
+
151
+ Once yer belly is lined with **salted pork and hardtack**, ye ought to sharpen yer cutlass and go huntin' for the greatest treasure of all: a merchant ship carryin' a fresh crate of **cinnamon and exotic nutmeg**!
152
+
153
+ If the winds be calm, ye can spend the afternoon fishin' off the stern for some **mahi-mahi**. We’ll grill it over a charcoal brazier with a squeeze of **sour lime** to keep the scurvy from claimin' yer teeth!
154
+
155
+ Now scurry off before I make ye peel a mountain of **onions** for me famous "Shipwreck Stew"! Arrr!
156
+
157
+ ## Tool Calling β€” Define Once, Use Anywhere
158
+
159
+ Define tools in a single canonical format. `fastllm` translates to each
160
+ provider’s native tool schema automatically. Here we start a tool-use
161
+ conversation with Claude, provide the result, then switch to GPT and
162
+ Gemini to continue:
163
+
164
+ ``` python
165
+ tools = [{"type": "function", "function": {
166
+ "name": "get_weather",
167
+ "description": "Get current weather for a city",
168
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
169
+ }}]
170
+
171
+ # Turn 1: Claude requests the tool
172
+ msgs = [user("What's the weather in Paris?")]
173
+ r1 = await stream(msgs, model='claude-sonnet-4-20250514', tools=tools, max_tokens=mtok)
174
+ print("Tool calls:", r1.tool_calls)
175
+ ```
176
+
177
+ I'll get the current weather information for Paris.
178
+ Tool calls: [ToolCall(id='toolu_01JNRNBQxzxT7uMXFQh16g8H', name='get_weather', arguments={'city': 'Paris'}, server=False, extra={'caller': {'type': 'direct'}})]
179
+
180
+ ``` python
181
+ # Provide the tool result
182
+ msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22Β°C, sunny with light clouds'])]
183
+
184
+ # Turn 2: Switch to GPT to interpret the result
185
+ msgs.append(user("Should I bring a jacket?"))
186
+ print("GPT: ", end='')
187
+ r2 = await stream(msgs, model='gpt-4o-mini', tools=tools, max_tokens=mtok)
188
+ ```
189
+
190
+ GPT: With the current temperature in Paris at 22Β°C and sunny with light clouds, you likely won't need a jacket. A light layer might be comfortable if you're out in the evening, but otherwise, it should be warm enough.
191
+
192
+ ``` python
193
+ # Turn 3: Gemini sees the full cross-provider tool history
194
+ msgs += [r2.message, user("How about tomorrow β€” will it rain?")]
195
+ r3 = await stream(msgs, model='models/gemini-3-flash-preview', tools=tools, max_tokens=mtok, web_search_options={})
196
+ ```
197
+
198
+ No, it's not expected to rain in Paris tomorrow, Thursday, April 30.
199
+
200
+ The forecast is looking very pleasant with sunny skies throughout the day and clear conditions at night. You can expect a high of around **24Β°C (75Β°F)** and a low of **11Β°C (52Β°F)**. It should be a great day for outdoor activities, though the temperature will drop in the evening, so you might want that jacket if you're out late!
201
+
202
+ Rain is currently predicted to return on Friday, May 1.
203
+
204
+ ## Tool Choice
205
+
206
+ Control whether the model must use tools, can’t use tools, or decides on
207
+ its own:
208
+
209
+ ``` python
210
+ # Force the model to call a tool (even for a greeting)
211
+ r = await stream([user("Hello there!")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='required', max_tokens=mtok)
212
+ print("Forced:", r.tool_calls)
213
+
214
+ # Prevent tool use (model must answer directly)
215
+ print("\nNo tools: ", end='')
216
+ r = await stream([user("What's the weather?")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='none', max_tokens=mtok)
217
+ ```
218
+
219
+
220
+ Forced: [ToolCall(id='toolu_01PvVn1rxozZjW3FBwmVPHms', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, extra={'caller': {'type': 'direct'}})]
221
+
222
+ No tools: I'd be happy to help you check the weather! However, I need to know which city you'd like me to check the weather for. Could you please tell me the name of the city?
223
+
224
+ ## Thinking / Extended Reasoning
225
+
226
+ Enable model reasoning with `reasoning_effort`. The canonical values
227
+ (`low`, `medium`, `high`) map to each provider’s native budget system.
228
+ Thinking tokens stream as 🧠:
229
+
230
+ ``` python
231
+ # Claude with thinking
232
+ print("Claude: ", end='')
233
+ r = await stream([user("What is 127 Γ— 849?")], model='claude-sonnet-4-6', reasoning_effort='low', max_tokens=8192)
234
+ for p in r.message.content:
235
+ if p.type == PartType.thinking: print(f"\n🧠 {p.text[:150]}...")
236
+
237
+ # Kimi with thinking β€” same interface
238
+ print("\nKimi: ", end='')
239
+ r = await stream([user("What is 127 Γ— 849?")], model='accounts/fireworks/models/kimi-k2p5', vendor_name='fireworks_ai', reasoning_effort='low', max_tokens=8192)
240
+ for p in r.message.content:
241
+ if p.type == PartType.thinking: print(f"\n🧠 {p.text[:150]}...")
242
+ ```
243
+
244
+ Claude: 🧠🧠🧠## Calculating 127 Γ— 849
245
+
246
+ **Breaking it down:**
247
+ - 127 Γ— 800 = 101,600
248
+ - 127 Γ— 40 = 5,080
249
+ - 127 Γ— 9 = 1,143
250
+
251
+ **Adding the parts:**
252
+ 101,600 + 5,080 + 1,143 = **107,823**
253
+
254
+ 🧠 127 Γ— 849:
255
+ 127 Γ— 800 = 101,600
256
+ 127 Γ— 49 = 127 Γ— 50 - 127 = 6,350 - 127 = 6,223
257
+ Total: 107,823...
258
+
259
+ Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠$127 \times 849 = 107{,}823$
260
+
261
+ 🧠 The user is asking for the product of 127 and 849. I need to calculate this.
262
+
263
+ Let me break it down:
264
+ 127 Γ— 849
265
+
266
+ I can use the distributive property:
267
+ 12...
268
+
269
+ ## Web Search (Server Tools)
270
+
271
+ OpenAI’s Responses API supports server-side web search. Server tool
272
+ calls are normalized alongside regular tool calls:
273
+
274
+ ``` python
275
+ ws_tools = [{"type": "web_search_preview"}]
276
+ print("GPT + web search: ", end='')
277
+ r = await stream([user("What is the latest Python release?")], model='gpt-4o-mini', tools=ws_tools, max_tokens=512)
278
+ print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
279
+ ```
280
+
281
+ GPT + web search: As of April 29, 2026, the latest stable release of Python is version 3.14.4, which was released on April 7, 2026. ([ivyml.space](https://ivyml.space/downloads/?utm_source=openai)) This version is currently in the "bugfix" phase, receiving full support for bug fixes and security updates. Python 3.14 was first released on October 7, 2025, and is scheduled to receive support until October 31, 2030. ([eol.wiki](https://eol.wiki/python/?utm_source=openai))
282
+
283
+ Python 3.15 is currently in the alpha development phase, with the first alpha release (3.15.0a1) planned for October 14, 2025. ([peps.python.org](https://peps.python.org/pep-0790/?utm_source=openai)) The stable release of Python 3.15 is expected in October 2026.
284
+
285
+ For more information on Python releases and their support statuses, you can visit the official Python documentation. ([python.org](https://www.python.org/doc/versions/?utm_source=openai))
286
+
287
+ Server tools used: ['web_search']
288
+
289
+ ## Caching (Anthropic)
290
+
291
+ Anthropic supports prompt caching via `cache_control`. Pass it through
292
+ `Part.data` β€” repeat calls with the same cached content save tokens:
293
+
294
+ ``` python
295
+ # Cache a long system prompt (must be >1024 tokens for Anthropic caching)
296
+ long_ctx = "You are an expert on the solar system. " * 200
297
+ system = Part(type=PartType.text, text=long_ctx, data={'cache_control': {'type': 'ephemeral'}})
298
+
299
+ print("Call 1: ", end='')
300
+ r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-4-20250514', system=system, max_tokens=mtok)
301
+ print(f"Usage: {r1.usage}")
302
+
303
+ print("Call 2: ", end='')
304
+ r2 = await stream([user("How about Saturn?")], model='claude-sonnet-4-20250514', system=system, max_tokens=mtok)
305
+ print(f"Usage: {r2.usage}")
306
+ ```
307
+
308
+ Call 1: Jupiter's mass is approximately 1.898 Γ— 10^27 kilograms (or 1,898,000,000,000,000,000,000,000,000 kg).
309
+
310
+ To put this in perspective:
311
+ - Jupiter is about 318 times more massive than Earth
312
+ - It contains more than twice the mass of all other planets in our solar system combined
313
+ - It's about 1/1047th the mass of our Sun
314
+
315
+ This enormous mass is what makes Jupiter such a dominant gravitational force in our solar system, allowing it to capture and hold onto its 95+ known moons and play a crucial role in shaping the orbits of other celestial bodies.
316
+ Usage: Usage(prompt_tokens=1814, completion_tokens=151, total_tokens=1965, cached_tokens=0, cache_creation_tokens=1802, reasoning_tokens=0, raw={'input_tokens': 12, 'cache_creation_input_tokens': 1802, 'cache_read_input_tokens': 0, 'output_tokens': 151})
317
+ Call 2: Saturn is truly one of the most spectacular planets in our solar system! Here are some key facts about this magnificent world:
318
+
319
+ **Basic characteristics:**
320
+ - The sixth planet from the Sun and second-largest in our solar system
321
+ - A gas giant composed primarily of hydrogen and helium
322
+ - About 9.5 times Earth's distance from the Sun
323
+ - Takes about 29.5 Earth years to orbit the Sun
324
+
325
+ **Famous ring system:**
326
+ - Saturn's rings are its most iconic feature - made of countless ice and rock particles
327
+ - The main rings span about 175,000 miles across but are surprisingly thin (often less than 30 feet thick)
328
+ - There are several distinct ring groups (A, B, C rings are the most prominent)
329
+ - The rings may be relatively young - possibly only 10-100 million years old
330
+
331
+ **Physical properties:**
332
+ - Diameter about 9 times larger than Earth
333
+ - Surprisingly low density - it would actually float in water!
334
+ - Rotates very quickly (about 10.5 hour days), causing it to bulge at the equator
335
+ - Has beautiful banded cloud patterns in its atmosphere
336
+
337
+ **Moons:**
338
+ - Saturn has 146 confirmed moons, including Titan (larger than Mercury, with lakes of liquid methane) and Enceladus (which shoots geysers of water ice from its south pole)
339
+
340
+ **Exploration:**
341
+ - Visited by Pioneer 11, Voyager 1 & 2, and most extensively by the Cassini mission (2004-2017)
342
+
343
+ What aspect of Saturn interests you most?
344
+ Usage: Usage(prompt_tokens=1812, completion_tokens=350, total_tokens=2162, cached_tokens=1802, cache_creation_tokens=0, reasoning_tokens=0, raw={'input_tokens': 10, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 1802, 'output_tokens': 350})
345
+
346
+ ## Media Inputs
347
+
348
+ Send images to any provider that supports them. The canonical
349
+ `input_image` part works everywhere:
350
+
351
+ ``` python
352
+ img_url = "https://img.freepik.com/free-photo/mountain-range-body-water_53876-139760.jpg?semt=ais_hybrid&w=740&q=80"
353
+ img_msg = Msg(role='user', content=[
354
+ Part(type=PartType.input_image, text=img_url),
355
+ Part(type=PartType.text, text="What do you see in this image?")
356
+ ])
357
+
358
+ for name, kw in [('claude-sonnet-4-20250514', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
359
+ print(f"{name:>30s}: ", end='')
360
+ r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
361
+ ```
362
+
363
+ claude-sonnet-4-20250514: I see a beautiful, serene landscape featuring:
364
+
365
+ - A calm lake or body of water that creates perfect mirror-like reflections
366
+ - Snow-capped mountains in the background creating a dramatic backdrop
367
+ - Dense forest of evergreen trees (likely pine or fir) lining the shoreline
368
+ - A wooden deck or boardwalk in the foreground with weathered planks
369
+ - Clear blue sky with some clouds
370
+ - The scene has a peaceful, pristine quality typical of alpine or mountain lake environments
371
+
372
+ The composition creates a sense of tranquility and natural beauty, with the wooden platform providing a viewing point to take in this scenic mountain landscape. The perfect reflections in the still water double the visual impact of the mountains and trees.
373
+ gpt-4o-mini: The image shows a serene landscape featuring a calm lake that reflects surrounding mountains and trees. The foreground includes a wooden deck or platform, which adds a sense of depth to the scene. In the background, the mountains are partially covered by mist, creating a peaceful and tranquil atmosphere. The overall color palette is dominated by blues and greens, evoking a natural and serene vibe.
374
+ models/gemini-3-flash-preview: This image is a serene landscape featuring a mountain lake. It is composed of several distinct layers:
375
+
376
+ * **Foreground:** At the bottom of the image, there is a rustic **wooden deck or platform** made of dark brown, weathered planks. The perspective makes it look as though the viewer is standing on this deck looking out at the view.
377
+ * **Middle Ground:** A **calm, blue lake** sits directly behind the deck. The water is so still that it acts like a mirror, perfectly reflecting the trees and mountains above it.
378
+ * **Shoreline:** Along the edge of the water, there is a thin strip of golden-yellow grass or low-lying vegetation. This is followed by a **dense line of green trees**, including some tall, dark conifers.
379
+ * **Background:** In the far distance, **massive mountains** rise up. They are a hazy blue-grey color, with their peaks covered in snow and ice.
380
+ * **Sky:** The sky is a very pale blue, almost white, with soft, bright light suggesting a sunny but perhaps slightly hazy day.
381
+
382
+ The overall atmosphere of the image is peaceful and majestic.
383
+
384
+ `fastllm` supports four media part types via `PartType`. Provider
385
+ support varies:
386
+
387
+ | Part Type | Anthropic | OpenAI Responses | OpenAI Chat | Gemini |
388
+ |---------------|-----------|------------------|-------------|--------|
389
+ | `input_image` | βœ… | βœ… | βœ… | βœ… |
390
+ | `input_audio` | ❌ | ❌ (coming soon) | βœ… | βœ… |
391
+ | `input_video` | ❌ | ❌ | ❌ | βœ… |
392
+ | `input_file` | βœ… | βœ… | βœ… | βœ… |
393
+
394
+ All media parts accept either a URL or a base64 data URL in `Part.text`.
395
+ Unsupported combinations raise a clear `ValueError`.