python-fastllm 0.0.57__py3-none-any.whl → 0.0.59__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.
fastllm/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  - `fastllm.responses`: Provider-independent Responses resources and streaming over FastLLM"""
4
4
 
5
- __version__ = "0.0.57"
5
+ __version__ = "0.0.59"
fastllm/acomplete.py CHANGED
@@ -39,7 +39,7 @@ vendor_mapping = {
39
39
  "fireworks_ai": ('openai_chat', "https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"),
40
40
  "qwen": ('openai_chat', "https://dashscope.aliyuncs.com/compatible-mode/v1", "QWEN_API_KEY"),
41
41
  "minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY"),
42
- "meta": ('openai', "https://api.meta.ai/v1", "META_API_KEY"),
42
+ "meta": ('openai_chat', "https://api.meta.ai/v1", "META_API_KEY"),
43
43
  "zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")}
44
44
 
45
45
  # %% ../nbs/06_acomplete.ipynb #e3ed40fb
@@ -282,6 +282,7 @@ def cost(usage, m):
282
282
  # %% ../nbs/02_oai_responses.ipynb #2efda2a1
283
283
  def fix_payload(payload, model, vendor_name):
284
284
  "Apply vendor request quirks to a built payload."
285
+ if model == 'gpt-6-astra': payload.pop('temperature', None)
285
286
  if vendor_name == 'codex':
286
287
  for k in 'temperature max_tokens max_output_tokens max_completion_tokens metadata'.split(): payload.pop(k, None)
287
288
  payload['store'] = False
fastllm/types.py CHANGED
@@ -7,7 +7,7 @@ __all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'sample_img_url',
7
7
  'sonn', 'sonn5', 'opus46', 'opus48', 'opus', 'opus5', 'fable', 'fable5', 'fable51', 'gpt54', 'gpt54m',
8
8
  'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark', 'model_info_registry', 'modern_llm',
9
9
  'effort_codes', 'deepseek_v4_common', 'deepseek_v4_flash_prices', 'mimo_v25_common', 'codex_pricing', 'sol',
10
- 'terra', 'luna', 'gpt56s', 'Usage', 'APIRegistry', 'mk_completion', 'fn_schema', 'payload_kwargs',
10
+ 'terra', 'luna', 'gpt56s', 'astra', 'Usage', 'APIRegistry', 'mk_completion', 'fn_schema', 'payload_kwargs',
11
11
  'provider_req', 'get_api_key', 'wrap_typed', 'unwrap_typed', 'resize_b64', 'model_prices_meta',
12
12
  'infer_api_name', 'get_model_meta', 'register_model_info', 'get_model_info', 'effort_code', 'effort_levels',
13
13
  'resolve_effort', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour', 'price_tier', 'tier_rate']
@@ -317,11 +317,16 @@ for model in (haik45, sonn45, sonn46, sonn5, opus46, opus48, opus5, fable5, fabl
317
317
 
318
318
  # %% ../nbs/00_types.ipynb #bb0c4c2a
319
319
  sol,terra,luna = gpt56s = 'gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna'.split()
320
+ astra = 'gpt-6-astra'
321
+ for model in (*gpt56s, astra): register_model_info(model, 'openai', base=model, max_input_tokens=272_000)
322
+
320
323
  # Codex serves only the suffixed names; bare `gpt-5.6` is rejected with a ChatGPT account.
321
- # Its window is smaller than the API's: 371,331 input tokens is accepted and 371,981 is not, on all three.
324
+ # Its window accepts 371,331 input tokens and rejects 371,981, on all three.
322
325
  for model in gpt56s:
323
326
  register_model_info(model, 'codex', base=model, base_vendor_name='openai', max_input_tokens=371_000, **codex_pricing)
324
327
  _flat_rates('codex', model)
328
+ register_model_info(astra, 'codex', base=astra, base_vendor_name='openai', **codex_pricing)
329
+ _flat_rates('codex', astra)
325
330
 
326
331
  # %% ../nbs/00_types.ipynb #24cc47ec
327
332
  def get_model_pricing(mn, vendor_name, million=True):
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-fastllm
3
+ Version: 0.0.59
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.27
14
+ Requires-Dist: fasttransport>=0.0.2
15
+ Requires-Dist: pillow
16
+
17
+ # fastllm
18
+
19
+
20
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
21
+
22
+ `fastllm` provides a common async interface for Anthropic, OpenAI Responses and Chat, Gemini, and OpenAI-compatible providers such as Kimi. Define messages and tools in a shared format and call `acomplete`. Select the model and, where needed, its provider without rewriting the conversation or tool loop. Supported features vary by provider.
23
+
24
+ `AsyncResponses` provides a streaming Responses API interface over the same providers. It translates input items and events and keeps a canonical `ResponseState` between turns. For transports without provider continuation ids, it replays that state automatically.
25
+
26
+ ## Install
27
+
28
+ Run `pip install python-fastllm` or clone from github and install locally.
29
+
30
+ ## Setup
31
+
32
+ ``` python
33
+ from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
34
+ from fastllm.acomplete import acomplete
35
+ from fastllm.types import sample_img_url, sample_doms
36
+ import asyncio, json
37
+ ```
38
+
39
+ ``` python
40
+ def user(text): return Msg(role='user', content=[Text(text)])
41
+
42
+ async def stream(msgs, model, max_think=0, **kw):
43
+ "Print streamed parts and return the final Completion; limit printed thinking deltas with max_think."
44
+ cnt = 0
45
+ async for o in await acomplete(msgs, model, stream=True, **kw):
46
+ if not isinstance(o, Part): continue
47
+ if isinstance(o, Thinking):
48
+ cnt += 1
49
+ if cnt > max_think: continue
50
+ print(o.formatted, end='', flush=True)
51
+ print()
52
+ return o
53
+ ```
54
+
55
+ ``` python
56
+ mtok = 1024
57
+ ```
58
+
59
+ ## Chat across providers
60
+
61
+ Use the same `acomplete` interface with Claude, GPT, Gemini, or Kimi. Set `model` and any provider-specific routing arguments:
62
+
63
+ ``` python
64
+ models = [
65
+ ('claude-sonnet-5', {}),
66
+ ('gpt-4o-mini', {}),
67
+ ('models/gemini-3-flash-preview', {}),
68
+ ('accounts/fireworks/models/kimi-k3', dict(vendor_name='fireworks_ai'))
69
+ ]
70
+ for name, kw in models:
71
+ r = await acomplete([user("Translate 'hello' into French. Return only the translation.")],
72
+ model=name, max_tokens=mtok, **kw)
73
+ text = ''.join(p.text for p in r.message.content if isinstance(p, Text))
74
+ print(f"{name}: {text.strip()}")
75
+ ```
76
+
77
+ claude-sonnet-5: Bonjour
78
+ gpt-4o-mini: Bonjour
79
+ models/gemini-3-flash-preview: Bonjour
80
+ accounts/fireworks/models/kimi-k3: Bonjour
81
+
82
+ ## System prompts
83
+
84
+ Pass `system` to supply a system prompt. `fastllm` translates it to the provider’s field: Anthropic `system`, OpenAI Responses `instructions`, or Gemini `system_instruction`.
85
+
86
+ ``` python
87
+ sys = "You are a pirate chef. Always respond in pirate speak and mention food. Use one short sentence."
88
+
89
+ print("Claude: ", end='')
90
+ r = await stream([user("What should I do today?")], model='claude-sonnet-5', system=sys, max_tokens=mtok)
91
+
92
+ print("Gemini: ", end='')
93
+ r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
94
+ ```
95
+
96
+ Claude: Arrr, chart a course fer the galley and cook up a hearty stew, matey!
97
+ Gemini: Sharpen yer cutlass and feast on spicy shark stew, ye scurvy dog!
98
+
99
+ ## Tool calling
100
+
101
+ Define tools in the shared schema. `fastllm` translates it to the provider’s tool format. This example handles a tool request, adds the result to the history, and continues the conversation:
102
+
103
+ ``` python
104
+ tools = [{"type": "function", "function": {
105
+ "name": "get_weather",
106
+ "description": "Get current weather for a city",
107
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
108
+ }}]
109
+
110
+ msgs = [user("What's the weather in Paris?")]
111
+ r1 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
112
+ print("Tool calls:", r1.tool_calls)
113
+ ```
114
+
115
+ - ⏳ `get_weather(city="Paris")` ⏳
116
+
117
+ Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01XacBpJHnsjtPWJWr55nA2m', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
118
+
119
+ ``` python
120
+ msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds']),
121
+ user("Should I bring a jacket? Answer in one short sentence.")]
122
+ r2 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
123
+ ```
124
+
125
+ No, a jacket isn't necessary — it's a mild, sunny 22°C day.
126
+
127
+ ## Tool Choice
128
+
129
+ Control whether the model must use tools, can’t use tools, or decides on its own:
130
+
131
+ ``` python
132
+ r = await acomplete([user("Hello there!")], model='claude-sonnet-5',
133
+ tools=tools, tool_choice='required', max_tokens=mtok)
134
+ print("Required:", [tc.name for tc in r.tool_calls])
135
+
136
+ r = await acomplete([user("What's the weather?")], model='claude-sonnet-5',
137
+ tools=tools, tool_choice='none', max_tokens=mtok)
138
+ print("None:", r.tool_calls)
139
+ ```
140
+
141
+ Required: ['get_weather']
142
+ None: []
143
+
144
+ ## Thinking and extended reasoning
145
+
146
+ Set `reasoning_effort` to `low`, `medium`, or `high` for supported models. The adapters translate these values to provider-specific settings. Reasoning output uses `Thinking` parts; this example prints a short excerpt alongside the answer.
147
+
148
+ ``` python
149
+ print("Claude: ", end='')
150
+ r = await stream([user("What is 127 × 849? Return just the number.")], model='claude-sonnet-4-6',
151
+ reasoning_effort='low', max_tokens=8192)
152
+ for p in r.message.content:
153
+ if isinstance(p, Thinking): print(f"Thinking excerpt: {p.text[:150]}...")
154
+
155
+ print("Kimi: ", end='')
156
+ r = await stream([user("What is 127 × 849? Return just the number.")],
157
+ model='accounts/fireworks/models/kimi-k3', vendor_name='fireworks_ai',
158
+ reasoning_effort='low', max_tokens=8192)
159
+ for p in r.message.content:
160
+ if isinstance(p, Thinking): print(f"Thinking excerpt: {p.text[:150]}...")
161
+ ```
162
+
163
+ Claude: 107823
164
+ Thinking excerpt: 127 × 849 = 127 × 800 + 127 × 49 = 101600 + 6223 = 107823...
165
+ Kimi: 107823
166
+ Thinking excerpt: 127 × 849 = 107823. Let me verify: 127*800=101600; 127*49=6223; total 107823. Return just number....
167
+
168
+ ## Web Search (Server Tools)
169
+
170
+ OpenAI’s Responses API supports server-side web search. Server tool calls are normalized alongside regular tool calls:
171
+
172
+ ``` python
173
+ ws_tools = [{"type": "web_search_preview"}]
174
+ r = await acomplete([user("What is the latest Python release? Reply with the version number only.")],
175
+ model='gpt-4o-mini', tools=ws_tools, max_tokens=512)
176
+ print("Server tools used:", [tc.name for tc in r.tool_calls if tc.server])
177
+ ```
178
+
179
+ Server tools used: ['web_search']
180
+
181
+ ## Caching with Anthropic
182
+
183
+ Set a part’s `cache_control` to request prompt caching. Later requests can reuse eligible cached content. The example prints usage for two calls with the same system prompt:
184
+
185
+ ``` python
186
+ long_ctx = "You are an expert on the solar system. " * 200
187
+ system = Text(long_ctx, cache_control={'type': 'ephemeral'})
188
+
189
+ r1 = await acomplete([user("What is Jupiter's mass? Answer in one sentence.")],
190
+ model='claude-sonnet-5', system=system, max_tokens=mtok)
191
+ print("First call: cache creation tokens", r1.usage.cache_creation_tokens)
192
+
193
+ r2 = await acomplete([user("What is Saturn's mass? Answer in one sentence.")],
194
+ model='claude-sonnet-5', system=system, max_tokens=mtok)
195
+ print("Second call: cache read tokens", r2.usage.cached_tokens)
196
+ ```
197
+
198
+ First call: cache creation tokens 2204
199
+ Second call: cache read tokens 2204
200
+
201
+ ## Media inputs
202
+
203
+ Use `InputImage` with a model that supports images. The example sends the same image message to three providers:
204
+
205
+ ``` python
206
+ img_msg = Msg(role='user', content=[
207
+ InputImage(sample_img_url),
208
+ Text("List three visible objects, using nouns only.")
209
+ ])
210
+
211
+ for name, kw in [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
212
+ print(f"{name}: ", end='')
213
+ r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
214
+ ```
215
+
216
+ claude-sonnet-5: Mountains, lake, trees
217
+ gpt-4o-mini: 1. Mountains
218
+ 2. Trees
219
+ 3. Lake
220
+ models/gemini-3-flash-preview: planks, lake, mountains
221
+
222
+ The media adapters support the following part types:
223
+
224
+ | Media part | Anthropic | OpenAI Responses | OpenAI Chat | Gemini |
225
+ |--------------|-----------|------------------|-------------|--------|
226
+ | `InputImage` | Yes | Yes | Yes | Yes |
227
+ | `InputAudio` | No | No | Yes | Yes |
228
+ | `InputVideo` | No | No | No | Yes |
229
+ | `InputFile` | Yes | Yes | Yes | Yes |
230
+
231
+ Media parts accept a URL or base64 data URL as their `text`. An unsupported combination raises `ValueError`.
@@ -1,18 +1,18 @@
1
- fastllm/__init__.py,sha256=vhdqOGEnVs5mjJWas3MsI3ONbX5hoUcm60bejnXWuHU,131
1
+ fastllm/__init__.py,sha256=6tGHvtWA9S06PKl5eFmjR7B9sGVZhtZsXqaK_d39b7k,131
2
2
  fastllm/_modidx.py,sha256=qzUtLbjOche-GNbeNun3VRIQdcm5g4dUOCLJ6T9fG-w,46374
3
- fastllm/acomplete.py,sha256=64-k3LgLoX1EoWcHBS_-epZ-JvX5JL-As9QTo-WSzDc,10772
3
+ fastllm/acomplete.py,sha256=CusLue35xiaswpmTZyLkJj6t4CVsNDeeY3Sk7hzVNoY,10777
4
4
  fastllm/anthropic.py,sha256=ktH-EuQMQnp8O2_6VmSrvvDCIe2QTeSR7jNpWfqf3L4,18884
5
5
  fastllm/chat.py,sha256=klji3SOYs5xyzrSFbNhy3uoQ2TSZ3a-8HTjvdRtaG5Q,25036
6
6
  fastllm/codex.py,sha256=r6auZ8egia83aGkWcF4132AzW4SsBvKeDwg_CxyXj9E,5808
7
7
  fastllm/gemini.py,sha256=vpY_f4Jqn344l9CANm6_c7Cmhjq2URXxWhdVv_LswSg,17615
8
8
  fastllm/model_prices.json,sha256=pa0j96LZgklYii8howX443QbukUP1iFr3lBcucS-6Yo,2133018
9
9
  fastllm/openai_chat.py,sha256=n5INfDCsAZIiKUImWeaX6SJ_OAJ_uMhw20HUkgFgdgM,12379
10
- fastllm/openai_responses.py,sha256=NrvzNCKkSQabbAMnxYCSPdROaT1u8Vp2eoRzbzAA-4M,15345
10
+ fastllm/openai_responses.py,sha256=Jbu8ddPC3rqlLgubOuOva4EWEtN6dk47MbwBfB8d9ng,15409
11
11
  fastllm/responses.py,sha256=9gh-UlhfswCcP0RCzLuPT5meWNh9mF_OIivzCzZU94I,22726
12
12
  fastllm/streaming.py,sha256=DrqNJkMhyntFKokY6GF6oW3CjELrk5XWmnxwcApJUyM,8770
13
- fastllm/types.py,sha256=OznlKQZUx2DrLE_O-sbIBsT6q6EBeVDlH9kXxhsGBSE,19445
14
- python_fastllm-0.0.57.dist-info/METADATA,sha256=rytO5QwiLRc5bqLNBnQkymhqkC-MrrwTRM6zJ2VkUaI,18537
15
- python_fastllm-0.0.57.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
16
- python_fastllm-0.0.57.dist-info/entry_points.txt,sha256=WkWd04F3xVYFP5bsexaQe9Se6uelwE5ULoTtHWreO0s,133
17
- python_fastllm-0.0.57.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
18
- python_fastllm-0.0.57.dist-info/RECORD,,
13
+ fastllm/types.py,sha256=W8t38EN1ZAzZaZ8QMi-Vin3fMxEpRhEZ8pm6fM5G3NA,19673
14
+ python_fastllm-0.0.59.dist-info/METADATA,sha256=fCzlAUhirh1MHNym6IjGbVAlG0ouKo0EAP9n5DVF1m0,9136
15
+ python_fastllm-0.0.59.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
16
+ python_fastllm-0.0.59.dist-info/entry_points.txt,sha256=WkWd04F3xVYFP5bsexaQe9Se6uelwE5ULoTtHWreO0s,133
17
+ python_fastllm-0.0.59.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
18
+ python_fastllm-0.0.59.dist-info/RECORD,,
@@ -1,373 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-fastllm
3
- Version: 0.0.57
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.27
14
- Requires-Dist: fasttransport>=0.0.2
15
- Requires-Dist: pillow
16
-
17
- # fastllm
18
-
19
-
20
- <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
21
-
22
- ## Install
23
-
24
- Run `pip install python-fastllm` or clone from github and install locally.
25
-
26
- ## Setup
27
-
28
- ``` python
29
- from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
30
- from fastllm.acomplete import acomplete
31
- from fastllm.types import sample_img_url, sample_doms
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-5', {}),
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
- ## System Prompts
85
-
86
- Pass a system prompt to any provider — `fastllm` maps it to each API’s native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini `system_instruction`):
87
-
88
- ``` python
89
- sys = "You are a pirate chef. Always respond in pirate speak and mention food."
90
-
91
- print("Claude: ", end='')
92
- r = await stream([user("What should I do today?")], model='claude-sonnet-5', system=sys, max_tokens=mtok)
93
-
94
- print("Gemini: ", end='')
95
- r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
96
- ```
97
-
98
- Claude: Ahoy there, me hearty! *tips chef's hat with a feathered plume*
99
-
100
- Ye be askin' what to do on this fine day, eh? Well, shiver me spatulas, I've got some tasty suggestions for ye!
101
-
102
- 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!
103
-
104
- 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!
105
-
106
- 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!
107
-
108
- 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!
109
-
110
- *waves wooden spoon like a cutlass*
111
-
112
- Arrr, what say ye? Does any of this grub sound temptin' to yer taste buds?
113
- 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.
114
-
115
- 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.
116
-
117
- 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!
118
-
119
- 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**.
120
-
121
- 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!
122
-
123
- ## Tool Calling — Define Once, Use Anywhere
124
-
125
- Define tools in a single canonical format. `fastllm` translates to each provider’s native tool schema automatically. Here Claude requests the tool, receives the result, and answers:
126
-
127
- ``` python
128
- tools = [{"type": "function", "function": {
129
- "name": "get_weather",
130
- "description": "Get current weather for a city",
131
- "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
132
- }}]
133
-
134
- # Turn 1: Claude requests the tool
135
- msgs = [user("What's the weather in Paris?")]
136
- r1 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
137
- print("Tool calls:", r1.tool_calls)
138
- ```
139
-
140
- I'll check the current weather in Paris for you.
141
- - ⏳ `get_weather(city="Paris")` ⏳
142
-
143
- Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
144
-
145
- ``` python
146
- # Provide the tool result, then continue the same conversation
147
- msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds']), user("Should I bring a jacket?")]
148
- print("Claude: ", end='')
149
- r2 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
150
- ```
151
-
152
- 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.
153
-
154
- ## Tool Choice
155
-
156
- Control whether the model must use tools, can’t use tools, or decides on its own:
157
-
158
- ``` python
159
- # Force the model to call a tool (even for a greeting)
160
- r = await stream([user("Hello there!")], model='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
161
- print("Forced:", r.tool_calls)
162
-
163
- # Prevent tool use (model must answer directly)
164
- print("\nNo tools: ", end='')
165
- r = await stream([user("What's the weather?")], model='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
166
- ```
167
-
168
- - ⏳ `get_weather(city="<UNKNOWN>")` ⏳
169
-
170
- Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
171
-
172
- 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?
173
-
174
- ## Thinking / Extended Reasoning
175
-
176
- Enable model reasoning with `reasoning_effort`. The canonical values (`low`, `medium`, `high`) map to each provider’s native budget system. Thinking tokens stream as 🧠:
177
-
178
- ``` python
179
- # Claude with thinking
180
- print("Claude: ", end='')
181
- r = await stream([user("What is 127 × 849?")], model='claude-sonnet-4-6', reasoning_effort='low', max_tokens=8192)
182
- for p in r.message.content:
183
- if isinstance(p, Thinking): print(f"\n🧠 {p.text[:150]}...")
184
-
185
- # Kimi with thinking — same interface
186
- print("\nKimi: ", end='')
187
- r = await stream([user("What is 127 × 849?")], model='accounts/fireworks/models/kimi-k2p5', vendor_name='fireworks_ai', reasoning_effort='low', max_tokens=8192)
188
- for p in r.message.content:
189
- if isinstance(p, Thinking): print(f"\n🧠 {p.text[:150]}...")
190
- ```
191
-
192
- Claude: 🧠🧠🧠## Calculating 127 × 849
193
-
194
- **Breaking it down:**
195
- - 127 × 800 = 101,600
196
- - 127 × 40 = 5,080
197
- - 127 × 9 = 1,143
198
-
199
- **Adding the parts:**
200
- 101,600 + 5,080 + 1,143 = **107,823**
201
-
202
- 🧠 127 × 849:
203
- 127 × 800 = 101,600
204
- 127 × 49 = 127 × 50 - 127 = 6350 - 127 = 6223
205
- Total: 107,823...
206
-
207
- Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠**107,823**
208
-
209
- Here's the calculation:
210
- - 127 × 800 = 101,600
211
- - 127 × 40 = 5,080
212
- - 127 × 9 = 1,143
213
- - **Total: 101,600 + 5,080 + 1,143 = 107,823**
214
-
215
- 🧠 The user is asking for the product of 127 and 849. I need to calculate 127 × 849.
216
-
217
- Let me calculate this step by step.
218
-
219
- Method 1: Standard multiplicat...
220
-
221
- ## Web Search (Server Tools)
222
-
223
- OpenAI’s Responses API supports server-side web search. Server tool calls are normalized alongside regular tool calls:
224
-
225
- ``` python
226
- ws_tools = [{"type": "web_search_preview"}]
227
- print("GPT + web search: ", end='')
228
- r = await stream([user("What is the latest Python release?")], model='gpt-4o-mini', tools=ws_tools, max_tokens=512)
229
- print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
230
- ```
231
-
232
- GPT + web search:
233
-
234
- ```json {.tool}
235
- {
236
- "id": "ws_0f48f4b49d3a4937006a2c04cb944481a3882056257ae43956",
237
- "name": "web_search",
238
- "args": {
239
- "type": "search",
240
- "queries": [
241
- "latest Python release October 2023"
242
- ],
243
- "query": "latest Python release October 2023"
244
- },
245
- "result": "Server tool call executed.",
246
- "server": true
247
- }
248
- ```
249
-
250
- 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))
251
-
252
- Python 3.14 introduced several significant features, including:
253
-
254
- - **PEP 779**: Official support for free-threaded Python, allowing threads to run more concurrently.
255
- - **PEP 649**: Deferred evaluation of annotations, improving the semantics of using annotations.
256
- - **PEP 750**: Template string literals (t-strings) for custom string processing, using the familiar syntax of f-strings.
257
- - **PEP 734**: Support for multiple interpreters in the standard library.
258
- - **PEP 784**: A new module `compression.zstd` providing support for the Zstandard compression algorithm.
259
-
260
- 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))
261
-
262
- 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))
263
-
264
- Server tools used: ['web_search']
265
-
266
- ## Caching (Anthropic)
267
-
268
- Anthropic supports prompt caching via `cache_control`. Set it on a part’s `cache_control` — repeat calls with the same cached content save tokens:
269
-
270
- ``` python
271
- # Cache a long system prompt (must be >1024 tokens for Anthropic caching)
272
- long_ctx = "You are an expert on the solar system. " * 200
273
- system = Text(long_ctx, cache_control={'type': 'ephemeral'})
274
-
275
- print("Call 1: ", end='')
276
- r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
277
- print(f"Usage: {r1.usage}")
278
-
279
- print("Call 2: ", end='')
280
- r2 = await stream([user("How about Saturn?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
281
- print(f"Usage: {r2.usage}")
282
- ```
283
-
284
- Call 1: Jupiter's mass is approximately 1.898 × 10²⁷ kilograms (or 1,898,000,000,000,000,000,000,000,000 kg).
285
-
286
- To put this in perspective:
287
- - Jupiter is about 318 times more massive than Earth
288
- - It contains more than twice the mass of all other planets in our solar system combined
289
- - Jupiter's mass is about 1/1047th the mass of the Sun
290
-
291
- 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.
292
- 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})
293
- Call 2: Saturn is truly one of the most spectacular planets in our solar system! Here are some key facts about this gas giant:
294
-
295
- **Basic Characteristics:**
296
- - Sixth planet from the Sun
297
- - Second largest planet (after Jupiter)
298
- - Composed primarily of hydrogen and helium
299
- - Has the lowest density of any planet - it would actually float in water!
300
-
301
- **Famous Ring System:**
302
- - Most prominent and extensive ring system in the solar system
303
- - Made primarily of ice particles and rocky debris
304
- - Rings are organized into distinct sections (A, B, C rings are the main ones)
305
- - Ring particles range from tiny ice crystals to house-sized chunks
306
-
307
- **Moons:**
308
- - Has 146 confirmed moons (the most of any planet)
309
- - Titan is its largest moon - larger than Mercury and has a thick atmosphere
310
- - Enceladus has geysers of water ice erupting from its south pole
311
- - Many other fascinating moons like Iapetus, Mimas, and Dione
312
-
313
- **Physical Features:**
314
- - Takes about 29.5 Earth years to orbit the Sun
315
- - A day on Saturn is only about 10.7 hours
316
- - Has extreme winds reaching up to 1,800 km/h
317
- - Beautiful hexagonal storm at its north pole
318
-
319
- **Exploration:**
320
- - Visited by Pioneer 11, Voyager 1 & 2, and most notably the Cassini mission (2004-2017)
321
-
322
- Is there a particular aspect of Saturn you'd like to know more about?
323
- 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})
324
-
325
- ## Media Inputs
326
-
327
- Send images to any provider that supports them. The canonical `input_image` part works everywhere:
328
-
329
- ``` python
330
- img_msg = Msg(role='user', content=[
331
- InputImage(sample_img_url),
332
- Text("What do you see in this image?")
333
- ])
334
-
335
- for name, kw in [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
336
- print(f"{name:>30s}: ", end='')
337
- r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
338
- ```
339
-
340
- 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:
341
-
342
- - A perfectly still lake that creates mirror-like reflections
343
- - Snow-capped mountains in the background under a partly cloudy sky
344
- - Dense forests of evergreen trees (likely pine or fir) lining the shoreline
345
- - The mountains, trees, and sky all reflected clearly in the calm water
346
- - Wooden planks in the foreground, suggesting the photo was taken from a dock or wooden viewing platform
347
- - The lighting appears soft and atmospheric, possibly during golden hour
348
- - The overall scene has a very peaceful, pristine wilderness quality typical of places like the Canadian Rockies, Alaska, or similar mountainous lake regions
349
-
350
- 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.
351
- 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.
352
- models/gemini-3-flash-preview: This image depicts a serene and majestic landscape, viewed from the perspective of someone standing on a wooden deck.
353
-
354
- Here is a breakdown of what is visible:
355
-
356
- * **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.
357
- * **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.
358
- * **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.
359
- * **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.
360
- * **Sky:** The sky is a pale, bright blue, almost white in some areas, with a few soft clouds visible in the upper right corner.
361
-
362
- The overall mood of the image is peaceful, cool-toned, and tranquil.
363
-
364
- `fastllm` supports four media part classes. Provider support varies:
365
-
366
- | Media part | Anthropic | OpenAI Responses | OpenAI Chat | Gemini |
367
- |--------------|-----------|------------------|-------------|--------|
368
- | `InputImage` | ✅ | ✅ | ✅ | ✅ |
369
- | `InputAudio` | ❌ | ❌ (coming soon) | ✅ | ✅ |
370
- | `InputVideo` | ❌ | ❌ | ❌ | ✅ |
371
- | `InputFile` | ✅ | ✅ | ✅ | ✅ |
372
-
373
- All media parts accept either a URL or a base64 data URL as their `text`. Unsupported combinations raise a clear `ValueError`.