python-fastllm 0.0.42__tar.gz → 0.0.44__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.42/python_fastllm.egg-info → python_fastllm-0.0.44}/PKG-INFO +27 -6
- python_fastllm-0.0.42/PKG-INFO → python_fastllm-0.0.44/README.md +25 -21
- python_fastllm-0.0.44/fastllm/__init__.py +5 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/_modidx.py +54 -1
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/acomplete.py +36 -54
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/anthropic.py +1 -1
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/chat.py +51 -32
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/gemini.py +12 -8
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/openai_chat.py +28 -18
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/openai_responses.py +45 -30
- python_fastllm-0.0.44/fastllm/responses.py +299 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/types.py +32 -4
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/pyproject.toml +1 -4
- python_fastllm-0.0.42/README.md → python_fastllm-0.0.44/python_fastllm.egg-info/PKG-INFO +42 -5
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/python_fastllm.egg-info/SOURCES.txt +1 -6
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/python_fastllm.egg-info/requires.txt +1 -0
- python_fastllm-0.0.42/fastllm/__init__.py +0 -1
- python_fastllm-0.0.42/fastllm/specs/anthropic.json +0 -1
- python_fastllm-0.0.42/fastllm/specs/anthropic.yml +0 -15684
- python_fastllm-0.0.42/fastllm/specs/gemini.json +0 -6951
- python_fastllm-0.0.42/fastllm/specs/openai.with-code-samples.json +0 -1
- python_fastllm-0.0.42/fastllm/specs/openai.with-code-samples.yml +0 -73650
- python_fastllm-0.0.42/fastllm/specs/spec_manifest.json +0 -17
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/fastllm/streaming.py +0 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.42 → python_fastllm-0.0.44}/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.44
|
|
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
|
|
@@ -12,6 +12,7 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
Requires-Dist: fastcore>=2.1.18
|
|
13
13
|
Requires-Dist: aidialog>=0.0.10
|
|
14
14
|
Requires-Dist: fastspec>=0.2.1
|
|
15
|
+
Requires-Dist: fasttransport>=0.0.2
|
|
15
16
|
Requires-Dist: pillow
|
|
16
17
|
|
|
17
18
|
# fastllm
|
|
@@ -26,8 +27,7 @@ Clone and install locally into your `aai-ws` env
|
|
|
26
27
|
## Setup
|
|
27
28
|
|
|
28
29
|
``` python
|
|
29
|
-
from
|
|
30
|
-
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg
|
|
30
|
+
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
31
31
|
from fastllm.acomplete import acomplete
|
|
32
32
|
import asyncio, json
|
|
33
33
|
|
|
@@ -178,7 +178,9 @@ print("Tool calls:", r1.tool_calls)
|
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
I'll check the current weather in Paris for you.
|
|
181
|
-
|
|
181
|
+
- ⏳ `get_weather(city="Paris")` ⏳
|
|
182
|
+
|
|
183
|
+
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
182
184
|
|
|
183
185
|
``` python
|
|
184
186
|
# Provide the tool result
|
|
@@ -218,8 +220,9 @@ print("\nNo tools: ", end='')
|
|
|
218
220
|
r = await stream([user("What's the weather?")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
219
221
|
```
|
|
220
222
|
|
|
223
|
+
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
221
224
|
|
|
222
|
-
Forced: [
|
|
225
|
+
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
223
226
|
|
|
224
227
|
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
228
|
|
|
@@ -281,7 +284,25 @@ r = await stream([user("What is the latest Python release?")], model='gpt-4o-min
|
|
|
281
284
|
print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
|
|
282
285
|
```
|
|
283
286
|
|
|
284
|
-
GPT + web search:
|
|
287
|
+
GPT + web search:
|
|
288
|
+
|
|
289
|
+
```json {.tool}
|
|
290
|
+
{
|
|
291
|
+
"id": "ws_0f48f4b49d3a4937006a2c04cb944481a3882056257ae43956",
|
|
292
|
+
"name": "web_search",
|
|
293
|
+
"args": {
|
|
294
|
+
"type": "search",
|
|
295
|
+
"queries": [
|
|
296
|
+
"latest Python release October 2023"
|
|
297
|
+
],
|
|
298
|
+
"query": "latest Python release October 2023"
|
|
299
|
+
},
|
|
300
|
+
"result": "Server tool call executed.",
|
|
301
|
+
"server": true
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
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
306
|
|
|
286
307
|
Python 3.14 introduced several significant features, including:
|
|
287
308
|
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-fastllm
|
|
3
|
-
Version: 0.0.42
|
|
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.10
|
|
14
|
-
Requires-Dist: fastspec>=0.2.1
|
|
15
|
-
Requires-Dist: pillow
|
|
16
|
-
|
|
17
1
|
# fastllm
|
|
18
2
|
|
|
19
3
|
|
|
@@ -26,8 +10,7 @@ Clone and install locally into your `aai-ws` env
|
|
|
26
10
|
## Setup
|
|
27
11
|
|
|
28
12
|
``` python
|
|
29
|
-
from
|
|
30
|
-
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg
|
|
13
|
+
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
31
14
|
from fastllm.acomplete import acomplete
|
|
32
15
|
import asyncio, json
|
|
33
16
|
|
|
@@ -178,7 +161,9 @@ print("Tool calls:", r1.tool_calls)
|
|
|
178
161
|
```
|
|
179
162
|
|
|
180
163
|
I'll check the current weather in Paris for you.
|
|
181
|
-
|
|
164
|
+
- ⏳ `get_weather(city="Paris")` ⏳
|
|
165
|
+
|
|
166
|
+
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
182
167
|
|
|
183
168
|
``` python
|
|
184
169
|
# Provide the tool result
|
|
@@ -218,8 +203,9 @@ print("\nNo tools: ", end='')
|
|
|
218
203
|
r = await stream([user("What's the weather?")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
219
204
|
```
|
|
220
205
|
|
|
206
|
+
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
221
207
|
|
|
222
|
-
Forced: [
|
|
208
|
+
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
223
209
|
|
|
224
210
|
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
211
|
|
|
@@ -281,7 +267,25 @@ r = await stream([user("What is the latest Python release?")], model='gpt-4o-min
|
|
|
281
267
|
print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
|
|
282
268
|
```
|
|
283
269
|
|
|
284
|
-
GPT + web search:
|
|
270
|
+
GPT + web search:
|
|
271
|
+
|
|
272
|
+
```json {.tool}
|
|
273
|
+
{
|
|
274
|
+
"id": "ws_0f48f4b49d3a4937006a2c04cb944481a3882056257ae43956",
|
|
275
|
+
"name": "web_search",
|
|
276
|
+
"args": {
|
|
277
|
+
"type": "search",
|
|
278
|
+
"queries": [
|
|
279
|
+
"latest Python release October 2023"
|
|
280
|
+
],
|
|
281
|
+
"query": "latest Python release October 2023"
|
|
282
|
+
},
|
|
283
|
+
"result": "Server tool call executed.",
|
|
284
|
+
"server": true
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
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
289
|
|
|
286
290
|
Python 3.14 introduced several significant features, including:
|
|
287
291
|
|
|
@@ -141,6 +141,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
141
141
|
'fastllm.gemini.denorm_user': ('gemini.html#denorm_user', 'fastllm/gemini.py'),
|
|
142
142
|
'fastllm.gemini.denorm_video': ('gemini.html#denorm_video', 'fastllm/gemini.py'),
|
|
143
143
|
'fastllm.gemini.denorm_web_search': ('gemini.html#denorm_web_search', 'fastllm/gemini.py'),
|
|
144
|
+
'fastllm.gemini.endpoint': ('gemini.html#endpoint', 'fastllm/gemini.py'),
|
|
144
145
|
'fastllm.gemini.get_hdrs': ('gemini.html#get_hdrs', 'fastllm/gemini.py'),
|
|
145
146
|
'fastllm.gemini.mk_payload': ('gemini.html#mk_payload', 'fastllm/gemini.py'),
|
|
146
147
|
'fastllm.gemini.norm_finish': ('gemini.html#norm_finish', 'fastllm/gemini.py'),
|
|
@@ -167,13 +168,18 @@ d = { 'settings': { 'branch': 'main',
|
|
|
167
168
|
'fastllm.openai_chat.denorm_tool_use': ('oai_chat.html#denorm_tool_use', 'fastllm/openai_chat.py'),
|
|
168
169
|
'fastllm.openai_chat.denorm_user': ('oai_chat.html#denorm_user', 'fastllm/openai_chat.py'),
|
|
169
170
|
'fastllm.openai_chat.denorm_web_search': ('oai_chat.html#denorm_web_search', 'fastllm/openai_chat.py'),
|
|
171
|
+
'fastllm.openai_chat.fix_payload': ('oai_chat.html#fix_payload', 'fastllm/openai_chat.py'),
|
|
170
172
|
'fastllm.openai_chat.get_hdrs': ('oai_chat.html#get_hdrs', 'fastllm/openai_chat.py'),
|
|
171
173
|
'fastllm.openai_chat.mk_payload': ('oai_chat.html#mk_payload', 'fastllm/openai_chat.py'),
|
|
172
174
|
'fastllm.openai_chat.norm_finish': ('oai_chat.html#norm_finish', 'fastllm/openai_chat.py'),
|
|
173
175
|
'fastllm.openai_chat.norm_parts': ('oai_chat.html#norm_parts', 'fastllm/openai_chat.py'),
|
|
174
176
|
'fastllm.openai_chat.norm_sse_event': ('oai_chat.html#norm_sse_event', 'fastllm/openai_chat.py'),
|
|
175
177
|
'fastllm.openai_chat.norm_tool_calls': ('oai_chat.html#norm_tool_calls', 'fastllm/openai_chat.py')},
|
|
176
|
-
'fastllm.openai_responses': { 'fastllm.openai_responses.
|
|
178
|
+
'fastllm.openai_responses': { 'fastllm.openai_responses.Completion.response_id': ( 'oai_responses.html#completion.response_id',
|
|
179
|
+
'fastllm/openai_responses.py'),
|
|
180
|
+
'fastllm.openai_responses.Completion.response_id_reusable': ( 'oai_responses.html#completion.response_id_reusable',
|
|
181
|
+
'fastllm/openai_responses.py'),
|
|
182
|
+
'fastllm.openai_responses._sanid': ('oai_responses.html#_sanid', 'fastllm/openai_responses.py'),
|
|
177
183
|
'fastllm.openai_responses.acollect_stream': ( 'oai_responses.html#acollect_stream',
|
|
178
184
|
'fastllm/openai_responses.py'),
|
|
179
185
|
'fastllm.openai_responses.cost': ('oai_responses.html#cost', 'fastllm/openai_responses.py'),
|
|
@@ -207,6 +213,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
207
213
|
'fastllm/openai_responses.py'),
|
|
208
214
|
'fastllm.openai_responses.denorm_web_search': ( 'oai_responses.html#denorm_web_search',
|
|
209
215
|
'fastllm/openai_responses.py'),
|
|
216
|
+
'fastllm.openai_responses.fix_payload': ( 'oai_responses.html#fix_payload',
|
|
217
|
+
'fastllm/openai_responses.py'),
|
|
210
218
|
'fastllm.openai_responses.get_hdrs': ( 'oai_responses.html#get_hdrs',
|
|
211
219
|
'fastllm/openai_responses.py'),
|
|
212
220
|
'fastllm.openai_responses.mk_payload': ( 'oai_responses.html#mk_payload',
|
|
@@ -223,6 +231,50 @@ d = { 'settings': { 'branch': 'main',
|
|
|
223
231
|
'fastllm/openai_responses.py'),
|
|
224
232
|
'fastllm.openai_responses.norm_usage': ( 'oai_responses.html#norm_usage',
|
|
225
233
|
'fastllm/openai_responses.py')},
|
|
234
|
+
'fastllm.responses': { 'fastllm.responses.AsyncResponses': ('responses.html#asyncresponses', 'fastllm/responses.py'),
|
|
235
|
+
'fastllm.responses.AsyncResponses.call': ('responses.html#asyncresponses.call', 'fastllm/responses.py'),
|
|
236
|
+
'fastllm.responses.AsyncResponses.events': ( 'responses.html#asyncresponses.events',
|
|
237
|
+
'fastllm/responses.py'),
|
|
238
|
+
'fastllm.responses.AsyncResponses.prepare': ( 'responses.html#asyncresponses.prepare',
|
|
239
|
+
'fastllm/responses.py'),
|
|
240
|
+
'fastllm.responses.AsyncResponses.state': ( 'responses.html#asyncresponses.state',
|
|
241
|
+
'fastllm/responses.py'),
|
|
242
|
+
'fastllm.responses.ResponseState': ('responses.html#responsestate', 'fastllm/responses.py'),
|
|
243
|
+
'fastllm.responses.ResponseState.__init__': ( 'responses.html#responsestate.__init__',
|
|
244
|
+
'fastllm/responses.py'),
|
|
245
|
+
'fastllm.responses.ResponseTurn': ('responses.html#responseturn', 'fastllm/responses.py'),
|
|
246
|
+
'fastllm.responses.ResponseTurn.__init__': ( 'responses.html#responseturn.__init__',
|
|
247
|
+
'fastllm/responses.py'),
|
|
248
|
+
'fastllm.responses.ResponseTurn.provider_previous_id': ( 'responses.html#responseturn.provider_previous_id',
|
|
249
|
+
'fastllm/responses.py'),
|
|
250
|
+
'fastllm.responses.ResponsesError': ('responses.html#responseserror', 'fastllm/responses.py'),
|
|
251
|
+
'fastllm.responses.ResponsesError.__init__': ( 'responses.html#responseserror.__init__',
|
|
252
|
+
'fastllm/responses.py'),
|
|
253
|
+
'fastllm.responses._ResponseStream': ('responses.html#_responsestream', 'fastllm/responses.py'),
|
|
254
|
+
'fastllm.responses._ResponseStream.__init__': ( 'responses.html#_responsestream.__init__',
|
|
255
|
+
'fastllm/responses.py'),
|
|
256
|
+
'fastllm.responses._ResponseStream.done_events': ( 'responses.html#_responsestream.done_events',
|
|
257
|
+
'fastllm/responses.py'),
|
|
258
|
+
'fastllm.responses._ResponseStream.event': ( 'responses.html#_responsestream.event',
|
|
259
|
+
'fastllm/responses.py'),
|
|
260
|
+
'fastllm.responses._ResponseStream.part_events': ( 'responses.html#_responsestream.part_events',
|
|
261
|
+
'fastllm/responses.py'),
|
|
262
|
+
'fastllm.responses._ResponseStream.text_events': ( 'responses.html#_responsestream.text_events',
|
|
263
|
+
'fastllm/responses.py'),
|
|
264
|
+
'fastllm.responses._ResponseStream.tool_events': ( 'responses.html#_responsestream.tool_events',
|
|
265
|
+
'fastllm/responses.py'),
|
|
266
|
+
'fastllm.responses._arguments': ('responses.html#_arguments', 'fastllm/responses.py'),
|
|
267
|
+
'fastllm.responses._msg_parts': ('responses.html#_msg_parts', 'fastllm/responses.py'),
|
|
268
|
+
'fastllm.responses._new_id': ('responses.html#_new_id', 'fastllm/responses.py'),
|
|
269
|
+
'fastllm.responses._response_base': ('responses.html#_response_base', 'fastllm/responses.py'),
|
|
270
|
+
'fastllm.responses._text_content': ('responses.html#_text_content', 'fastllm/responses.py'),
|
|
271
|
+
'fastllm.responses.normalize_call_ids': ('responses.html#normalize_call_ids', 'fastllm/responses.py'),
|
|
272
|
+
'fastllm.responses.pending_response': ('responses.html#pending_response', 'fastllm/responses.py'),
|
|
273
|
+
'fastllm.responses.response_event': ('responses.html#response_event', 'fastllm/responses.py'),
|
|
274
|
+
'fastllm.responses.response_input': ('responses.html#response_input', 'fastllm/responses.py'),
|
|
275
|
+
'fastllm.responses.response_object': ('responses.html#response_object', 'fastllm/responses.py'),
|
|
276
|
+
'fastllm.responses.response_output': ('responses.html#response_output', 'fastllm/responses.py'),
|
|
277
|
+
'fastllm.responses.response_usage': ('responses.html#response_usage', 'fastllm/responses.py')},
|
|
226
278
|
'fastllm.streaming': { 'fastllm.streaming.Delta': ('streaming.html#delta', 'fastllm/streaming.py'),
|
|
227
279
|
'fastllm.streaming.Delta.__eq__': ('streaming.html#delta.__eq__', 'fastllm/streaming.py'),
|
|
228
280
|
'fastllm.streaming.Delta.__hash__': ('streaming.html#delta.__hash__', 'fastllm/streaming.py'),
|
|
@@ -279,6 +331,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
279
331
|
'fastllm.types.model_prices_meta': ('types.html#model_prices_meta', 'fastllm/types.py'),
|
|
280
332
|
'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
|
|
281
333
|
'fastllm.types.price_tier': ('types.html#price_tier', 'fastllm/types.py'),
|
|
334
|
+
'fastllm.types.provider_req': ('types.html#provider_req', 'fastllm/types.py'),
|
|
282
335
|
'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
|
|
283
336
|
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
|
|
284
337
|
'fastllm.types.tier_rate': ('types.html#tier_rate', 'fastllm/types.py'),
|
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_acomplete.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
|
-
__all__ = ['
|
|
7
|
-
'mk_client', 'ContextWindowExceededError', 'acomplete']
|
|
6
|
+
__all__ = ['vendor_mapping', 'defaults', 'split_vendor', 'mk_client', 'ContextWindowExceededError', 'acomplete']
|
|
8
7
|
|
|
9
8
|
# %% ../nbs/06_acomplete.ipynb #f2f57253
|
|
10
9
|
import asyncio,json,httpx2
|
|
11
|
-
from importlib.resources import files
|
|
12
10
|
from fastcore.utils import *
|
|
13
11
|
from fastcore.meta import *
|
|
14
|
-
from
|
|
15
|
-
from fastspec.oapi import *
|
|
12
|
+
from fasttransport.core import AsyncHttpCli
|
|
16
13
|
from fastspec.errors import APIError
|
|
17
14
|
|
|
18
15
|
from .types import *
|
|
@@ -24,12 +21,6 @@ from .openai_chat import *
|
|
|
24
21
|
from .anthropic import *
|
|
25
22
|
from .gemini import *
|
|
26
23
|
|
|
27
|
-
# %% ../nbs/06_acomplete.ipynb #6d11ac28
|
|
28
|
-
specs_path = files('fastllm') / 'specs'
|
|
29
|
-
ant_spec = SpecParser.from_openapi(dict2obj(json.loads((specs_path/'anthropic.json').read_text())))
|
|
30
|
-
oai_spec = SpecParser.from_openapi(dict2obj(json.loads((specs_path/'openai.with-code-samples.json').read_text())))
|
|
31
|
-
gem_spec = SpecParser.from_discovery(dict2obj(json.loads((specs_path/'gemini.json').read_text())))
|
|
32
|
-
|
|
33
24
|
# %% ../nbs/06_acomplete.ipynb #32ee2546
|
|
34
25
|
_codex_path = os.getenv('CODEX_AUTH_PATH', '~/.codex/auth.json')
|
|
35
26
|
_codex_json = _codex_path, ('tokens','access_token')
|
|
@@ -47,11 +38,8 @@ vendor_mapping = {
|
|
|
47
38
|
"fireworks_ai": ('openai_chat', "https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"),
|
|
48
39
|
"qwen": ('openai_chat', "https://dashscope.aliyuncs.com/compatible-mode/v1", "QWEN_API_KEY"),
|
|
49
40
|
"minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY"),
|
|
50
|
-
"meta_ai": ('openai', "https://api.meta.ai/v1", "META_API_KEY")
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# %% ../nbs/06_acomplete.ipynb #77d27ea7
|
|
54
|
-
api2spec = {'openai':oai_spec, 'openai_chat':oai_spec, 'anthropic':ant_spec, 'gemini':gem_spec}
|
|
41
|
+
"meta_ai": ('openai', "https://api.meta.ai/v1", "META_API_KEY"),
|
|
42
|
+
"zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")}
|
|
55
43
|
|
|
56
44
|
# %% ../nbs/06_acomplete.ipynb #e3ed40fb
|
|
57
45
|
def split_vendor(model):
|
|
@@ -96,13 +84,11 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_ur
|
|
|
96
84
|
api_key = get_api_key(api_key, env_api_nm)
|
|
97
85
|
except KeyError: raise ValueError(f"Unknown vendor '{vendor_name}', {err_msg}")
|
|
98
86
|
elif base_url and api_key: vendor_name, api_name = ifnone(vendor_name, 'custom'), ifnone(api_name, 'openai_chat')
|
|
99
|
-
elif (api_name:=infer_api_name(model)): base_url, vendor_name =
|
|
87
|
+
elif (api_name:=infer_api_name(model)): base_url, vendor_name = vendor_mapping[api_name][1], api_name
|
|
100
88
|
else: raise ValueError(f"Model {model} can't be auto resolved, {err_msg}")
|
|
101
89
|
api = api_registry[api_name]
|
|
102
|
-
|
|
103
|
-
cli =
|
|
104
|
-
if base_url is not None:
|
|
105
|
-
for op in cli.ops: op.base_url = base_url # pyright: ignore[reportAttributeAccessIssue]
|
|
90
|
+
hdrs = merge(api.get_hdrs(api_key), ifnone(xtra_hdrs, {}))
|
|
91
|
+
cli = AsyncHttpCli(base_url, base_headers=hdrs, timeout=timeout)
|
|
106
92
|
return cli, api_name, vendor_name
|
|
107
93
|
|
|
108
94
|
# %% ../nbs/06_acomplete.ipynb #df851a5c
|
|
@@ -113,8 +99,8 @@ def _is_ctx_exceeded(code, msg):
|
|
|
113
99
|
if any(x in m for x in ("string_above_max_length", "invalid 'user'")): return False
|
|
114
100
|
if str(code or "").lower() == "context_length_exceeded": return True
|
|
115
101
|
return any(s in m for s in ("exceed context limit", "maximum context length", "maximum context limit",
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
"longer than the model's context length", "input tokens exceed the configured limit",
|
|
103
|
+
"exceeds the maximum number of tokens allowed", "prompt is too long", "exceeds the context window"))
|
|
118
104
|
|
|
119
105
|
def _classify_error(exc):
|
|
120
106
|
"Upgrade generic `APIError` to a specific subclass if applicable."
|
|
@@ -134,14 +120,13 @@ async def _classify_error_stream(gen):
|
|
|
134
120
|
# %% ../nbs/06_acomplete.ipynb #f626a4e1
|
|
135
121
|
defaults = SimpleNamespace(debug_mode=False)
|
|
136
122
|
|
|
137
|
-
def _debug_print(model, api_name, vendor_name, payload,
|
|
123
|
+
def _debug_print(model, api_name, vendor_name, payload, url):
|
|
138
124
|
"Pretty-print acomplete inputs when defaults.debug_mode is set"
|
|
139
125
|
from pprint import pformat
|
|
140
126
|
p = dict(payload)
|
|
141
|
-
if defaults.debug_mode == 'brief' and 'tools' in p:
|
|
142
|
-
p['tools'] = '; '.join(o.get('name', o.get('type', o)) for o in p['tools'])
|
|
127
|
+
if defaults.debug_mode == 'brief' and 'tools' in p: p['tools'] = '; '.join(o.get('name', o.get('type', o)) for o in p['tools'])
|
|
143
128
|
print('━'*60)
|
|
144
|
-
print(f"\033[1;36mfastllm debug\033[0m model={model} vendor={vendor_name} api={api_name}
|
|
129
|
+
print(f"\033[1;36mfastllm debug\033[0m model={model} vendor={vendor_name} api={api_name} url={url}")
|
|
145
130
|
print('─'*60)
|
|
146
131
|
print(f"\033[1;33mpayload:\033[0m\n{pformat(p, width=120, sort_dicts=False)}")
|
|
147
132
|
print('━'*60)
|
|
@@ -169,39 +154,36 @@ async def _retry_stream(mk_gen, retries=2, retry_delay=0.5):
|
|
|
169
154
|
|
|
170
155
|
# %% ../nbs/06_acomplete.ipynb #2379ec94
|
|
171
156
|
@delegates(payload_kwargs)
|
|
172
|
-
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
173
|
-
|
|
174
|
-
stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
|
|
157
|
+
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None, base_url=None, endpoint=None, xtra_body=None, xtra_hdrs=None,
|
|
158
|
+
stream=False, previous_response_id=None, stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
|
|
175
159
|
"Unified completion across different APIs."
|
|
176
160
|
if not vendor_name and not api_name and not (base_url and api_key):
|
|
177
161
|
v, m = split_vendor(model)
|
|
178
162
|
if v in vendor_mapping: vendor_name, model = v, m
|
|
179
163
|
elif v: api_name, model = v, m # a registered transport api (e.g. claude_code): not an HTTP vendor
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
|
|
184
|
-
async def _mk_gen():
|
|
185
|
-
async for o in api.acollect_stream(payload, model=model, vendor_name='claude_code', stop_callables=stop_callables): yield o
|
|
186
|
-
return _retry_stream(_mk_gen, retries, retry_delay)
|
|
187
|
-
cli, api_name, vendor_name = mk_client(model=model, vendor_name=vendor_name, api_name=api_name, api_key=api_key, base_url=base_url, xtra_hdrs=xtra_hdrs)
|
|
164
|
+
own = api_name in api_registry and not hasattr(api_registry[api_name], 'endpoint') if api_name else False
|
|
165
|
+
if own: vendor_name = ifnone(vendor_name, api_name) # an own-transport api: no HTTP client to build
|
|
166
|
+
else: cli, api_name, vendor_name = mk_client(model=model, vendor_name=vendor_name, api_name=api_name, api_key=api_key, base_url=base_url, xtra_hdrs=xtra_hdrs)
|
|
188
167
|
api = api_registry[api_name]
|
|
168
|
+
if previous_response_id is not None:
|
|
169
|
+
if not getattr(api, 'supports_previous_response_id', False):
|
|
170
|
+
raise ValueError('previous_response_id is not supported by this transport')
|
|
171
|
+
kwargs['previous_response_id'] = previous_response_id
|
|
189
172
|
payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
func = attrgetter(api.op_path[stream])(cli)
|
|
198
|
-
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, payload, func)
|
|
199
|
-
async def _call(): return await func(**payload)
|
|
200
|
-
if not stream:
|
|
201
|
-
resp = await _retry_call(_call, retries, retry_delay)
|
|
202
|
-
return mk_completion(resp, model=model, api_name=api_name, vendor_name=vendor_name)
|
|
203
|
-
|
|
173
|
+
xtra_body = ifnone(xtra_body, {})
|
|
174
|
+
if fix := getattr(api, 'fix_payload', None): fix(payload, model, vendor_name)
|
|
175
|
+
if not own:
|
|
176
|
+
ep = endpoint or api.endpoint
|
|
177
|
+
path,params = ep(model, stream) if callable(ep) else (ep, None)
|
|
178
|
+
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, payload | xtra_body, None if own else cli._url(path))
|
|
179
|
+
async def _call(): return await provider_req(cli, path, payload | xtra_body, params=params, stream=stream)
|
|
204
180
|
async def _mk_gen():
|
|
205
|
-
resp = await _call()
|
|
181
|
+
resp = payload if own else await _call() # an own-transport api's `acollect_stream` makes the request itself, from the payload
|
|
206
182
|
async for o in api.acollect_stream(resp, model=model, vendor_name=vendor_name, stop_callables=stop_callables): yield o
|
|
207
|
-
|
|
183
|
+
if stream or own:
|
|
184
|
+
result = _retry_stream(_mk_gen, retries, retry_delay)
|
|
185
|
+
if stream: return result
|
|
186
|
+
async for completion in result: pass # own-transport apis are streaming-only: drain for the completion
|
|
187
|
+
return completion
|
|
188
|
+
resp = await _retry_call(_call, retries, retry_delay)
|
|
189
|
+
return mk_completion(resp, model=model, api_name=api_name, vendor_name=vendor_name)
|
|
@@ -343,4 +343,4 @@ def cost(usage, m):
|
|
|
343
343
|
# %% ../nbs/04_anthropic.ipynb #f7c0b989
|
|
344
344
|
api_registry.register('anthropic', norm_tool_calls=norm_tool_calls, norm_parts=norm_parts, norm_finish=norm_finish, norm_usage=norm_usage,
|
|
345
345
|
finalize_usage=finalize_usage, acollect_stream=acollect_stream, mk_payload=mk_payload, cost=cost, get_hdrs=get_hdrs,
|
|
346
|
-
|
|
346
|
+
endpoint='/v1/messages')
|
|
@@ -71,7 +71,7 @@ def _mk_tool_result(res):
|
|
|
71
71
|
# %% ../nbs/07_chat.ipynb #a0fcc96e
|
|
72
72
|
def _call_func(tc:ToolUse, tool_schemas, ns, callf):
|
|
73
73
|
"Call tool function synchronously and return formatted result"
|
|
74
|
-
fn, valid = tc.name, {
|
|
74
|
+
fn, valid = tc.name, {fs[0] for o in tool_schemas or [] if (fs:=fn_schema(o))}
|
|
75
75
|
if fn not in valid: return f"Tool not defined in tool_schemas: {fn}"
|
|
76
76
|
else: return callf(fn, tc.arguments, ns=ns, raise_on_err=False)
|
|
77
77
|
|
|
@@ -92,14 +92,14 @@ async def structured(
|
|
|
92
92
|
):
|
|
93
93
|
"Return the value of the tool call (generally used for structured outputs)"
|
|
94
94
|
t = lite_mk_func(tool)
|
|
95
|
-
r = await acomplete(msgs, m, system=sp, tools=[t], tool_choice=
|
|
95
|
+
r = await acomplete(msgs, m, system=sp, tools=[t], tool_choice=fn_schema(t)[0], **kwargs)
|
|
96
96
|
return tool(**r.tool_calls[0].arguments)
|
|
97
97
|
|
|
98
98
|
# %% ../nbs/07_chat.ipynb #1fe8a9bc
|
|
99
99
|
def _has_search(info): return bool(info.get('search_context_cost_per_query') or info.get('supports_web_search'))
|
|
100
100
|
|
|
101
101
|
# %% ../nbs/07_chat.ipynb #2d78087b
|
|
102
|
-
effort = AttrDict({o[0]:o for o in ('low','medium','high')})
|
|
102
|
+
effort = AttrDict({o[0]:o for o in ('none','low','medium','high')})
|
|
103
103
|
effort['x'] = 'max'
|
|
104
104
|
|
|
105
105
|
# %% ../nbs/07_chat.ipynb #dc17f844
|
|
@@ -125,10 +125,9 @@ class UsageStats:
|
|
|
125
125
|
@classmethod
|
|
126
126
|
def from_response(cls, r):
|
|
127
127
|
u = r.usage
|
|
128
|
-
return cls(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
web_search_requests=search_count(r), cost=r.cost)
|
|
128
|
+
return cls(model=r.model or '', prompt_tokens=u.prompt_tokens or 0, completion_tokens=u.completion_tokens or 0,
|
|
129
|
+
total_tokens=u.total_tokens or 0, cached_tokens=u.cached_tokens or 0, cache_creation_tokens=u.cache_creation_tokens or 0,
|
|
130
|
+
reasoning_tokens=u.reasoning_tokens or 0, web_search_requests=search_count(r), cost=r.cost)
|
|
132
131
|
|
|
133
132
|
def __add__(self, other):
|
|
134
133
|
if other is None: return self
|
|
@@ -170,7 +169,9 @@ class AsyncChat:
|
|
|
170
169
|
vendor_name=None, # Vendor name, one of vendor_mapping which resolves api_base/api_key automatically
|
|
171
170
|
api_key=None, # API key when model can't be resolved or vendor_name is not known or codex
|
|
172
171
|
base_url=None, # API base url when model can't be resolved or vendor_name is not known
|
|
172
|
+
endpoint=None, # Override the transport's request path, for a server mounting Responses at a custom location
|
|
173
173
|
extra_headers=None, # Extra HTTP headers for custom providers
|
|
174
|
+
use_previous_response_id=False, # Continue tool rounds with Responses IDs instead of replaying history
|
|
174
175
|
markup=0, # Cost markup multiplier (e.g. 0.5 for 50%)
|
|
175
176
|
showthink=False, # Stamp streamed thinking parts to display their text rather than 🧠 glyphs
|
|
176
177
|
cbs:list=None, # Chat callbacks
|
|
@@ -187,6 +188,7 @@ class AsyncChat:
|
|
|
187
188
|
elif ns is None: ns = globals()
|
|
188
189
|
self.tool_schemas = [lite_mk_func(t) for t in tools] if tools else None
|
|
189
190
|
self.use = UsageStats()
|
|
191
|
+
self.response_id,self._response_hist_idx = None,0
|
|
190
192
|
self._turn_start = 0 # index into `hist` where the current turn began, for `full`
|
|
191
193
|
self.last_req_use = None # usage of the latest request only; `use` accumulates across a turn's tool-call steps
|
|
192
194
|
store_attr(but='cbs')
|
|
@@ -223,7 +225,10 @@ class AsyncChat:
|
|
|
223
225
|
def _usrtools(tcs): return L(tcs).filter(lambda o: not o.server) if tcs else None
|
|
224
226
|
|
|
225
227
|
# %% ../nbs/07_chat.ipynb #19b87f53
|
|
228
|
+
_effort_names = dict(none='n', low='l', medium='m', high='h', xhigh='x', max='x')
|
|
229
|
+
|
|
226
230
|
def _think_kw(model, think, vendor_name):
|
|
231
|
+
think = _effort_names.get(think, think)
|
|
227
232
|
if not think: return {}
|
|
228
233
|
if 'opus-4-7' in model:
|
|
229
234
|
e = 'xhigh' if think=='h' else effort.get(think)
|
|
@@ -250,6 +255,7 @@ def _prep_call(self:AsyncChat, search, max_tokens, kwargs, stream=False, think=N
|
|
|
250
255
|
if self.vendor_name: kwargs['vendor_name'] = self.vendor_name
|
|
251
256
|
if self.api_key: kwargs['api_key'] = self.api_key
|
|
252
257
|
if self.base_url: kwargs['base_url'] = self.base_url
|
|
258
|
+
if self.endpoint: kwargs['endpoint'] = self.endpoint
|
|
253
259
|
if self.extra_headers: kwargs['xtra_headers'] = self.extra_headers
|
|
254
260
|
kwargs.update(_think_kw(self.model, think, self.vendor_name))
|
|
255
261
|
return max_tokens
|
|
@@ -276,19 +282,23 @@ async def astream_with_complete(self, agen, postproc=noop):
|
|
|
276
282
|
# %% ../nbs/07_chat.ipynb #a049cf52
|
|
277
283
|
@patch
|
|
278
284
|
@delegates(acomplete)
|
|
279
|
-
async def _call(self:AsyncChat, msg=None,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
self.
|
|
285
|
+
async def _call(self:AsyncChat, msg=None, step=1, search=None, tool_choice=None, initial_body=None, **kwargs):
|
|
286
|
+
"One model turn plus its tool round, recursing for the next; turn-constant options come from `turn_opts`"
|
|
287
|
+
t = AttrDict(self.turn_opts)
|
|
288
|
+
if step>t.max_steps+1: return
|
|
289
|
+
max_tokens = self._prep_call(search, t.max_tokens, kwargs, stream=t.stream, think=t.think)
|
|
290
|
+
self.turn_sysp, turn_msgs = self._prep_msg(msg)
|
|
291
|
+
self.turn_msgs = turn_msgs[self._response_hist_idx:] if self.response_id else turn_msgs
|
|
284
292
|
async for o in self._call_cbs('after_msgs'): yield o
|
|
285
293
|
|
|
286
|
-
self.turn_kwargs, self.stream = kwargs, stream
|
|
294
|
+
self.turn_kwargs, self.stream = kwargs, t.stream
|
|
295
|
+
kw = merge(kwargs, dict(xtra_body=merge(kwargs.get('xtra_body'), initial_body))) if initial_body else kwargs
|
|
287
296
|
async for o in self._call_cbs('before_acomplete'): yield o
|
|
288
|
-
res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream, tools=self.tool_schemas,
|
|
289
|
-
tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if think else ifnone(temp,self.temp),
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=t.stream, tools=self.tool_schemas,
|
|
298
|
+
tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if t.think else ifnone(t.temp,self.temp),
|
|
299
|
+
previous_response_id=self.response_id if self.use_previous_response_id else None,
|
|
300
|
+
cache_idxs=self.cache_idxs if self.cache else [], ttl=self.ttl, **kw)
|
|
301
|
+
if t.stream:
|
|
292
302
|
res = astream_with_complete(res, postproc=postproc)
|
|
293
303
|
async for chunk in res:
|
|
294
304
|
if isinstance(chunk, Thinking) and self.showthink: chunk.showthink = True
|
|
@@ -296,6 +306,10 @@ async def _call(self:AsyncChat, msg=None, temp=None, think=None, search=None, st
|
|
|
296
306
|
res = res.value
|
|
297
307
|
self.turn_res, self.turn_msg = res, contents(res)
|
|
298
308
|
self.hist.append(self.turn_msg)
|
|
309
|
+
if self.use_previous_response_id:
|
|
310
|
+
if res.response_id: self.response_id,self._response_hist_idx = res.response_id,len(self.hist)
|
|
311
|
+
elif res.tool_calls: raise ValueError('tool-call response requires a continuation-capable transport')
|
|
312
|
+
else: self.response_id,self._response_hist_idx = None,0
|
|
299
313
|
async for o in self._call_cbs('after_acomplete'): yield o
|
|
300
314
|
self._track(self.turn_res)
|
|
301
315
|
yield res
|
|
@@ -303,46 +317,51 @@ async def _call(self:AsyncChat, msg=None, temp=None, think=None, search=None, st
|
|
|
303
317
|
self.toolloop, self.prompt, tmsg = False, None, None
|
|
304
318
|
async for o in self._call_cbs('before_tool_calls'): yield o
|
|
305
319
|
if tcs := _usrtools(res.tool_calls):
|
|
306
|
-
tres = await parallel_async(_alite_call_func, tcs, timeout=tc_timeout, n_workers=n_workers, pause=pause, **self.tcdict)
|
|
320
|
+
tres = await parallel_async(_alite_call_func, tcs, timeout=t.tc_timeout, n_workers=t.n_workers, pause=t.pause, **self.tcdict)
|
|
307
321
|
tmsg = mk_tool_res_msg(tcs, tres)
|
|
308
322
|
for r in tmsg.content: yield r
|
|
309
323
|
self.hist.append(tmsg)
|
|
310
324
|
yield Refresh()
|
|
311
|
-
if step>=max_steps-1 or _has_stop(tmsg.content): self.prompt,tool_choice,search = mk_msg(final_prompt),'none',False
|
|
325
|
+
if step>=t.max_steps-1 or _has_stop(tmsg.content): self.prompt,tool_choice,search = mk_msg(t.final_prompt),'none',False
|
|
312
326
|
self.toolloop = True
|
|
313
327
|
|
|
314
328
|
async for o in self._call_cbs('after_tool_calls'): yield o
|
|
315
|
-
if self.toolloop and step <= max_steps:
|
|
329
|
+
if self.toolloop and step <= t.max_steps:
|
|
316
330
|
try:
|
|
317
|
-
async for result in self._call(
|
|
318
|
-
self.prompt, temp, think, search, stream, max_steps, step+1,
|
|
319
|
-
final_prompt, tool_choice=tool_choice, **kwargs): yield result
|
|
331
|
+
async for result in self._call(self.prompt, step+1, search, tool_choice, **kwargs): yield result
|
|
320
332
|
except ContextWindowExceededError:
|
|
321
333
|
if tmsg is not None:
|
|
322
334
|
for p in tmsg.content:
|
|
323
335
|
if len(p.text)>1000: p.text = _cwe_msg + trunc_str(p.text, mx=1000)
|
|
324
|
-
async for result in self._call(
|
|
325
|
-
self.prompt, temp, think, search, stream, max_steps, step+1,
|
|
326
|
-
final_prompt, tool_choice='none', **kwargs): yield result
|
|
336
|
+
async for result in self._call(self.prompt, step+1, search, 'none', **kwargs): yield result
|
|
327
337
|
|
|
328
338
|
# %% ../nbs/07_chat.ipynb #1361515a
|
|
329
339
|
@patch
|
|
330
|
-
@delegates(
|
|
340
|
+
@delegates(acomplete)
|
|
331
341
|
async def __call__(
|
|
332
342
|
self:AsyncChat,
|
|
333
343
|
msg=None, # Message str, or list of multiple message parts
|
|
334
344
|
temp=None, # Override temp set on chat initialization
|
|
335
|
-
think=None, # Thinking
|
|
345
|
+
think=None, # Thinking effort: l/m/h/x, or the full names
|
|
336
346
|
search=None, # Override search set on chat initialization (l,m,h)
|
|
337
347
|
stream=False, # Stream results
|
|
338
|
-
max_steps=2,
|
|
348
|
+
max_steps=2, # Maximum number of tool calls
|
|
339
349
|
final_prompt=_final_prompt, # Final prompt when tool calls have ran out
|
|
350
|
+
initial_body=None, # Extra body fields sent with the first request only; tool-loop continuations use the standard payload
|
|
340
351
|
return_all=False, # Returns all intermediate ModelResponses if not streaming and has tool calls
|
|
352
|
+
tool_choice=None, # Force ('required'/a tool name) or disable ('none') tool use
|
|
353
|
+
max_tokens=None, # Response token limit (default: the model's maximum)
|
|
354
|
+
n_workers=8, # Max concurrent tool executions
|
|
355
|
+
pause=0.001, # Pause between tool submissions
|
|
356
|
+
tc_timeout=7200, # Per-tool-call timeout in seconds
|
|
341
357
|
**kwargs
|
|
342
358
|
):
|
|
359
|
+
self.response_id,self._response_hist_idx = None,0
|
|
343
360
|
self.use = UsageStats()
|
|
344
361
|
self._turn_start = len(self.hist)
|
|
345
|
-
|
|
362
|
+
self.turn_opts = dict(temp=temp, think=think, stream=stream, max_steps=max_steps, final_prompt=final_prompt,
|
|
363
|
+
max_tokens=max_tokens, n_workers=n_workers, pause=pause, tc_timeout=tc_timeout)
|
|
364
|
+
result_gen = self._call(msg, 1, search, tool_choice, initial_body, **kwargs)
|
|
346
365
|
if stream or return_all: return result_gen
|
|
347
366
|
async for res in result_gen: pass
|
|
348
367
|
return res # normal chat behavior only return last msg
|
|
@@ -406,7 +425,7 @@ _lang2tool = dict(py='python', bash='bash')
|
|
|
406
425
|
def _active_fence_langs(tool_schemas):
|
|
407
426
|
"Return set of active fence langs whose mapped tool is registered"
|
|
408
427
|
if not tool_schemas: return set()
|
|
409
|
-
names = {
|
|
428
|
+
names = {fs[0] for t in tool_schemas if (fs:=fn_schema(t))}
|
|
410
429
|
return {lang for lang, tname in _lang2tool.items() if tname in names}
|
|
411
430
|
|
|
412
431
|
# %% ../nbs/07_chat.ipynb #72274cdc
|
|
@@ -415,7 +434,7 @@ async def run_fence_tool(lang, code, ns):
|
|
|
415
434
|
tname = _lang2tool[lang]
|
|
416
435
|
arg = dict(code=code) if lang == 'py' else dict(cmd=code)
|
|
417
436
|
res = _mk_tool_result(await call_func_async(tname, arg, ns=ns, raise_on_err=False))
|
|
418
|
-
return mk_result_fence(trunc_str(
|
|
437
|
+
return mk_result_fence(trunc_str(tool_text(res)))
|
|
419
438
|
|
|
420
439
|
# %% ../nbs/07_chat.ipynb #740ee3a4
|
|
421
440
|
class FenceToolCallback(ChatCallback):
|