python-fastllm 0.0.63__tar.gz → 0.0.65__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.63 → python_fastllm-0.0.65}/PKG-INFO +12 -12
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/README.md +11 -11
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/__init__.py +1 -1
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/_modidx.py +6 -4
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/acomplete.py +21 -38
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/anthropic.py +41 -44
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/chat.py +50 -55
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/codex.py +13 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/gemini.py +29 -29
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/model_prices.json +40924 -24115
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/openai_chat.py +31 -35
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/openai_responses.py +36 -34
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/responses.py +28 -28
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/types.py +53 -34
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/PKG-INFO +12 -12
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/streaming.py +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/pyproject.toml +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/SOURCES.txt +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/requires.txt +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.63 → python_fastllm-0.0.65}/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.65
|
|
4
4
|
Author-email: Kerem Turgutlu <keremturgutlu@gmail.com>
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/AnswerDotAI/fastllm
|
|
@@ -32,7 +32,7 @@ Run `pip install python-fastllm` or clone from github and install locally.
|
|
|
32
32
|
``` python
|
|
33
33
|
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
34
34
|
from fastllm.acomplete import acomplete
|
|
35
|
-
from fastllm.types import sample_img_url, sample_doms
|
|
35
|
+
from fastllm.types import sample_img_url, sample_doms, sonn, sonn46
|
|
36
36
|
import asyncio, json
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -61,7 +61,7 @@ mtok = 1024
|
|
|
61
61
|
Use the same `acomplete` interface with Claude, GPT, Gemini, or Kimi. Set `model` and any provider-specific routing arguments:
|
|
62
62
|
|
|
63
63
|
``` python
|
|
64
|
-
models = [(
|
|
64
|
+
models = [(sonn, {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {}),
|
|
65
65
|
('accounts/fireworks/models/kimi-k3', dict(vendor_name='fireworks_ai'))]
|
|
66
66
|
for name, kw in models:
|
|
67
67
|
r = await acomplete([user("Translate 'hello' into French. Return only the translation.")],
|
|
@@ -83,7 +83,7 @@ Pass `system` to supply a system prompt. `fastllm` translates it to the provider
|
|
|
83
83
|
sys = "You are a pirate chef. Always respond in pirate speak and mention food. Use one short sentence."
|
|
84
84
|
|
|
85
85
|
print("Claude: ", end='')
|
|
86
|
-
r = await stream([user("What should I do today?")], model=
|
|
86
|
+
r = await stream([user("What should I do today?")], model=sonn, system=sys, max_tokens=mtok)
|
|
87
87
|
|
|
88
88
|
print("Gemini: ", end='')
|
|
89
89
|
r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
|
|
@@ -101,7 +101,7 @@ tools = [{"type": "function", "function": dict(name="get_weather", description="
|
|
|
101
101
|
parameters=dict(type="object", properties={"city": {"type": "string"}}, required=["city"]))}]
|
|
102
102
|
|
|
103
103
|
msgs = [user("What's the weather in Paris?")]
|
|
104
|
-
r1 = await stream(msgs, model=
|
|
104
|
+
r1 = await stream(msgs, model=sonn, tools=tools, max_tokens=mtok)
|
|
105
105
|
print("Tool calls:", r1.tool_calls)
|
|
106
106
|
```
|
|
107
107
|
|
|
@@ -112,7 +112,7 @@ print("Tool calls:", r1.tool_calls)
|
|
|
112
112
|
``` python
|
|
113
113
|
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds']),
|
|
114
114
|
user("Should I bring a jacket? Answer in one short sentence.")]
|
|
115
|
-
r2 = await stream(msgs, model=
|
|
115
|
+
r2 = await stream(msgs, model=sonn, tools=tools, max_tokens=mtok)
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
No, a jacket isn't necessary — it's a mild, sunny 22°C day.
|
|
@@ -122,10 +122,10 @@ r2 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
|
122
122
|
Control whether the model must use tools, can’t use tools, or decides on its own:
|
|
123
123
|
|
|
124
124
|
``` python
|
|
125
|
-
r = await acomplete([user('Hello there!')], model=
|
|
125
|
+
r = await acomplete([user('Hello there!')], model=sonn, tools=tools, tool_choice='required', max_tokens=mtok)
|
|
126
126
|
print("Required:", [tc.name for tc in r.tool_calls])
|
|
127
127
|
|
|
128
|
-
r = await acomplete([user("What's the weather?")], model=
|
|
128
|
+
r = await acomplete([user("What's the weather?")], model=sonn, tools=tools, tool_choice='none', max_tokens=mtok)
|
|
129
129
|
print("None:", r.tool_calls)
|
|
130
130
|
```
|
|
131
131
|
|
|
@@ -138,7 +138,7 @@ Set `reasoning_effort` to `low`, `medium`, or `high` for supported models. The a
|
|
|
138
138
|
|
|
139
139
|
``` python
|
|
140
140
|
print("Claude: ", end='')
|
|
141
|
-
r = await stream([user("What is 127 × 849? Return just the number.")], model=
|
|
141
|
+
r = await stream([user("What is 127 × 849? Return just the number.")], model=sonn46,
|
|
142
142
|
reasoning_effort='low', max_tokens=8192)
|
|
143
143
|
for p in r.message.content:
|
|
144
144
|
if isinstance(p, Thinking): print(f"Thinking excerpt: {p.text[:150]}...")
|
|
@@ -177,11 +177,11 @@ long_ctx = "You are an expert on the solar system. " * 200
|
|
|
177
177
|
system = Text(long_ctx, cache_control={'type': 'ephemeral'})
|
|
178
178
|
|
|
179
179
|
r1 = await acomplete([user("What is Jupiter's mass? Answer in one sentence.")],
|
|
180
|
-
model=
|
|
180
|
+
model=sonn, system=system, max_tokens=mtok)
|
|
181
181
|
print("First call: cache creation tokens", r1.usage.cache_creation_tokens)
|
|
182
182
|
|
|
183
183
|
r2 = await acomplete([user("What is Saturn's mass? Answer in one sentence.")],
|
|
184
|
-
model=
|
|
184
|
+
model=sonn, system=system, max_tokens=mtok)
|
|
185
185
|
print("Second call: cache read tokens", r2.usage.cached_tokens)
|
|
186
186
|
```
|
|
187
187
|
|
|
@@ -195,7 +195,7 @@ Use `InputImage` with a model that supports images. The example sends the same i
|
|
|
195
195
|
``` python
|
|
196
196
|
img_msg = Msg(role='user', content=[InputImage(sample_img_url), Text('List three visible objects, using nouns only.')])
|
|
197
197
|
|
|
198
|
-
for name, kw in [(
|
|
198
|
+
for name, kw in [(sonn, {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
|
|
199
199
|
print(f"{name}: ", end='')
|
|
200
200
|
r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
|
|
201
201
|
```
|
|
@@ -16,7 +16,7 @@ Run `pip install python-fastllm` or clone from github and install locally.
|
|
|
16
16
|
``` python
|
|
17
17
|
from aidialog.msg_parts import Msg, Part, Text, Thinking, ToolUse, InputImage, mk_tool_res_msg, Completion
|
|
18
18
|
from fastllm.acomplete import acomplete
|
|
19
|
-
from fastllm.types import sample_img_url, sample_doms
|
|
19
|
+
from fastllm.types import sample_img_url, sample_doms, sonn, sonn46
|
|
20
20
|
import asyncio, json
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -45,7 +45,7 @@ mtok = 1024
|
|
|
45
45
|
Use the same `acomplete` interface with Claude, GPT, Gemini, or Kimi. Set `model` and any provider-specific routing arguments:
|
|
46
46
|
|
|
47
47
|
``` python
|
|
48
|
-
models = [(
|
|
48
|
+
models = [(sonn, {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {}),
|
|
49
49
|
('accounts/fireworks/models/kimi-k3', dict(vendor_name='fireworks_ai'))]
|
|
50
50
|
for name, kw in models:
|
|
51
51
|
r = await acomplete([user("Translate 'hello' into French. Return only the translation.")],
|
|
@@ -67,7 +67,7 @@ Pass `system` to supply a system prompt. `fastllm` translates it to the provider
|
|
|
67
67
|
sys = "You are a pirate chef. Always respond in pirate speak and mention food. Use one short sentence."
|
|
68
68
|
|
|
69
69
|
print("Claude: ", end='')
|
|
70
|
-
r = await stream([user("What should I do today?")], model=
|
|
70
|
+
r = await stream([user("What should I do today?")], model=sonn, system=sys, max_tokens=mtok)
|
|
71
71
|
|
|
72
72
|
print("Gemini: ", end='')
|
|
73
73
|
r = await stream([user("What should I do today?")], model='models/gemini-3-flash-preview', system=sys, max_tokens=mtok)
|
|
@@ -85,7 +85,7 @@ tools = [{"type": "function", "function": dict(name="get_weather", description="
|
|
|
85
85
|
parameters=dict(type="object", properties={"city": {"type": "string"}}, required=["city"]))}]
|
|
86
86
|
|
|
87
87
|
msgs = [user("What's the weather in Paris?")]
|
|
88
|
-
r1 = await stream(msgs, model=
|
|
88
|
+
r1 = await stream(msgs, model=sonn, tools=tools, max_tokens=mtok)
|
|
89
89
|
print("Tool calls:", r1.tool_calls)
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -96,7 +96,7 @@ print("Tool calls:", r1.tool_calls)
|
|
|
96
96
|
``` python
|
|
97
97
|
msgs += [r1.message, mk_tool_res_msg(r1.tool_calls, ['22°C, sunny with light clouds']),
|
|
98
98
|
user("Should I bring a jacket? Answer in one short sentence.")]
|
|
99
|
-
r2 = await stream(msgs, model=
|
|
99
|
+
r2 = await stream(msgs, model=sonn, tools=tools, max_tokens=mtok)
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
No, a jacket isn't necessary — it's a mild, sunny 22°C day.
|
|
@@ -106,10 +106,10 @@ r2 = await stream(msgs, model='claude-sonnet-5', tools=tools, max_tokens=mtok)
|
|
|
106
106
|
Control whether the model must use tools, can’t use tools, or decides on its own:
|
|
107
107
|
|
|
108
108
|
``` python
|
|
109
|
-
r = await acomplete([user('Hello there!')], model=
|
|
109
|
+
r = await acomplete([user('Hello there!')], model=sonn, tools=tools, tool_choice='required', max_tokens=mtok)
|
|
110
110
|
print("Required:", [tc.name for tc in r.tool_calls])
|
|
111
111
|
|
|
112
|
-
r = await acomplete([user("What's the weather?")], model=
|
|
112
|
+
r = await acomplete([user("What's the weather?")], model=sonn, tools=tools, tool_choice='none', max_tokens=mtok)
|
|
113
113
|
print("None:", r.tool_calls)
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -122,7 +122,7 @@ Set `reasoning_effort` to `low`, `medium`, or `high` for supported models. The a
|
|
|
122
122
|
|
|
123
123
|
``` python
|
|
124
124
|
print("Claude: ", end='')
|
|
125
|
-
r = await stream([user("What is 127 × 849? Return just the number.")], model=
|
|
125
|
+
r = await stream([user("What is 127 × 849? Return just the number.")], model=sonn46,
|
|
126
126
|
reasoning_effort='low', max_tokens=8192)
|
|
127
127
|
for p in r.message.content:
|
|
128
128
|
if isinstance(p, Thinking): print(f"Thinking excerpt: {p.text[:150]}...")
|
|
@@ -161,11 +161,11 @@ long_ctx = "You are an expert on the solar system. " * 200
|
|
|
161
161
|
system = Text(long_ctx, cache_control={'type': 'ephemeral'})
|
|
162
162
|
|
|
163
163
|
r1 = await acomplete([user("What is Jupiter's mass? Answer in one sentence.")],
|
|
164
|
-
model=
|
|
164
|
+
model=sonn, system=system, max_tokens=mtok)
|
|
165
165
|
print("First call: cache creation tokens", r1.usage.cache_creation_tokens)
|
|
166
166
|
|
|
167
167
|
r2 = await acomplete([user("What is Saturn's mass? Answer in one sentence.")],
|
|
168
|
-
model=
|
|
168
|
+
model=sonn, system=system, max_tokens=mtok)
|
|
169
169
|
print("Second call: cache read tokens", r2.usage.cached_tokens)
|
|
170
170
|
```
|
|
171
171
|
|
|
@@ -179,7 +179,7 @@ Use `InputImage` with a model that supports images. The example sends the same i
|
|
|
179
179
|
``` python
|
|
180
180
|
img_msg = Msg(role='user', content=[InputImage(sample_img_url), Text('List three visible objects, using nouns only.')])
|
|
181
181
|
|
|
182
|
-
for name, kw in [(
|
|
182
|
+
for name, kw in [(sonn, {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
|
|
183
183
|
print(f"{name}: ", end='')
|
|
184
184
|
r = await stream([img_msg], model=name, max_tokens=mtok, **kw)
|
|
185
185
|
```
|
|
@@ -10,7 +10,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
10
10
|
'fastllm.acomplete._classify_error': ('acomplete.html#_classify_error', 'fastllm/acomplete.py'),
|
|
11
11
|
'fastllm.acomplete._classify_error_stream': ( 'acomplete.html#_classify_error_stream',
|
|
12
12
|
'fastllm/acomplete.py'),
|
|
13
|
-
'fastllm.acomplete._codex_mtime_policy': ('acomplete.html#_codex_mtime_policy', 'fastllm/acomplete.py'),
|
|
14
13
|
'fastllm.acomplete._debug_print': ('acomplete.html#_debug_print', 'fastllm/acomplete.py'),
|
|
15
14
|
'fastllm.acomplete._is_ctx_exceeded': ('acomplete.html#_is_ctx_exceeded', 'fastllm/acomplete.py'),
|
|
16
15
|
'fastllm.acomplete._raise_if_done': ('acomplete.html#_raise_if_done', 'fastllm/acomplete.py'),
|
|
@@ -112,7 +111,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
112
111
|
'fastllm.chat._inject_tool_reminder': ('chat.html#_inject_tool_reminder', 'fastllm/chat.py'),
|
|
113
112
|
'fastllm.chat._lite_call_func': ('chat.html#_lite_call_func', 'fastllm/chat.py'),
|
|
114
113
|
'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
|
|
115
|
-
'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
|
|
116
114
|
'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
|
|
117
115
|
'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
|
|
118
116
|
'fastllm.chat.adisplay_stream': ('chat.html#adisplay_stream', 'fastllm/chat.py'),
|
|
@@ -126,7 +124,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
126
124
|
'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
|
|
127
125
|
'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py'),
|
|
128
126
|
'fastllm.chat.think_sep': ('chat.html#think_sep', 'fastllm/chat.py')},
|
|
129
|
-
'fastllm.codex': { 'fastllm.codex.
|
|
127
|
+
'fastllm.codex': { 'fastllm.codex._codex_hdrs': ('codex.html#_codex_hdrs', 'fastllm/codex.py'),
|
|
128
|
+
'fastllm.codex._codex_vendor_auth': ('codex.html#_codex_vendor_auth', 'fastllm/codex.py'),
|
|
129
|
+
'fastllm.codex._merge_tokens': ('codex.html#_merge_tokens', 'fastllm/codex.py'),
|
|
130
130
|
'fastllm.codex._post': ('codex.html#_post', 'fastllm/codex.py'),
|
|
131
131
|
'fastllm.codex._token_grant': ('codex.html#_token_grant', 'fastllm/codex.py'),
|
|
132
132
|
'fastllm.codex.codex_auth': ('codex.html#codex_auth', 'fastllm/codex.py'),
|
|
@@ -373,10 +373,11 @@ d = { 'settings': { 'branch': 'main',
|
|
|
373
373
|
'fastllm.types.Usage.__add__': ('types.html#usage.__add__', 'fastllm/types.py'),
|
|
374
374
|
'fastllm.types.Usage.__eq__': ('types.html#usage.__eq__', 'fastllm/types.py'),
|
|
375
375
|
'fastllm.types.Usage.__init__': ('types.html#usage.__init__', 'fastllm/types.py'),
|
|
376
|
-
'fastllm.types._flat_rates': ('types.html#_flat_rates', 'fastllm/types.py'),
|
|
377
376
|
'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
|
|
378
377
|
'fastllm.types.effort_code': ('types.html#effort_code', 'fastllm/types.py'),
|
|
378
|
+
'fastllm.types.effort_kwargs': ('types.html#effort_kwargs', 'fastllm/types.py'),
|
|
379
379
|
'fastllm.types.effort_levels': ('types.html#effort_levels', 'fastllm/types.py'),
|
|
380
|
+
'fastllm.types.find_models': ('types.html#find_models', 'fastllm/types.py'),
|
|
380
381
|
'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
|
|
381
382
|
'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
|
|
382
383
|
'fastllm.types.get_model_info': ('types.html#get_model_info', 'fastllm/types.py'),
|
|
@@ -390,6 +391,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
390
391
|
'fastllm.types.price_tier': ('types.html#price_tier', 'fastllm/types.py'),
|
|
391
392
|
'fastllm.types.provider_req': ('types.html#provider_req', 'fastllm/types.py'),
|
|
392
393
|
'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
|
|
394
|
+
'fastllm.types.register_sub_model': ('types.html#register_sub_model', 'fastllm/types.py'),
|
|
393
395
|
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
|
|
394
396
|
'fastllm.types.resolve_effort': ('types.html#resolve_effort', 'fastllm/types.py'),
|
|
395
397
|
'fastllm.types.tier_rate': ('types.html#tier_rate', 'fastllm/types.py'),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""A unified async API for model completions and streaming"""
|
|
2
2
|
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_acomplete.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
6
|
__all__ = ['vendor_mapping', 'defaults', 'split_vendor', 'mk_client', 'ContextWindowExceededError', 'acomplete']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/
|
|
8
|
+
# %% ../nbs/03_acomplete.ipynb #f2f57253
|
|
9
9
|
import asyncio,json,httpx2
|
|
10
10
|
from fastcore.utils import *
|
|
11
11
|
from fastcore.meta import *
|
|
@@ -13,7 +13,7 @@ from fasttransport.core import AsyncHttpCli
|
|
|
13
13
|
from fasttransport.errors import APIError
|
|
14
14
|
|
|
15
15
|
from .types import *
|
|
16
|
-
from .codex import
|
|
16
|
+
from .codex import *
|
|
17
17
|
from aidialog.msg_parts import (Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo,
|
|
18
18
|
InputFile, mk_tool_res_msg, Completion)
|
|
19
19
|
from .streaming import *
|
|
@@ -22,15 +22,13 @@ from .openai_chat import *
|
|
|
22
22
|
from .anthropic import *
|
|
23
23
|
from .gemini import *
|
|
24
24
|
|
|
25
|
-
# %% ../nbs/
|
|
26
|
-
_codex_path = codex_auth_path()
|
|
27
|
-
_codex_json = _codex_path, ('tokens','access_token')
|
|
25
|
+
# %% ../nbs/03_acomplete.ipynb #32ee2546
|
|
28
26
|
vendor_mapping = {
|
|
29
27
|
"openai": ('openai', 'https://api.openai.com/v1', 'OPENAI_API_KEY'),
|
|
30
28
|
"anthropic": ('anthropic', 'https://api.anthropic.com', 'ANTHROPIC_API_KEY'),
|
|
31
29
|
"gemini": ('gemini', 'https://generativelanguage.googleapis.com/', 'GEMINI_API_KEY'),
|
|
32
30
|
"openai_chat": ('openai_chat', 'https://api.openai.com/v1', 'OPENAI_API_KEY'),
|
|
33
|
-
"codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN'
|
|
31
|
+
"codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN'),
|
|
34
32
|
"moonshot": ('openai_chat', "https://api.moonshot.ai/v1", "MOONSHOT_API_KEY"),
|
|
35
33
|
"deepseek": ('openai_chat', "https://api.deepseek.com/v1", "DEEPSEEK_API_KEY"),
|
|
36
34
|
"inception": ('openai_chat', "https://api.inceptionlabs.ai/v1", "INCEPTION_API_KEY"),
|
|
@@ -43,7 +41,7 @@ vendor_mapping = {
|
|
|
43
41
|
"meta": ('openai_chat', "https://api.meta.ai/v1", "META_API_KEY"),
|
|
44
42
|
"zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")}
|
|
45
43
|
|
|
46
|
-
# %% ../nbs/
|
|
44
|
+
# %% ../nbs/03_acomplete.ipynb #e3ed40fb
|
|
47
45
|
def split_vendor(model):
|
|
48
46
|
"Split a `'vendor/model'` path into `(vendor_name, model)`: the prefix must name a known vendor or a registered api (installed `fastllm.apis` plugins load on demand), else `(None, model)`."
|
|
49
47
|
if model and '/' in model:
|
|
@@ -51,23 +49,8 @@ def split_vendor(model):
|
|
|
51
49
|
if v in vendor_mapping or v in api_registry: return v, m
|
|
52
50
|
return None, model
|
|
53
51
|
|
|
54
|
-
# %% ../nbs/
|
|
55
|
-
|
|
56
|
-
"`flexicache` policy: expire codex clients when the codex auth file changes"
|
|
57
|
-
def policy(state, args, kwargs):
|
|
58
|
-
v = kwargs.get('vendor_name') or (args[1] if len(args)>1 else None)
|
|
59
|
-
if not v:
|
|
60
|
-
m = kwargs.get('model') or (args[0] if args else None)
|
|
61
|
-
v = split_vendor(m)[0] if m else None
|
|
62
|
-
if v != 'codex': return None
|
|
63
|
-
try: mt = Path(_codex_path).expanduser().stat().st_mtime
|
|
64
|
-
except OSError: mt = None
|
|
65
|
-
return mt if state != mt else None
|
|
66
|
-
policy.needs_args = True
|
|
67
|
-
return policy
|
|
68
|
-
|
|
69
|
-
# %% ../nbs/06_acomplete.ipynb #79075d95
|
|
70
|
-
@flexicache(_codex_mtime_policy())
|
|
52
|
+
# %% ../nbs/03_acomplete.ipynb #79075d95
|
|
53
|
+
@flexicache()
|
|
71
54
|
def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_token=None, base_url=None, xtra_hdrs=None,
|
|
72
55
|
timeout=httpx2.Timeout(connect=30, read=300, write=30, pool=10)):
|
|
73
56
|
err_msg = f"please pass a valid one vendor: {', '.join(list(vendor_mapping))} or pass `api_name`,`base_url` and `api_key`"
|
|
@@ -77,12 +60,8 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_t
|
|
|
77
60
|
if vendor_name:
|
|
78
61
|
override_base_url = base_url
|
|
79
62
|
try:
|
|
80
|
-
api_name, base_url, env_api_nm
|
|
63
|
+
api_name, base_url, env_api_nm = vendor_mapping[vendor_name]
|
|
81
64
|
base_url = override_base_url or base_url
|
|
82
|
-
if auth_json and not api_key and not oauth_token and not os.getenv(env_api_nm):
|
|
83
|
-
fn,keys = auth_json[0] # pyright: ignore[reportAssignmentType]
|
|
84
|
-
auth_fn = Path(fn).expanduser()
|
|
85
|
-
if auth_fn.exists(): api_key = nested_idx(json.loads(auth_fn.read_text()), *keys)
|
|
86
65
|
if not oauth_token: api_key = get_api_key(api_key, env_api_nm)
|
|
87
66
|
except KeyError: raise ValueError(f"Unknown vendor '{vendor_name}', {err_msg}")
|
|
88
67
|
elif base_url and (api_key or oauth_token): vendor_name, api_name = ifnone(vendor_name, 'custom'), ifnone(api_name, 'openai_chat')
|
|
@@ -93,7 +72,7 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_t
|
|
|
93
72
|
cli = AsyncHttpCli(base_url, base_headers=hdrs, timeout=timeout)
|
|
94
73
|
return cli, api_name, vendor_name
|
|
95
74
|
|
|
96
|
-
# %% ../nbs/
|
|
75
|
+
# %% ../nbs/03_acomplete.ipynb #df851a5c
|
|
97
76
|
class ContextWindowExceededError(APIError): pass
|
|
98
77
|
|
|
99
78
|
def _is_ctx_exceeded(code, msg):
|
|
@@ -119,8 +98,8 @@ async def _classify_error_stream(gen):
|
|
|
119
98
|
async for x in gen: yield x
|
|
120
99
|
except APIError as e: raise _classify_error(e) from e
|
|
121
100
|
|
|
122
|
-
# %% ../nbs/
|
|
123
|
-
defaults = SimpleNamespace(debug_mode=False)
|
|
101
|
+
# %% ../nbs/03_acomplete.ipynb #f626a4e1
|
|
102
|
+
defaults = SimpleNamespace(debug_mode=False, reasoning_effort=None)
|
|
124
103
|
|
|
125
104
|
def _debug_print(model, api_name, vendor_name, payload, url):
|
|
126
105
|
"Pretty-print acomplete inputs when defaults.debug_mode is set"
|
|
@@ -133,7 +112,7 @@ def _debug_print(model, api_name, vendor_name, payload, url):
|
|
|
133
112
|
print(f"\033[1;33mpayload:\033[0m\n{pformat(p, width=120, sort_dicts=False)}")
|
|
134
113
|
print('━'*60)
|
|
135
114
|
|
|
136
|
-
# %% ../nbs/
|
|
115
|
+
# %% ../nbs/03_acomplete.ipynb #497c8565
|
|
137
116
|
async def _raise_if_done(e, n, retries, retry_delay, yielded=False):
|
|
138
117
|
e = _classify_error(e)
|
|
139
118
|
if yielded or not e.retryable or n == retries: raise e
|
|
@@ -154,7 +133,7 @@ async def _retry_stream(mk_gen, retries=2, retry_delay=0.5):
|
|
|
154
133
|
return
|
|
155
134
|
except APIError as e: await _raise_if_done(e, n, retries, retry_delay, yielded=yielded)
|
|
156
135
|
|
|
157
|
-
# %% ../nbs/
|
|
136
|
+
# %% ../nbs/03_acomplete.ipynb #2379ec94
|
|
158
137
|
@delegates(payload_kwargs)
|
|
159
138
|
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None, oauth_token=None, base_url=None, endpoint=None, xtra_body=None, xtra_hdrs=None,
|
|
160
139
|
stream=False, previous_response_id=None, stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
|
|
@@ -168,7 +147,12 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
168
147
|
if own:
|
|
169
148
|
vendor_name = ifnone(vendor_name, api_name) # an own-transport api: no HTTP client to build
|
|
170
149
|
if oauth_token: kwargs['oauth_token'] = oauth_token # its `mk_payload` applies the user's token itself
|
|
171
|
-
else:
|
|
150
|
+
else:
|
|
151
|
+
auth = vendor_auth.get(vendor_name)
|
|
152
|
+
if auth and not (api_key or oauth_token or os.getenv(vendor_mapping[vendor_name][2])): api_key = await auth()
|
|
153
|
+
cli, api_name, vendor_name = mk_client(model=model, vendor_name=vendor_name, api_name=api_name, api_key=api_key, oauth_token=oauth_token, base_url=base_url)
|
|
154
|
+
if (eff := defaults.reasoning_effort) and 'reasoning_effort' not in kwargs and effort_levels(get_model_info(model, vendor_name)):
|
|
155
|
+
kwargs |= effort_kwargs(model, eff, vendor_name)
|
|
172
156
|
api = api_registry[api_name]
|
|
173
157
|
if previous_response_id is not None:
|
|
174
158
|
if not getattr(api, 'supports_previous_response_id', False):
|
|
@@ -179,8 +163,7 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
179
163
|
body = payload | ifnone(xtra_body, {})
|
|
180
164
|
if not own:
|
|
181
165
|
headers = xtra_hdrs or {}
|
|
182
|
-
if
|
|
183
|
-
if not any(k.lower() == 'session-id' for k in headers): headers = {**headers, 'session-id':key}
|
|
166
|
+
if hk := vendor_hdrs.get(vendor_name): headers = hk(body, headers)
|
|
184
167
|
ep = endpoint or api.endpoint
|
|
185
168
|
path,params = ep(model, stream) if callable(ep) else (ep, None)
|
|
186
169
|
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, body, None if own else cli._url(path))
|