python-fastllm 0.0.50__tar.gz → 0.0.51__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.
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/PKG-INFO +16 -72
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/README.md +14 -70
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/__init__.py +1 -1
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/_modidx.py +6 -1
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/anthropic.py +17 -7
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/responses.py +19 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/types.py +9 -8
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/pyproject.toml +1 -1
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/PKG-INFO +16 -72
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/requires.txt +1 -1
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/acomplete.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/chat.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/gemini.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/openai_chat.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/openai_responses.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/fastllm/streaming.py +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/SOURCES.txt +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.50 → python_fastllm-0.0.51}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fastllm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.51
|
|
4
4
|
Author-email: Kerem Turgutlu <keremturgutlu@gmail.com>
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/AnswerDotAI/fastllm
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
10
10
|
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: fastcore>=2.1.18
|
|
13
|
-
Requires-Dist: aidialog>=0.0.
|
|
13
|
+
Requires-Dist: aidialog>=0.0.25
|
|
14
14
|
Requires-Dist: fasttransport>=0.0.2
|
|
15
15
|
Requires-Dist: pillow
|
|
16
16
|
|
|
@@ -28,7 +28,7 @@ Run `pip install python-fastllm` or clone from github and install locally.
|
|
|
28
28
|
``` python
|
|
29
29
|
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
30
30
|
from fastllm.acomplete import acomplete
|
|
31
|
-
from fastllm.types import sample_img_url
|
|
31
|
+
from fastllm.types import sample_img_url, sample_doms
|
|
32
32
|
import asyncio, json
|
|
33
33
|
|
|
34
34
|
# Helpers
|
|
@@ -57,7 +57,7 @@ The same `acomplete` call works with Claude, GPT, Gemini, and Kimi. Just change
|
|
|
57
57
|
|
|
58
58
|
``` python
|
|
59
59
|
models = [
|
|
60
|
-
('claude-sonnet-
|
|
60
|
+
('claude-sonnet-5', {}),
|
|
61
61
|
('gpt-4o-mini', {}),
|
|
62
62
|
('models/gemini-3-flash-preview', {}),
|
|
63
63
|
('accounts/fireworks/models/kimi-k2p5', dict(vendor_name='fireworks_ai'))
|
|
@@ -81,46 +81,6 @@ for name, kw in models:
|
|
|
81
81
|
|
|
82
82
|
The response should be simple and direct.
|
|
83
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
84
|
## System Prompts
|
|
125
85
|
|
|
126
86
|
Pass a system prompt to any provider — `fastllm` maps it to each API’s native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini `system_instruction`):
|
|
@@ -129,7 +89,7 @@ Pass a system prompt to any provider — `fastllm` maps it to each API’s nativ
|
|
|
129
89
|
sys = "You are a pirate chef. Always respond in pirate speak and mention food."
|
|
130
90
|
|
|
131
91
|
print("Claude: ", end='')
|
|
132
|
-
r = await stream([user("What should I do today?")], model='claude-sonnet-
|
|
92
|
+
r = await stream([user("What should I do today?")], model='claude-sonnet-5', system=sys, max_tokens=mtok)
|
|
133
93
|
|
|
134
94
|
print("Gemini: ", end='')
|
|
135
95
|
r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
|
|
@@ -162,7 +122,7 @@ r = await stream([user("What should I do today?")], model='models/gemini-3-flash
|
|
|
162
122
|
|
|
163
123
|
## Tool Calling — Define Once, Use Anywhere
|
|
164
124
|
|
|
165
|
-
Define tools in a single canonical format. `fastllm` translates to each provider’s native tool schema automatically. Here
|
|
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:
|
|
166
126
|
|
|
167
127
|
``` python
|
|
168
128
|
tools = [{"type": "function", "function": {
|
|
@@ -173,7 +133,7 @@ tools = [{"type": "function", "function": {
|
|
|
173
133
|
|
|
174
134
|
# Turn 1: Claude requests the tool
|
|
175
135
|
msgs = [user("What's the weather in Paris?")]
|
|
176
|
-
r1 = await stream(msgs, model='claude-sonnet-
|
|
136
|
+
r1 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
177
137
|
print("Tool calls:", r1.tool_calls)
|
|
178
138
|
```
|
|
179
139
|
|
|
@@ -183,44 +143,28 @@ print("Tool calls:", r1.tool_calls)
|
|
|
183
143
|
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
184
144
|
|
|
185
145
|
``` python
|
|
186
|
-
# Provide the tool result
|
|
187
|
-
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds'])]
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
msgs.append(user("Should I bring a jacket?"))
|
|
191
|
-
print("GPT: ", end='')
|
|
192
|
-
r2 = await stream(msgs, model='gpt-4o-mini', tools=tools, max_tokens=mtok)
|
|
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)
|
|
193
150
|
```
|
|
194
151
|
|
|
195
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.
|
|
196
153
|
|
|
197
|
-
``` python
|
|
198
|
-
# Turn 3: Gemini sees the full cross-provider tool history
|
|
199
|
-
msgs += [r2.message, user("How about tomorrow — will it rain?")]
|
|
200
|
-
r3 = await stream(msgs, model='models/gemini-3-flash-preview', tools=tools, max_tokens=mtok, web_search_options={})
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Tomorrow in Paris (Saturday, June 13), the weather is expected to be mostly pleasant and sunny.
|
|
204
|
-
|
|
205
|
-
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.
|
|
206
|
-
|
|
207
|
-
So, while you might see a stray shower, you likely won't need to worry about heavy rain!
|
|
208
|
-
|
|
209
154
|
## Tool Choice
|
|
210
155
|
|
|
211
156
|
Control whether the model must use tools, can’t use tools, or decides on its own:
|
|
212
157
|
|
|
213
158
|
``` python
|
|
214
159
|
# Force the model to call a tool (even for a greeting)
|
|
215
|
-
r = await stream([user("Hello there!")], model='claude-sonnet-
|
|
160
|
+
r = await stream([user("Hello there!")], model='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
|
|
216
161
|
print("Forced:", r.tool_calls)
|
|
217
162
|
|
|
218
163
|
# Prevent tool use (model must answer directly)
|
|
219
164
|
print("\nNo tools: ", end='')
|
|
220
|
-
r = await stream([user("What's the weather?")], model='claude-sonnet-
|
|
165
|
+
r = await stream([user("What's the weather?")], model='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
221
166
|
```
|
|
222
167
|
|
|
223
|
-
|
|
224
168
|
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
225
169
|
|
|
226
170
|
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
@@ -329,11 +273,11 @@ long_ctx = "You are an expert on the solar system. " * 200
|
|
|
329
273
|
system = Text(long_ctx, cache_control={'type': 'ephemeral'})
|
|
330
274
|
|
|
331
275
|
print("Call 1: ", end='')
|
|
332
|
-
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-
|
|
276
|
+
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
333
277
|
print(f"Usage: {r1.usage}")
|
|
334
278
|
|
|
335
279
|
print("Call 2: ", end='')
|
|
336
|
-
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-
|
|
280
|
+
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
337
281
|
print(f"Usage: {r2.usage}")
|
|
338
282
|
```
|
|
339
283
|
|
|
@@ -388,7 +332,7 @@ img_msg = Msg(role='user', content=[
|
|
|
388
332
|
Text("What do you see in this image?")
|
|
389
333
|
])
|
|
390
334
|
|
|
391
|
-
for name, kw in [('claude-sonnet-
|
|
335
|
+
for name, kw in [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
|
|
392
336
|
print(f"{name:>30s}: ", end='')
|
|
393
337
|
r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
|
|
394
338
|
```
|
|
@@ -12,7 +12,7 @@ Run `pip install python-fastllm` or clone from github and install locally.
|
|
|
12
12
|
``` python
|
|
13
13
|
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
14
14
|
from fastllm.acomplete import acomplete
|
|
15
|
-
from fastllm.types import sample_img_url
|
|
15
|
+
from fastllm.types import sample_img_url, sample_doms
|
|
16
16
|
import asyncio, json
|
|
17
17
|
|
|
18
18
|
# Helpers
|
|
@@ -41,7 +41,7 @@ The same `acomplete` call works with Claude, GPT, Gemini, and Kimi. Just change
|
|
|
41
41
|
|
|
42
42
|
``` python
|
|
43
43
|
models = [
|
|
44
|
-
('claude-sonnet-
|
|
44
|
+
('claude-sonnet-5', {}),
|
|
45
45
|
('gpt-4o-mini', {}),
|
|
46
46
|
('models/gemini-3-flash-preview', {}),
|
|
47
47
|
('accounts/fireworks/models/kimi-k2p5', dict(vendor_name='fireworks_ai'))
|
|
@@ -65,46 +65,6 @@ for name, kw in models:
|
|
|
65
65
|
|
|
66
66
|
The response should be simple and direct.
|
|
67
67
|
|
|
68
|
-
## Multi-Turn — Swap Providers Mid-Conversation
|
|
69
|
-
|
|
70
|
-
Build a conversation with one provider, then seamlessly continue it with another. `fastllm` translates between every provider’s native format automatically:
|
|
71
|
-
|
|
72
|
-
``` python
|
|
73
|
-
# Turn 1: Claude starts the conversation
|
|
74
|
-
msgs = [user("Name the 3 largest planets in our solar system. One sentence.")]
|
|
75
|
-
print("Claude: ", end='')
|
|
76
|
-
r1 = await stream(msgs, model='claude-sonnet-4-20250514', max_tokens=mtok)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Claude: The three largest planets in our solar system are Jupiter, Saturn, and Neptune.
|
|
80
|
-
|
|
81
|
-
``` python
|
|
82
|
-
# Turn 2: Switch to GPT — just change the model string
|
|
83
|
-
msgs += [r1.message, user("Which one has the most moons?")]
|
|
84
|
-
print("GPT: ", end='')
|
|
85
|
-
r2 = await stream(msgs, model='gpt-4o-mini', max_tokens=mtok)
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
GPT: As of now, Saturn has the most moons, with over 80 confirmed moons.
|
|
89
|
-
|
|
90
|
-
``` python
|
|
91
|
-
# Turn 3: Switch to Gemini — same msgs, different provider
|
|
92
|
-
msgs += [r2.message, user("Summarize our conversation in one sentence.")]
|
|
93
|
-
print("Gemini: ", end='')
|
|
94
|
-
r3 = await stream(msgs, model='models/gemini-3-flash-preview', max_tokens=mtok)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Gemini: The conversation identified the three largest planets in the solar system and noted that Saturn currently has the most moons.
|
|
98
|
-
|
|
99
|
-
``` python
|
|
100
|
-
# Turn 4: Switch to Kimi — works the same way
|
|
101
|
-
msgs += [r3.message, user("Thanks! What's one surprising fact about Saturn?")]
|
|
102
|
-
print("Kimi: ", end='')
|
|
103
|
-
r4 = await stream(msgs, model='accounts/fireworks/models/kimi-k2p5', vendor_name='fireworks_ai', max_tokens=mtok)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Kimi: 🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠Saturn is less dense than water, meaning it would theoretically float if you could find a bathtub large enough to hold it.
|
|
107
|
-
|
|
108
68
|
## System Prompts
|
|
109
69
|
|
|
110
70
|
Pass a system prompt to any provider — `fastllm` maps it to each API’s native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini `system_instruction`):
|
|
@@ -113,7 +73,7 @@ Pass a system prompt to any provider — `fastllm` maps it to each API’s nativ
|
|
|
113
73
|
sys = "You are a pirate chef. Always respond in pirate speak and mention food."
|
|
114
74
|
|
|
115
75
|
print("Claude: ", end='')
|
|
116
|
-
r = await stream([user("What should I do today?")], model='claude-sonnet-
|
|
76
|
+
r = await stream([user("What should I do today?")], model='claude-sonnet-5', system=sys, max_tokens=mtok)
|
|
117
77
|
|
|
118
78
|
print("Gemini: ", end='')
|
|
119
79
|
r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
|
|
@@ -146,7 +106,7 @@ r = await stream([user("What should I do today?")], model='models/gemini-3-flash
|
|
|
146
106
|
|
|
147
107
|
## Tool Calling — Define Once, Use Anywhere
|
|
148
108
|
|
|
149
|
-
Define tools in a single canonical format. `fastllm` translates to each provider’s native tool schema automatically. Here
|
|
109
|
+
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:
|
|
150
110
|
|
|
151
111
|
``` python
|
|
152
112
|
tools = [{"type": "function", "function": {
|
|
@@ -157,7 +117,7 @@ tools = [{"type": "function", "function": {
|
|
|
157
117
|
|
|
158
118
|
# Turn 1: Claude requests the tool
|
|
159
119
|
msgs = [user("What's the weather in Paris?")]
|
|
160
|
-
r1 = await stream(msgs, model='claude-sonnet-
|
|
120
|
+
r1 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
161
121
|
print("Tool calls:", r1.tool_calls)
|
|
162
122
|
```
|
|
163
123
|
|
|
@@ -167,44 +127,28 @@ print("Tool calls:", r1.tool_calls)
|
|
|
167
127
|
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
168
128
|
|
|
169
129
|
``` python
|
|
170
|
-
# Provide the tool result
|
|
171
|
-
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds'])]
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
msgs.append(user("Should I bring a jacket?"))
|
|
175
|
-
print("GPT: ", end='')
|
|
176
|
-
r2 = await stream(msgs, model='gpt-4o-mini', tools=tools, max_tokens=mtok)
|
|
130
|
+
# Provide the tool result, then continue the same conversation
|
|
131
|
+
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds']), user("Should I bring a jacket?")]
|
|
132
|
+
print("Claude: ", end='')
|
|
133
|
+
r2 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
177
134
|
```
|
|
178
135
|
|
|
179
136
|
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.
|
|
180
137
|
|
|
181
|
-
``` python
|
|
182
|
-
# Turn 3: Gemini sees the full cross-provider tool history
|
|
183
|
-
msgs += [r2.message, user("How about tomorrow — will it rain?")]
|
|
184
|
-
r3 = await stream(msgs, model='models/gemini-3-flash-preview', tools=tools, max_tokens=mtok, web_search_options={})
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Tomorrow in Paris (Saturday, June 13), the weather is expected to be mostly pleasant and sunny.
|
|
188
|
-
|
|
189
|
-
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.
|
|
190
|
-
|
|
191
|
-
So, while you might see a stray shower, you likely won't need to worry about heavy rain!
|
|
192
|
-
|
|
193
138
|
## Tool Choice
|
|
194
139
|
|
|
195
140
|
Control whether the model must use tools, can’t use tools, or decides on its own:
|
|
196
141
|
|
|
197
142
|
``` python
|
|
198
143
|
# Force the model to call a tool (even for a greeting)
|
|
199
|
-
r = await stream([user("Hello there!")], model='claude-sonnet-
|
|
144
|
+
r = await stream([user("Hello there!")], model='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
|
|
200
145
|
print("Forced:", r.tool_calls)
|
|
201
146
|
|
|
202
147
|
# Prevent tool use (model must answer directly)
|
|
203
148
|
print("\nNo tools: ", end='')
|
|
204
|
-
r = await stream([user("What's the weather?")], model='claude-sonnet-
|
|
149
|
+
r = await stream([user("What's the weather?")], model='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
205
150
|
```
|
|
206
151
|
|
|
207
|
-
|
|
208
152
|
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
209
153
|
|
|
210
154
|
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
@@ -313,11 +257,11 @@ long_ctx = "You are an expert on the solar system. " * 200
|
|
|
313
257
|
system = Text(long_ctx, cache_control={'type': 'ephemeral'})
|
|
314
258
|
|
|
315
259
|
print("Call 1: ", end='')
|
|
316
|
-
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-
|
|
260
|
+
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
317
261
|
print(f"Usage: {r1.usage}")
|
|
318
262
|
|
|
319
263
|
print("Call 2: ", end='')
|
|
320
|
-
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-
|
|
264
|
+
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
321
265
|
print(f"Usage: {r2.usage}")
|
|
322
266
|
```
|
|
323
267
|
|
|
@@ -372,7 +316,7 @@ img_msg = Msg(role='user', content=[
|
|
|
372
316
|
Text("What do you see in this image?")
|
|
373
317
|
])
|
|
374
318
|
|
|
375
|
-
for name, kw in [('claude-sonnet-
|
|
319
|
+
for name, kw in [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
|
|
376
320
|
print(f"{name:>30s}: ", end='')
|
|
377
321
|
r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
|
|
378
322
|
```
|
|
@@ -51,7 +51,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
51
51
|
'fastllm.anthropic.norm_tool_calls': ('anthropic.html#norm_tool_calls', 'fastllm/anthropic.py'),
|
|
52
52
|
'fastllm.anthropic.norm_tr_parts': ('anthropic.html#norm_tr_parts', 'fastllm/anthropic.py'),
|
|
53
53
|
'fastllm.anthropic.norm_usage': ('anthropic.html#norm_usage', 'fastllm/anthropic.py'),
|
|
54
|
-
'fastllm.anthropic.raw_msg': ('anthropic.html#raw_msg', 'fastllm/anthropic.py')
|
|
54
|
+
'fastllm.anthropic.raw_msg': ('anthropic.html#raw_msg', 'fastllm/anthropic.py'),
|
|
55
|
+
'fastllm.anthropic.search_hits': ('anthropic.html#search_hits', 'fastllm/anthropic.py')},
|
|
55
56
|
'fastllm.chat': { 'fastllm.chat.AsyncChat': ('chat.html#asyncchat', 'fastllm/chat.py'),
|
|
56
57
|
'fastllm.chat.AsyncChat.__call__': ('chat.html#asyncchat.__call__', 'fastllm/chat.py'),
|
|
57
58
|
'fastllm.chat.AsyncChat.__init__': ('chat.html#asyncchat.__init__', 'fastllm/chat.py'),
|
|
@@ -255,11 +256,15 @@ d = { 'settings': { 'branch': 'main',
|
|
|
255
256
|
'fastllm.openai_responses.raw_msg': ( 'oai_responses.html#raw_msg',
|
|
256
257
|
'fastllm/openai_responses.py')},
|
|
257
258
|
'fastllm.responses': { 'fastllm.responses.AsyncResponses': ('responses.html#asyncresponses', 'fastllm/responses.py'),
|
|
259
|
+
'fastllm.responses.AsyncResponses.__init__': ( 'responses.html#asyncresponses.__init__',
|
|
260
|
+
'fastllm/responses.py'),
|
|
258
261
|
'fastllm.responses.AsyncResponses.call': ('responses.html#asyncresponses.call', 'fastllm/responses.py'),
|
|
259
262
|
'fastllm.responses.AsyncResponses.events': ( 'responses.html#asyncresponses.events',
|
|
260
263
|
'fastllm/responses.py'),
|
|
261
264
|
'fastllm.responses.AsyncResponses.prepare': ( 'responses.html#asyncresponses.prepare',
|
|
262
265
|
'fastllm/responses.py'),
|
|
266
|
+
'fastllm.responses.AsyncResponses.serve': ( 'responses.html#asyncresponses.serve',
|
|
267
|
+
'fastllm/responses.py'),
|
|
263
268
|
'fastllm.responses.AsyncResponses.server_results': ( 'responses.html#asyncresponses.server_results',
|
|
264
269
|
'fastllm/responses.py'),
|
|
265
270
|
'fastllm.responses.AsyncResponses.server_turn': ( 'responses.html#asyncresponses.server_turn',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_anthropic.ipynb.
|
|
2
2
|
|
|
3
3
|
# %% auto #0
|
|
4
|
-
__all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'finalize_usage', 'norm_finish', '
|
|
5
|
-
'raw_msg', 'norm_tr_parts', 'norm_sse_event', 'delta_index_fn', 'collate_raw',
|
|
6
|
-
'denorm_tool_use', 'denorm_assistant', 'denorm_tool', 'denorm_msgs', 'denorm_tool_schs',
|
|
4
|
+
__all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'finalize_usage', 'norm_finish', 'search_hits',
|
|
5
|
+
'norm_parts', 'raw_msg', 'norm_tr_parts', 'norm_sse_event', 'delta_index_fn', 'collate_raw',
|
|
6
|
+
'acollect_stream', 'denorm_tool_use', 'denorm_assistant', 'denorm_tool', 'denorm_msgs', 'denorm_tool_schs',
|
|
7
7
|
'denorm_tool_choice', 'denorm_reasoning', 'denorm_web_search', 'denorm_system', 'denorm_user',
|
|
8
8
|
'denorm_image', 'denorm_file', 'denorm_tool_result', 'apply_cache_idxs', 'mk_payload', 'get_hdrs', 'cost']
|
|
9
9
|
|
|
@@ -15,7 +15,7 @@ from fastcore.meta import *
|
|
|
15
15
|
from fasttransport.errors import api_error_from_event
|
|
16
16
|
|
|
17
17
|
from .types import *
|
|
18
|
-
from aidialog.msg_parts import (Part, PartType, mk_part, Msg, Text, Thinking, ToolUse, ToolResult, tool_text,
|
|
18
|
+
from aidialog.msg_parts import (Part, PartType, mk_part, Msg, Completion, Text, Thinking, ToolUse, ToolResult, tool_text,
|
|
19
19
|
InputImage, InputAudio, InputVideo, InputFile, data_url)
|
|
20
20
|
from .streaming import *
|
|
21
21
|
from .streaming import mk_acollect_stream
|
|
@@ -69,8 +69,16 @@ def _ant_part_type(typ):
|
|
|
69
69
|
ant_parts_mapping = dict(text=PartType.text, thinking=PartType.thinking, redacted_thinking=PartType.thinking, tool_use=PartType.tool_use, server_tool_use=PartType.tool_use, mcp_tool_use=PartType.tool_use)
|
|
70
70
|
return ant_parts_mapping.get(typ, typ)
|
|
71
71
|
|
|
72
|
+
def search_hits(parts, blocks):
|
|
73
|
+
"Set each server call's `text` to the `title: url` lines of its result block, when the response has one"
|
|
74
|
+
calls = {p.id: p for p in parts if isinstance(p, ToolUse)}
|
|
75
|
+
for b in blocks:
|
|
76
|
+
if b.get('type', '').endswith('_tool_result') and (tc := calls.get(b.get('tool_use_id'))):
|
|
77
|
+
tc.text = '\n'.join(f"{r.get('title', '')}: {r.get('url', '')}" for r in b.get('content') or [] if isinstance(r, dict) and r.get('url'))
|
|
78
|
+
return parts
|
|
79
|
+
|
|
72
80
|
def norm_parts(resp):
|
|
73
|
-
"Canonical parts of a response; a server tool's result block lives
|
|
81
|
+
"Canonical parts of a response; a server tool's result block lives in the raw message, and as the call's `text`"
|
|
74
82
|
parts = []
|
|
75
83
|
for b in resp.get("content", []):
|
|
76
84
|
typ = _ant_part_type(b.get("type", "text"))
|
|
@@ -78,7 +86,7 @@ def norm_parts(resp):
|
|
|
78
86
|
elif typ == PartType.tool_use:
|
|
79
87
|
if tc := norm_tool_call(b): parts.append(tc)
|
|
80
88
|
elif typ == PartType.text: parts.append(Text(b.get("text", ""), raw=b))
|
|
81
|
-
return parts
|
|
89
|
+
return search_hits(parts, resp.get("content", []))
|
|
82
90
|
|
|
83
91
|
# %% ../nbs/04_anthropic.ipynb #f24e52db
|
|
84
92
|
def raw_msg(resp):
|
|
@@ -150,7 +158,9 @@ def collate_raw(deltas):
|
|
|
150
158
|
@delegates(mk_acollect_stream, but=['index_fn', 'api_name'])
|
|
151
159
|
async def acollect_stream(resp, **kwargs):
|
|
152
160
|
res = mk_acollect_stream(norm_and_yield(resp, norm_sse_event), index_fn=delta_index_fn, api_name='anthropic', **kwargs)
|
|
153
|
-
async for o in res:
|
|
161
|
+
async for o in res:
|
|
162
|
+
if isinstance(o, Completion): search_hits(o.message.content, o.message.raw['content'])
|
|
163
|
+
yield o
|
|
154
164
|
|
|
155
165
|
# %% ../nbs/04_anthropic.ipynb #bb11ab62
|
|
156
166
|
def _ant_cc(block, p):
|
|
@@ -160,6 +160,7 @@ class ResponseTurn(BasicRepr):
|
|
|
160
160
|
# %% ../nbs/06a_responses.ipynb #a648297f
|
|
161
161
|
class AsyncResponses:
|
|
162
162
|
"Run individual Responses turns over FastLLM."
|
|
163
|
+
def __init__(self): self.tasks = set() # served turns still running
|
|
163
164
|
def prepare(self, body, previous=None, server_tools=None, **kwargs):
|
|
164
165
|
if not isinstance(body, dict): raise ResponsesError('Request body must be an object')
|
|
165
166
|
model = body.get('model')
|
|
@@ -343,3 +344,21 @@ async def events(self:AsyncResponses, turn, finalize=None, stream=None):
|
|
|
343
344
|
except ResponsesError as exc: yield state.event('error', code=exc.code, message=str(exc), param=exc.param)
|
|
344
345
|
except APIError as exc: yield state.event('error', code=exc.code or 'provider_error', message=str(exc), param=None)
|
|
345
346
|
|
|
347
|
+
|
|
348
|
+
# %% ../nbs/06a_responses.ipynb #cc32ce40
|
|
349
|
+
@patch
|
|
350
|
+
def serve(self:AsyncResponses, turn, finalize=None, stream=None, timeout=None):
|
|
351
|
+
"The frames of `events`, from a task that outlives its reader, so a disconnected client still gets its turn finalized"
|
|
352
|
+
q = asyncio.Queue()
|
|
353
|
+
async def _run():
|
|
354
|
+
try:
|
|
355
|
+
async with asyncio.timeout(timeout):
|
|
356
|
+
async for frame in self.events(turn, finalize=finalize, stream=stream): q.put_nowait(frame)
|
|
357
|
+
except TimeoutError: q.put_nowait(response_event('error', 0, code='timeout', message=f'turn exceeded {timeout}s', param=None))
|
|
358
|
+
finally: q.put_nowait(None)
|
|
359
|
+
t = asyncio.create_task(_run())
|
|
360
|
+
self.tasks.add(t)
|
|
361
|
+
t.add_done_callback(self.tasks.discard)
|
|
362
|
+
async def _frames():
|
|
363
|
+
while (frame := await q.get()) is not None: yield frame
|
|
364
|
+
return _frames()
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_types.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
|
-
__all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'sample_img_url', '
|
|
7
|
-
'opus46', 'opus48', 'opus', 'opus5', 'fable', 'fable5', 'fable51', 'gpt54', 'gpt54m',
|
|
8
|
-
'codex54m', 'codex55', 'codex53spark', 'model_info_registry', 'modern_llm',
|
|
9
|
-
'deepseek_v4_common', 'deepseek_v4_flash_prices', 'mimo_v25_common', 'codex_pricing', 'sol',
|
|
10
|
-
'gpt56s', 'Usage', 'APIRegistry', 'mk_completion', 'fn_schema', 'payload_kwargs',
|
|
11
|
-
'get_api_key', 'wrap_typed', 'unwrap_typed', 'resize_b64', 'model_prices_meta',
|
|
12
|
-
'get_model_meta', 'register_model_info', 'get_model_info', 'effort_code', 'effort_levels',
|
|
13
|
-
'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour', 'price_tier', 'tier_rate']
|
|
6
|
+
__all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'sample_img_url', 'sample_doms', 'haik45', 'sonn45', 'sonn46',
|
|
7
|
+
'sonn', 'sonn5', 'opus46', 'opus48', 'opus', 'opus5', 'fable', 'fable5', 'fable51', 'gpt54', 'gpt54m',
|
|
8
|
+
'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark', 'model_info_registry', 'modern_llm',
|
|
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',
|
|
11
|
+
'provider_req', 'get_api_key', 'wrap_typed', 'unwrap_typed', 'resize_b64', 'model_prices_meta',
|
|
12
|
+
'infer_api_name', 'get_model_meta', 'register_model_info', 'get_model_info', 'effort_code', 'effort_levels',
|
|
13
|
+
'resolve_effort', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour', 'price_tier', 'tier_rate']
|
|
14
14
|
|
|
15
15
|
# %% ../nbs/00_types.ipynb #b4d047fd
|
|
16
16
|
import httpx2, base64, io
|
|
@@ -170,6 +170,7 @@ def model_prices_meta():
|
|
|
170
170
|
|
|
171
171
|
# %% ../nbs/00_types.ipynb #019882a3
|
|
172
172
|
sample_img_url = "https://img.magnific.com/free-photo/mountain-range-body-water_53876-139760.jpg?semt=ais_hybrid&w=740&q=80"
|
|
173
|
+
sample_doms = ('img.magnific.com', 'arxiv.org', 'openaiassets.blob.core.windows.net') # hosts of the sample media the lessons fetch, for cachy
|
|
173
174
|
|
|
174
175
|
# %% ../nbs/00_types.ipynb #68e488d8
|
|
175
176
|
def infer_api_name(model):
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Programming Language :: Python :: 3 :: Only",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ['fastcore>=2.1.18', 'aidialog>=0.0.
|
|
18
|
+
dependencies = ['fastcore>=2.1.18', 'aidialog>=0.0.25', 'fasttransport>=0.0.2', 'pillow']
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
fastllm-auth = "fastllm.codex:main"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fastllm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.51
|
|
4
4
|
Author-email: Kerem Turgutlu <keremturgutlu@gmail.com>
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/AnswerDotAI/fastllm
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
10
10
|
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: fastcore>=2.1.18
|
|
13
|
-
Requires-Dist: aidialog>=0.0.
|
|
13
|
+
Requires-Dist: aidialog>=0.0.25
|
|
14
14
|
Requires-Dist: fasttransport>=0.0.2
|
|
15
15
|
Requires-Dist: pillow
|
|
16
16
|
|
|
@@ -28,7 +28,7 @@ Run `pip install python-fastllm` or clone from github and install locally.
|
|
|
28
28
|
``` python
|
|
29
29
|
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
30
30
|
from fastllm.acomplete import acomplete
|
|
31
|
-
from fastllm.types import sample_img_url
|
|
31
|
+
from fastllm.types import sample_img_url, sample_doms
|
|
32
32
|
import asyncio, json
|
|
33
33
|
|
|
34
34
|
# Helpers
|
|
@@ -57,7 +57,7 @@ The same `acomplete` call works with Claude, GPT, Gemini, and Kimi. Just change
|
|
|
57
57
|
|
|
58
58
|
``` python
|
|
59
59
|
models = [
|
|
60
|
-
('claude-sonnet-
|
|
60
|
+
('claude-sonnet-5', {}),
|
|
61
61
|
('gpt-4o-mini', {}),
|
|
62
62
|
('models/gemini-3-flash-preview', {}),
|
|
63
63
|
('accounts/fireworks/models/kimi-k2p5', dict(vendor_name='fireworks_ai'))
|
|
@@ -81,46 +81,6 @@ for name, kw in models:
|
|
|
81
81
|
|
|
82
82
|
The response should be simple and direct.
|
|
83
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
84
|
## System Prompts
|
|
125
85
|
|
|
126
86
|
Pass a system prompt to any provider — `fastllm` maps it to each API’s native mechanism (Anthropic `system`, OpenAI `instructions`, Gemini `system_instruction`):
|
|
@@ -129,7 +89,7 @@ Pass a system prompt to any provider — `fastllm` maps it to each API’s nativ
|
|
|
129
89
|
sys = "You are a pirate chef. Always respond in pirate speak and mention food."
|
|
130
90
|
|
|
131
91
|
print("Claude: ", end='')
|
|
132
|
-
r = await stream([user("What should I do today?")], model='claude-sonnet-
|
|
92
|
+
r = await stream([user("What should I do today?")], model='claude-sonnet-5', system=sys, max_tokens=mtok)
|
|
133
93
|
|
|
134
94
|
print("Gemini: ", end='')
|
|
135
95
|
r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
|
|
@@ -162,7 +122,7 @@ r = await stream([user("What should I do today?")], model='models/gemini-3-flash
|
|
|
162
122
|
|
|
163
123
|
## Tool Calling — Define Once, Use Anywhere
|
|
164
124
|
|
|
165
|
-
Define tools in a single canonical format. `fastllm` translates to each provider’s native tool schema automatically. Here
|
|
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:
|
|
166
126
|
|
|
167
127
|
``` python
|
|
168
128
|
tools = [{"type": "function", "function": {
|
|
@@ -173,7 +133,7 @@ tools = [{"type": "function", "function": {
|
|
|
173
133
|
|
|
174
134
|
# Turn 1: Claude requests the tool
|
|
175
135
|
msgs = [user("What's the weather in Paris?")]
|
|
176
|
-
r1 = await stream(msgs, model='claude-sonnet-
|
|
136
|
+
r1 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
177
137
|
print("Tool calls:", r1.tool_calls)
|
|
178
138
|
```
|
|
179
139
|
|
|
@@ -183,44 +143,28 @@ print("Tool calls:", r1.tool_calls)
|
|
|
183
143
|
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
184
144
|
|
|
185
145
|
``` python
|
|
186
|
-
# Provide the tool result
|
|
187
|
-
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds'])]
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
msgs.append(user("Should I bring a jacket?"))
|
|
191
|
-
print("GPT: ", end='')
|
|
192
|
-
r2 = await stream(msgs, model='gpt-4o-mini', tools=tools, max_tokens=mtok)
|
|
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)
|
|
193
150
|
```
|
|
194
151
|
|
|
195
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.
|
|
196
153
|
|
|
197
|
-
``` python
|
|
198
|
-
# Turn 3: Gemini sees the full cross-provider tool history
|
|
199
|
-
msgs += [r2.message, user("How about tomorrow — will it rain?")]
|
|
200
|
-
r3 = await stream(msgs, model='models/gemini-3-flash-preview', tools=tools, max_tokens=mtok, web_search_options={})
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Tomorrow in Paris (Saturday, June 13), the weather is expected to be mostly pleasant and sunny.
|
|
204
|
-
|
|
205
|
-
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.
|
|
206
|
-
|
|
207
|
-
So, while you might see a stray shower, you likely won't need to worry about heavy rain!
|
|
208
|
-
|
|
209
154
|
## Tool Choice
|
|
210
155
|
|
|
211
156
|
Control whether the model must use tools, can’t use tools, or decides on its own:
|
|
212
157
|
|
|
213
158
|
``` python
|
|
214
159
|
# Force the model to call a tool (even for a greeting)
|
|
215
|
-
r = await stream([user("Hello there!")], model='claude-sonnet-
|
|
160
|
+
r = await stream([user("Hello there!")], model='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
|
|
216
161
|
print("Forced:", r.tool_calls)
|
|
217
162
|
|
|
218
163
|
# Prevent tool use (model must answer directly)
|
|
219
164
|
print("\nNo tools: ", end='')
|
|
220
|
-
r = await stream([user("What's the weather?")], model='claude-sonnet-
|
|
165
|
+
r = await stream([user("What's the weather?")], model='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
221
166
|
```
|
|
222
167
|
|
|
223
|
-
|
|
224
168
|
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
225
169
|
|
|
226
170
|
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
@@ -329,11 +273,11 @@ long_ctx = "You are an expert on the solar system. " * 200
|
|
|
329
273
|
system = Text(long_ctx, cache_control={'type': 'ephemeral'})
|
|
330
274
|
|
|
331
275
|
print("Call 1: ", end='')
|
|
332
|
-
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-
|
|
276
|
+
r1 = await stream([user("What is Jupiter's mass?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
333
277
|
print(f"Usage: {r1.usage}")
|
|
334
278
|
|
|
335
279
|
print("Call 2: ", end='')
|
|
336
|
-
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-
|
|
280
|
+
r2 = await stream([user("How about Saturn?")], model='claude-sonnet-5', system=system, max_tokens=mtok)
|
|
337
281
|
print(f"Usage: {r2.usage}")
|
|
338
282
|
```
|
|
339
283
|
|
|
@@ -388,7 +332,7 @@ img_msg = Msg(role='user', content=[
|
|
|
388
332
|
Text("What do you see in this image?")
|
|
389
333
|
])
|
|
390
334
|
|
|
391
|
-
for name, kw in [('claude-sonnet-
|
|
335
|
+
for name, kw in [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
|
|
392
336
|
print(f"{name:>30s}: ", end='')
|
|
393
337
|
r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
|
|
394
338
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fastllm-0.0.50 → python_fastllm-0.0.51}/python_fastllm.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|