python-fastllm 0.0.36__tar.gz → 0.0.38__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.
Files changed (33) hide show
  1. python_fastllm-0.0.38/PKG-INFO +408 -0
  2. python_fastllm-0.0.38/README.md +392 -0
  3. python_fastllm-0.0.38/fastllm/__init__.py +1 -0
  4. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/_modidx.py +25 -17
  5. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/acomplete.py +1 -1
  6. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/anthropic.py +56 -54
  7. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/chat.py +78 -129
  8. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/gemini.py +31 -34
  9. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/openai_chat.py +26 -29
  10. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/openai_responses.py +29 -33
  11. python_fastllm-0.0.38/fastllm/streaming.py +194 -0
  12. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/types.py +13 -10
  13. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/pyproject.toml +1 -1
  14. python_fastllm-0.0.38/python_fastllm.egg-info/PKG-INFO +408 -0
  15. python_fastllm-0.0.38/python_fastllm.egg-info/requires.txt +4 -0
  16. python_fastllm-0.0.36/PKG-INFO +0 -398
  17. python_fastllm-0.0.36/README.md +0 -381
  18. python_fastllm-0.0.36/fastllm/__init__.py +0 -1
  19. python_fastllm-0.0.36/fastllm/streaming.py +0 -160
  20. python_fastllm-0.0.36/python_fastllm.egg-info/PKG-INFO +0 -398
  21. python_fastllm-0.0.36/python_fastllm.egg-info/requires.txt +0 -5
  22. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/codex.py +0 -0
  23. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/anthropic.json +0 -0
  24. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/anthropic.yml +0 -0
  25. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/gemini.json +0 -0
  26. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/openai.with-code-samples.json +0 -0
  27. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/openai.with-code-samples.yml +0 -0
  28. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/fastllm/specs/spec_manifest.json +0 -0
  29. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/python_fastllm.egg-info/SOURCES.txt +0 -0
  30. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/python_fastllm.egg-info/dependency_links.txt +0 -0
  31. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/python_fastllm.egg-info/entry_points.txt +0 -0
  32. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/python_fastllm.egg-info/top_level.txt +0 -0
  33. {python_fastllm-0.0.36 → python_fastllm-0.0.38}/setup.cfg +0 -0
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-fastllm
3
+ Version: 0.0.38
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: fastcore>=2.1.18
13
+ Requires-Dist: aidialog>=0.0.8
14
+ Requires-Dist: fastspec>=0.0.11
15
+ Requires-Dist: pillow
16
+
17
+ # fastllm
18
+
19
+
20
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
21
+
22
+ ## Install
23
+
24
+ Clone and install locally into your `aai-ws` env
25
+
26
+ ## Setup
27
+
28
+ ``` python
29
+ from fastllm.types import Completion
30
+ from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg
31
+ from fastllm.acomplete import acomplete
32
+ import asyncio, json
33
+
34
+ # Helpers
35
+ def user(text): return Msg(role='user', content=[Text(text)])
36
+
37
+ async def stream(msgs, model, max_think=10, **kw):
38
+ "Stream a response, printing each item's `formatted` (🧠 per thinking delta). Returns the final Completion."
39
+ cnt = 0
40
+ async for o in await acomplete(msgs, model, stream=True, **kw):
41
+ if not isinstance(o, Part): continue
42
+ if isinstance(o, Thinking):
43
+ cnt += 1
44
+ if cnt > max_think: continue
45
+ print(o.formatted, end='', flush=True)
46
+ print()
47
+ return o
48
+ ```
49
+
50
+ ``` python
51
+ mtok = 1024
52
+ ```
53
+
54
+ ## Chat — One Interface, Every Provider
55
+
56
+ The same `acomplete` call works with Claude, GPT, Gemini, and Kimi. Just change the model name:
57
+
58
+ ``` python
59
+ models = [
60
+ ('claude-sonnet-4-20250514', {}),
61
+ ('gpt-4o-mini', {}),
62
+ ('models/gemini-3-flash-preview', {}),
63
+ ('accounts/fireworks/models/kimi-k2p5', dict(vendor_name='fireworks_ai'))
64
+ ]
65
+ for name, kw in models:
66
+ r = await acomplete([user("Say 'hello' in French.")], model=name, max_tokens=mtok, **kw)
67
+ print(f"{name:>30s} → {r.message.content[0].text.strip()}")
68
+ ```
69
+
70
+ claude-sonnet-4-20250514 → Bonjour!
71
+ gpt-4o-mini → In French, "hello" is said as "bonjour."
72
+ models/gemini-3-flash-preview → Bonjour.
73
+ accounts/fireworks/models/kimi-k2p5 → The user is asking me to say "hello" in French. This is a very straightforward request. The common ways to say hello in French are:
74
+
75
+ 1. "Bonjour" - the standard, formal way to say hello/good day
76
+ 2. "Salut" - informal way to say hi/hello (also used for goodbye)
77
+ 3. "Bonsoir" - good evening
78
+ 4. "Coucou" - very informal, cute way to say hi
79
+
80
+ Since the user just asked for "hello" without specifying context, "Bonjour" is the most appropriate and standard answer. I should provide the most common translation and perhaps mention the informal alternative for completeness.
81
+
82
+ The response should be simple and direct.
83
+
84
+ ## Multi-Turn — Swap Providers Mid-Conversation
85
+
86
+ Build a conversation with one provider, then seamlessly continue it with another. `fastllm` translates between every provider’s native format automatically:
87
+
88
+ ``` python
89
+ # Turn 1: Claude starts the conversation
90
+ msgs = [user("Name the 3 largest planets in our solar system. One sentence.")]
91
+ print("Claude: ", end='')
92
+ r1 = await stream(msgs, model='claude-sonnet-4-20250514', max_tokens=mtok)
93
+ ```
94
+
95
+ Claude: The three largest planets in our solar system are Jupiter, Saturn, and Neptune.
96
+
97
+ ``` python
98
+ # Turn 2: Switch to GPT — just change the model string
99
+ msgs += [r1.message, user("Which one has the most moons?")]
100
+ print("GPT: ", end='')
101
+ r2 = await stream(msgs, model='gpt-4o-mini', max_tokens=mtok)
102
+ ```
103
+
104
+ GPT: As of now, Saturn has the most moons, with over 80 confirmed moons.
105
+
106
+ ``` python
107
+ # Turn 3: Switch to Gemini — same msgs, different provider
108
+ msgs += [r2.message, user("Summarize our conversation in one sentence.")]
109
+ print("Gemini: ", end='')
110
+ r3 = await stream(msgs, model='models/gemini-3-flash-preview', max_tokens=mtok)
111
+ ```
112
+
113
+ Gemini: The conversation identified the three largest planets in the solar system and noted that Saturn currently has the most moons.
114
+
115
+ ``` python
116
+ # Turn 4: Switch to Kimi — works the same way
117
+ msgs += [r3.message, user("Thanks! What's one surprising fact about Saturn?")]
118
+ print("Kimi: ", end='')
119
+ r4 = await stream(msgs, model='accounts/fireworks/models/kimi-k2p5', vendor_name='fireworks_ai', max_tokens=mtok)
120
+ ```
121
+
122
+ Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠Saturn is less dense than water, meaning it would theoretically float if you could find a bathtub large enough to hold it.
123
+
124
+ ## System Prompts
125
+
126
+ Pass a system prompt to any provider — `fastllm` maps it to each API’s native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini `system_instruction`):
127
+
128
+ ``` python
129
+ sys = "You are a pirate chef. Always respond in pirate speak and mention food."
130
+
131
+ print("Claude: ", end='')
132
+ r = await stream([user("What should I do today?")], model='claude-sonnet-4-20250514', system=sys, max_tokens=mtok)
133
+
134
+ print("Gemini: ", end='')
135
+ r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
136
+ ```
137
+
138
+ Claude: Ahoy there, me hearty! *tips chef's hat with a feathered plume*
139
+
140
+ Ye be askin' what to do on this fine day, eh? Well, shiver me spatulas, I've got some tasty suggestions for ye!
141
+
142
+ First, ye should be raidin' yer galley (that be yer kitchen, landlubber!) and whip up some grub fit for a crew! Perhaps some hearty sea biscuits with honey, or a fine fish stew that'll warm yer bones like treasure warms the heart!
143
+
144
+ Then, if the weather be fair, take yerself outside and feel that salty breeze on yer face while ye munch on some portable provisions - maybe some dried fruits and nuts, perfect for any seafarin' adventure!
145
+
146
+ And if ye be feelin' ambitious, batten down the hatches and try cookin' somethin' new! A spicy jambalaya or some coconut rice that'll transport ye straight to the Caribbean seas!
147
+
148
+ Remember, me matey - a pirate's day ain't complete without good food in yer belly and the spirit of adventure in yer heart! Now off with ye, and may yer meals be as bountiful as buried treasure!
149
+
150
+ *waves wooden spoon like a cutlass*
151
+
152
+ Arrr, what say ye? Does any of this grub sound temptin' to yer taste buds?
153
+ Gemini: Ahoy there, ye scurvy bilge-rat! If ye be lookin' for a way to spend yer daylight hours, I’ve got just the plan for a hungry soul like yerself.
154
+
155
+ First, ye should sharpen yer cutlass—not for fightin' off the Royal Navy, mind ye, but for slicin' through a thick slab of **salted pork** and some **ripe mangoes**! A dull blade is the mark of a lazy cook, and we'll have none of that on this vessel.
156
+
157
+ Once yer steel is keen, head down to the shoreline and see if ye can't scavenge some **clams** or a **fat crustacean** from the tide pools. If ye find a crab the size of a cannonball, we'll toss it in the pot with some stolen spices and a splash of grog to make a **bisque** that’ll make yer toes curl!
158
+
159
+ And if the wind be light, ye can spend the afternoon scrubbin' the barnacles off the hull—it builds an appetite for a massive bowl of **hardtack and lobscouse stew**.
160
+
161
+ Now, quit yer lollygaggin' and get to the galley! There be **onions** that won't peel themselves, and I'll be hornswoggled if I let a single clove of **garlic** go to waste! Arrr!
162
+
163
+ ## Tool Calling — Define Once, Use Anywhere
164
+
165
+ Define tools in a single canonical format. `fastllm` translates to each provider’s native tool schema automatically. Here we start a tool-use conversation with Claude, provide the result, then switch to GPT and Gemini to continue:
166
+
167
+ ``` python
168
+ tools = [{"type": "function", "function": {
169
+ "name": "get_weather",
170
+ "description": "Get current weather for a city",
171
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
172
+ }}]
173
+
174
+ # Turn 1: Claude requests the tool
175
+ msgs = [user("What's the weather in Paris?")]
176
+ r1 = await stream(msgs, model='claude-sonnet-4-20250514', tools=tools, max_tokens=mtok)
177
+ print("Tool calls:", r1.tool_calls)
178
+ ```
179
+
180
+ I'll check the current weather in Paris for you.
181
+ Tool calls: [ToolCall(id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, extra={'caller': {'type': 'direct'}})]
182
+
183
+ ``` python
184
+ # Provide the tool result
185
+ msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds'])]
186
+
187
+ # Turn 2: Switch to GPT to interpret the result
188
+ msgs.append(user("Should I bring a jacket?"))
189
+ print("GPT: ", end='')
190
+ r2 = await stream(msgs, model='gpt-4o-mini', tools=tools, max_tokens=mtok)
191
+ ```
192
+
193
+ GPT: With the temperature at 22°C and sunny with light clouds, a jacket isn't necessary for most people. However, if you tend to feel cold easily or if you plan to be out in the evening when it might cool down a bit, it could be a good idea to bring a light jacket.
194
+
195
+ ``` python
196
+ # Turn 3: Gemini sees the full cross-provider tool history
197
+ msgs += [r2.message, user("How about tomorrow — will it rain?")]
198
+ r3 = await stream(msgs, model='models/gemini-3-flash-preview', tools=tools, max_tokens=mtok, web_search_options={})
199
+ ```
200
+
201
+ Tomorrow in Paris (Saturday, June 13), the weather is expected to be mostly pleasant and sunny.
202
+
203
+ While there is a small chance of light, passing showers (around a 20-25% chance), most forecasts indicate a dry day with mostly clear or scattered clouds. Temperatures will likely reach a comfortable high of around 22°C to 27°C (72°F to 80°F), making it a great day for being outdoors.
204
+
205
+ So, while you might see a stray shower, you likely won't need to worry about heavy rain!
206
+
207
+ ## Tool Choice
208
+
209
+ Control whether the model must use tools, can’t use tools, or decides on its own:
210
+
211
+ ``` python
212
+ # Force the model to call a tool (even for a greeting)
213
+ r = await stream([user("Hello there!")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='required', max_tokens=mtok)
214
+ print("Forced:", r.tool_calls)
215
+
216
+ # Prevent tool use (model must answer directly)
217
+ print("\nNo tools: ", end='')
218
+ r = await stream([user("What's the weather?")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='none', max_tokens=mtok)
219
+ ```
220
+
221
+
222
+ Forced: [ToolCall(id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, extra={'caller': {'type': 'direct'}})]
223
+
224
+ No tools: I'd be happy to help you get the weather information! However, I need to know which city you'd like me to check the weather for. Could you please tell me the city name?
225
+
226
+ ## Thinking / Extended Reasoning
227
+
228
+ Enable model reasoning with `reasoning_effort`. The canonical values (`low`, `medium`, `high`) map to each provider’s native budget system. 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 isinstance(p, 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 isinstance(p, 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 = 6350 - 127 = 6223
257
+ Total: 107,823...
258
+
259
+ Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠**107,823**
260
+
261
+ Here's the calculation:
262
+ - 127 × 800 = 101,600
263
+ - 127 × 40 = 5,080
264
+ - 127 × 9 = 1,143
265
+ - **Total: 101,600 + 5,080 + 1,143 = 107,823**
266
+
267
+ 🧠 The user is asking for the product of 127 and 849. I need to calculate 127 × 849.
268
+
269
+ Let me calculate this step by step.
270
+
271
+ Method 1: Standard multiplicat...
272
+
273
+ ## Web Search (Server Tools)
274
+
275
+ OpenAI’s Responses API supports server-side web search. Server tool calls are normalized alongside regular tool calls:
276
+
277
+ ``` python
278
+ ws_tools = [{"type": "web_search_preview"}]
279
+ print("GPT + web search: ", end='')
280
+ r = await stream([user("What is the latest Python release?")], model='gpt-4o-mini', tools=ws_tools, max_tokens=512)
281
+ print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
282
+ ```
283
+
284
+ GPT + web search: As of June 12, 2026, the latest stable release of Python is version 3.14.5, which was released on May 10, 2026. ([test.python.org](https://test.python.org/downloads/latest?utm_source=openai))
285
+
286
+ Python 3.14 introduced several significant features, including:
287
+
288
+ - **PEP 779**: Official support for free-threaded Python, allowing threads to run more concurrently.
289
+ - **PEP 649**: Deferred evaluation of annotations, improving the semantics of using annotations.
290
+ - **PEP 750**: Template string literals (t-strings) for custom string processing, using the familiar syntax of f-strings.
291
+ - **PEP 734**: Support for multiple interpreters in the standard library.
292
+ - **PEP 784**: A new module `compression.zstd` providing support for the Zstandard compression algorithm.
293
+
294
+ For a comprehensive list of changes and improvements in Python 3.14, you can refer to the official release notes. ([docs.python.org](https://docs.python.org/it/3.14/whatsnew/3.14.html?utm_source=openai))
295
+
296
+ You can download the latest version of Python from the official Python website. ([python.org](https://www.python.org/downloads/?lang=python&utm_source=openai))
297
+
298
+ Server tools used: ['web_search']
299
+
300
+ ## Caching (Anthropic)
301
+
302
+ Anthropic supports prompt caching via `cache_control`. Set it on a part’s `cache_control` — repeat calls with the same cached content save tokens:
303
+
304
+ ``` python
305
+ # Cache a long system prompt (must be >1024 tokens for Anthropic caching)
306
+ long_ctx = "You are an expert on the solar system. " * 200
307
+ system = Text(long_ctx, cache_control={'type': 'ephemeral'})
308
+
309
+ print("Call 1: ", end='')
310
+ r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-4-20250514', system=system, max_tokens=mtok)
311
+ print(f"Usage: {r1.usage}")
312
+
313
+ print("Call 2: ", end='')
314
+ r2 = await stream([user("How about Saturn?")], model='claude-sonnet-4-20250514', system=system, max_tokens=mtok)
315
+ print(f"Usage: {r2.usage}")
316
+ ```
317
+
318
+ Call 1: Jupiter's mass is approximately 1.898 × 10²⁷ kilograms (or 1,898,000,000,000,000,000,000,000,000 kg).
319
+
320
+ To put this in perspective:
321
+ - Jupiter is about 318 times more massive than Earth
322
+ - It contains more than twice the mass of all other planets in our solar system combined
323
+ - Jupiter's mass is about 1/1047th the mass of the Sun
324
+
325
+ This enormous mass gives Jupiter its strong gravitational influence, which helps it act as a "cosmic vacuum cleaner" by capturing asteroids and comets that might otherwise threaten the inner planets.
326
+ Usage: Usage(prompt_tokens=1814, completion_tokens=145, total_tokens=1959, 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': 145})
327
+ Call 2: Saturn is truly one of the most spectacular planets in our solar system! Here are some key facts about this gas giant:
328
+
329
+ **Basic Characteristics:**
330
+ - Sixth planet from the Sun
331
+ - Second largest planet (after Jupiter)
332
+ - Composed primarily of hydrogen and helium
333
+ - Has the lowest density of any planet - it would actually float in water!
334
+
335
+ **Famous Ring System:**
336
+ - Most prominent and extensive ring system in the solar system
337
+ - Made primarily of ice particles and rocky debris
338
+ - Rings are organized into distinct sections (A, B, C rings are the main ones)
339
+ - Ring particles range from tiny ice crystals to house-sized chunks
340
+
341
+ **Moons:**
342
+ - Has 146 confirmed moons (the most of any planet)
343
+ - Titan is its largest moon - larger than Mercury and has a thick atmosphere
344
+ - Enceladus has geysers of water ice erupting from its south pole
345
+ - Many other fascinating moons like Iapetus, Mimas, and Dione
346
+
347
+ **Physical Features:**
348
+ - Takes about 29.5 Earth years to orbit the Sun
349
+ - A day on Saturn is only about 10.7 hours
350
+ - Has extreme winds reaching up to 1,800 km/h
351
+ - Beautiful hexagonal storm at its north pole
352
+
353
+ **Exploration:**
354
+ - Visited by Pioneer 11, Voyager 1 & 2, and most notably the Cassini mission (2004-2017)
355
+
356
+ Is there a particular aspect of Saturn you'd like to know more about?
357
+ Usage: Usage(prompt_tokens=1812, completion_tokens=337, total_tokens=2149, 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': 337})
358
+
359
+ ## Media Inputs
360
+
361
+ Send images to any provider that supports them. The canonical `input_image` part works everywhere:
362
+
363
+ ``` python
364
+ img_url = "https://img.freepik.com/free-photo/mountain-range-body-water_53876-139760.jpg?semt=ais_hybrid&w=740&q=80"
365
+ img_msg = Msg(role='user', content=[
366
+ InputImage(img_url),
367
+ Text("What do you see in this image?")
368
+ ])
369
+
370
+ for name, kw in [('claude-sonnet-4-20250514', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
371
+ print(f"{name:>30s}: ", end='')
372
+ r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
373
+ ```
374
+
375
+ claude-sonnet-4-20250514: I see a beautiful, serene landscape photograph taken from what appears to be a wooden dock or platform. The image shows:
376
+
377
+ - A perfectly still lake that creates mirror-like reflections
378
+ - Snow-capped mountains in the background under a partly cloudy sky
379
+ - Dense forests of evergreen trees (likely pine or fir) lining the shoreline
380
+ - The mountains, trees, and sky all reflected clearly in the calm water
381
+ - Wooden planks in the foreground, suggesting the photo was taken from a dock or wooden viewing platform
382
+ - The lighting appears soft and atmospheric, possibly during golden hour
383
+ - The overall scene has a very peaceful, pristine wilderness quality typical of places like the Canadian Rockies, Alaska, or similar mountainous lake regions
384
+
385
+ The composition creates a sense of depth and tranquility, with the wooden platform in the foreground leading the eye toward the expansive natural landscape beyond.
386
+ gpt-4o-mini: The image features a serene landscape with a body of water reflecting surrounding trees and mountains. In the foreground, there is a wooden deck or platform, enhancing the picturesque view. The scene is calm and depicts natural beauty, characterized by lush greenery, a clear sky, and distant snow-capped mountains.
387
+ models/gemini-3-flash-preview: This image depicts a serene and majestic landscape, viewed from the perspective of someone standing on a wooden deck.
388
+
389
+ Here is a breakdown of what is visible:
390
+
391
+ * **Foreground:** In the immediate foreground are weathered brown wooden planks of a deck or pier. The planks run diagonally, drawing the eye toward the center of the image.
392
+ * **Midground (The Lake):** Beyond the deck is a large, still lake. The water is a deep blue and acts as a perfect mirror, reflecting the trees and mountains above with startling clarity.
393
+ * **Shoreline:** Along the far edge of the lake is a dense line of lush green evergreen trees. A thin, bright yellow or golden strip of vegetation runs along the very edge of the water.
394
+ * **Background (Mountains):** Towering over the entire scene are massive, rugged mountains. The peaks are covered in white snow or glaciers and appear in shades of light blue and grey due to a soft, atmospheric haze.
395
+ * **Sky:** The sky is a pale, bright blue, almost white in some areas, with a few soft clouds visible in the upper right corner.
396
+
397
+ The overall mood of the image is peaceful, cool-toned, and tranquil.
398
+
399
+ `fastllm` supports four media part classes. Provider support varies:
400
+
401
+ | Media part | Anthropic | OpenAI Responses | OpenAI Chat | Gemini |
402
+ |--------------|-----------|------------------|-------------|--------|
403
+ | `InputImage` | ✅ | ✅ | ✅ | ✅ |
404
+ | `InputAudio` | ❌ | ❌ (coming soon) | ✅ | ✅ |
405
+ | `InputVideo` | ❌ | ❌ | ❌ | ✅ |
406
+ | `InputFile` | ✅ | ✅ | ✅ | ✅ |
407
+
408
+ All media parts accept either a URL or a base64 data URL as their `text`. Unsupported combinations raise a clear `ValueError`.