python-fastllm 0.0.43__tar.gz → 0.0.45__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.43/python_fastllm.egg-info → python_fastllm-0.0.45}/PKG-INFO +27 -7
- python_fastllm-0.0.43/PKG-INFO → python_fastllm-0.0.45/README.md +25 -21
- python_fastllm-0.0.45/fastllm/__init__.py +5 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/_modidx.py +54 -1
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/acomplete.py +36 -55
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/anthropic.py +2 -2
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/chat.py +51 -32
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/gemini.py +13 -9
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/openai_chat.py +29 -19
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/openai_responses.py +46 -31
- python_fastllm-0.0.45/fastllm/responses.py +299 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/streaming.py +1 -1
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/types.py +29 -8
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/pyproject.toml +1 -4
- python_fastllm-0.0.43/README.md → python_fastllm-0.0.45/python_fastllm.egg-info/PKG-INFO +41 -5
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/python_fastllm.egg-info/SOURCES.txt +1 -6
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/python_fastllm.egg-info/requires.txt +1 -1
- python_fastllm-0.0.43/fastllm/__init__.py +0 -1
- python_fastllm-0.0.43/fastllm/specs/anthropic.json +0 -1
- python_fastllm-0.0.43/fastllm/specs/anthropic.yml +0 -15684
- python_fastllm-0.0.43/fastllm/specs/gemini.json +0 -6951
- python_fastllm-0.0.43/fastllm/specs/openai.with-code-samples.json +0 -1
- python_fastllm-0.0.43/fastllm/specs/openai.with-code-samples.yml +0 -73650
- python_fastllm-0.0.43/fastllm/specs/spec_manifest.json +0 -17
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.43 → python_fastllm-0.0.45}/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.45
|
|
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
|
|
@@ -11,7 +11,7 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: fastcore>=2.1.18
|
|
13
13
|
Requires-Dist: aidialog>=0.0.10
|
|
14
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: fasttransport>=0.0.2
|
|
15
15
|
Requires-Dist: pillow
|
|
16
16
|
|
|
17
17
|
# fastllm
|
|
@@ -26,8 +26,7 @@ Clone and install locally into your `aai-ws` env
|
|
|
26
26
|
## Setup
|
|
27
27
|
|
|
28
28
|
``` python
|
|
29
|
-
from
|
|
30
|
-
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg
|
|
29
|
+
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
31
30
|
from fastllm.acomplete import acomplete
|
|
32
31
|
import asyncio, json
|
|
33
32
|
|
|
@@ -178,7 +177,9 @@ print("Tool calls:", r1.tool_calls)
|
|
|
178
177
|
```
|
|
179
178
|
|
|
180
179
|
I'll check the current weather in Paris for you.
|
|
181
|
-
|
|
180
|
+
- ⏳ `get_weather(city="Paris")` ⏳
|
|
181
|
+
|
|
182
|
+
Tool calls: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01RMN1WM7vPBT3ovv5Ex6VzC', name='get_weather', arguments={'city': 'Paris'}, server=False, text=None)]
|
|
182
183
|
|
|
183
184
|
``` python
|
|
184
185
|
# Provide the tool result
|
|
@@ -218,8 +219,9 @@ print("\nNo tools: ", end='')
|
|
|
218
219
|
r = await stream([user("What's the weather?")], model='claude-sonnet-4-20250514', tools=tools, tool_choice='none', max_tokens=mtok)
|
|
219
220
|
```
|
|
220
221
|
|
|
222
|
+
- ⏳ `get_weather(city="<UNKNOWN>")` ⏳
|
|
221
223
|
|
|
222
|
-
Forced: [
|
|
224
|
+
Forced: [ToolUse(raw={'caller': {'type': 'direct'}}, cache_control=None, id='toolu_01U7tAXjXAtwjp6xNSMbsyPU', name='get_weather', arguments={'city': '<UNKNOWN>'}, server=False, text=None)]
|
|
223
225
|
|
|
224
226
|
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
227
|
|
|
@@ -281,7 +283,25 @@ r = await stream([user("What is the latest Python release?")], model='gpt-4o-min
|
|
|
281
283
|
print(f"\nServer tools used: {[tc.name for tc in r.tool_calls if tc.server]}")
|
|
282
284
|
```
|
|
283
285
|
|
|
284
|
-
GPT + web search:
|
|
286
|
+
GPT + web search:
|
|
287
|
+
|
|
288
|
+
```json {.tool}
|
|
289
|
+
{
|
|
290
|
+
"id": "ws_0f48f4b49d3a4937006a2c04cb944481a3882056257ae43956",
|
|
291
|
+
"name": "web_search",
|
|
292
|
+
"args": {
|
|
293
|
+
"type": "search",
|
|
294
|
+
"queries": [
|
|
295
|
+
"latest Python release October 2023"
|
|
296
|
+
],
|
|
297
|
+
"query": "latest Python release October 2023"
|
|
298
|
+
},
|
|
299
|
+
"result": "Server tool call executed.",
|
|
300
|
+
"server": true
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
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
305
|
|
|
286
306
|
Python 3.14 introduced several significant features, including:
|
|
287
307
|
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-fastllm
|
|
3
|
-
Version: 0.0.43
|
|
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,17 +3,14 @@
|
|
|
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
|
|
16
|
-
from fastspec.errors import APIError
|
|
12
|
+
from fasttransport.core import AsyncHttpCli
|
|
13
|
+
from fasttransport.errors import APIError
|
|
17
14
|
|
|
18
15
|
from .types import *
|
|
19
16
|
from aidialog.msg_parts import (Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo,
|
|
@@ -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')
|
|
@@ -48,11 +39,7 @@ vendor_mapping = {
|
|
|
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
41
|
"meta_ai": ('openai', "https://api.meta.ai/v1", "META_API_KEY"),
|
|
51
|
-
"zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# %% ../nbs/06_acomplete.ipynb #77d27ea7
|
|
55
|
-
api2spec = {'openai':oai_spec, 'openai_chat':oai_spec, 'anthropic':ant_spec, 'gemini':gem_spec}
|
|
42
|
+
"zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")}
|
|
56
43
|
|
|
57
44
|
# %% ../nbs/06_acomplete.ipynb #e3ed40fb
|
|
58
45
|
def split_vendor(model):
|
|
@@ -97,13 +84,11 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_ur
|
|
|
97
84
|
api_key = get_api_key(api_key, env_api_nm)
|
|
98
85
|
except KeyError: raise ValueError(f"Unknown vendor '{vendor_name}', {err_msg}")
|
|
99
86
|
elif base_url and api_key: vendor_name, api_name = ifnone(vendor_name, 'custom'), ifnone(api_name, 'openai_chat')
|
|
100
|
-
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
|
|
101
88
|
else: raise ValueError(f"Model {model} can't be auto resolved, {err_msg}")
|
|
102
89
|
api = api_registry[api_name]
|
|
103
|
-
|
|
104
|
-
cli =
|
|
105
|
-
if base_url is not None:
|
|
106
|
-
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)
|
|
107
92
|
return cli, api_name, vendor_name
|
|
108
93
|
|
|
109
94
|
# %% ../nbs/06_acomplete.ipynb #df851a5c
|
|
@@ -114,8 +99,8 @@ def _is_ctx_exceeded(code, msg):
|
|
|
114
99
|
if any(x in m for x in ("string_above_max_length", "invalid 'user'")): return False
|
|
115
100
|
if str(code or "").lower() == "context_length_exceeded": return True
|
|
116
101
|
return any(s in m for s in ("exceed context limit", "maximum context length", "maximum context limit",
|
|
117
|
-
|
|
118
|
-
|
|
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"))
|
|
119
104
|
|
|
120
105
|
def _classify_error(exc):
|
|
121
106
|
"Upgrade generic `APIError` to a specific subclass if applicable."
|
|
@@ -135,14 +120,13 @@ async def _classify_error_stream(gen):
|
|
|
135
120
|
# %% ../nbs/06_acomplete.ipynb #f626a4e1
|
|
136
121
|
defaults = SimpleNamespace(debug_mode=False)
|
|
137
122
|
|
|
138
|
-
def _debug_print(model, api_name, vendor_name, payload,
|
|
123
|
+
def _debug_print(model, api_name, vendor_name, payload, url):
|
|
139
124
|
"Pretty-print acomplete inputs when defaults.debug_mode is set"
|
|
140
125
|
from pprint import pformat
|
|
141
126
|
p = dict(payload)
|
|
142
|
-
if defaults.debug_mode == 'brief' and 'tools' in p:
|
|
143
|
-
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'])
|
|
144
128
|
print('━'*60)
|
|
145
|
-
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}")
|
|
146
130
|
print('─'*60)
|
|
147
131
|
print(f"\033[1;33mpayload:\033[0m\n{pformat(p, width=120, sort_dicts=False)}")
|
|
148
132
|
print('━'*60)
|
|
@@ -170,39 +154,36 @@ async def _retry_stream(mk_gen, retries=2, retry_delay=0.5):
|
|
|
170
154
|
|
|
171
155
|
# %% ../nbs/06_acomplete.ipynb #2379ec94
|
|
172
156
|
@delegates(payload_kwargs)
|
|
173
|
-
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
174
|
-
|
|
175
|
-
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):
|
|
176
159
|
"Unified completion across different APIs."
|
|
177
160
|
if not vendor_name and not api_name and not (base_url and api_key):
|
|
178
161
|
v, m = split_vendor(model)
|
|
179
162
|
if v in vendor_mapping: vendor_name, model = v, m
|
|
180
163
|
elif v: api_name, model = v, m # a registered transport api (e.g. claude_code): not an HTTP vendor
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
|
|
185
|
-
async def _mk_gen():
|
|
186
|
-
async for o in api.acollect_stream(payload, model=model, vendor_name='claude_code', stop_callables=stop_callables): yield o
|
|
187
|
-
return _retry_stream(_mk_gen, retries, retry_delay)
|
|
188
|
-
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)
|
|
189
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
|
|
190
172
|
payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
|
|
191
|
-
|
|
192
|
-
if
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
func = attrgetter(api.op_path[stream])(cli)
|
|
199
|
-
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, payload, func)
|
|
200
|
-
async def _call(): return await func(**payload)
|
|
201
|
-
if not stream:
|
|
202
|
-
resp = await _retry_call(_call, retries, retry_delay)
|
|
203
|
-
return mk_completion(resp, model=model, api_name=api_name, vendor_name=vendor_name)
|
|
204
|
-
|
|
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)
|
|
205
180
|
async def _mk_gen():
|
|
206
|
-
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
|
|
207
182
|
async for o in api.acollect_stream(resp, model=model, vendor_name=vendor_name, stop_callables=stop_callables): yield o
|
|
208
|
-
|
|
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)
|
|
@@ -12,7 +12,7 @@ import json
|
|
|
12
12
|
from collections import Counter
|
|
13
13
|
from fastcore.utils import *
|
|
14
14
|
from fastcore.meta import *
|
|
15
|
-
from
|
|
15
|
+
from fasttransport.errors import api_error_from_event
|
|
16
16
|
|
|
17
17
|
from .types import *
|
|
18
18
|
from aidialog.msg_parts import (Part, PartType, mk_part, Msg, Text, Thinking, ToolUse, ToolResult, tool_text, ServerToolResult,
|
|
@@ -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')
|