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.
Files changed (23) hide show
  1. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/PKG-INFO +12 -12
  2. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/README.md +11 -11
  3. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/__init__.py +1 -1
  4. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/_modidx.py +6 -4
  5. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/acomplete.py +21 -38
  6. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/anthropic.py +41 -44
  7. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/chat.py +50 -55
  8. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/codex.py +13 -0
  9. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/gemini.py +29 -29
  10. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/model_prices.json +40924 -24115
  11. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/openai_chat.py +31 -35
  12. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/openai_responses.py +36 -34
  13. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/responses.py +28 -28
  14. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/types.py +53 -34
  15. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/PKG-INFO +12 -12
  16. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/fastllm/streaming.py +0 -0
  17. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/pyproject.toml +0 -0
  18. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/SOURCES.txt +0 -0
  19. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/dependency_links.txt +0 -0
  20. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/entry_points.txt +0 -0
  21. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/requires.txt +0 -0
  22. {python_fastllm-0.0.63 → python_fastllm-0.0.65}/python_fastllm.egg-info/top_level.txt +0 -0
  23. {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.63
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 = [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {}),
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='claude-sonnet-5', system=sys, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
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='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
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='claude-sonnet-4-6',
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='claude-sonnet-5', system=system, max_tokens=mtok)
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='claude-sonnet-5', system=system, max_tokens=mtok)
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 [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
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 = [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {}),
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='claude-sonnet-5', system=sys, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, max_tokens=mtok)
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='claude-sonnet-5', tools=tools, tool_choice='required', max_tokens=mtok)
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='claude-sonnet-5', tools=tools, tool_choice='none', max_tokens=mtok)
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='claude-sonnet-4-6',
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='claude-sonnet-5', system=system, max_tokens=mtok)
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='claude-sonnet-5', system=system, max_tokens=mtok)
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 [('claude-sonnet-5', {}), ('gpt-4o-mini', {}), ('models/gemini-3-flash-preview', {})]:
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
  ```
@@ -2,4 +2,4 @@
2
2
 
3
3
  - `fastllm.responses`: Provider-independent Responses resources and streaming over FastLLM"""
4
4
 
5
- __version__ = "0.0.63"
5
+ __version__ = "0.0.65"
@@ -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._merge_tokens': ('codex.html#_merge_tokens', 'fastllm/codex.py'),
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/06_acomplete.ipynb.
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/06_acomplete.ipynb #f2f57253
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 codex_auth_path
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/06_acomplete.ipynb #32ee2546
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', _codex_json),
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/06_acomplete.ipynb #e3ed40fb
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/06_acomplete.ipynb #977a115b
55
- def _codex_mtime_policy():
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, *auth_json = vendor_mapping[vendor_name]
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/06_acomplete.ipynb #df851a5c
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/06_acomplete.ipynb #f626a4e1
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/06_acomplete.ipynb #497c8565
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/06_acomplete.ipynb #2379ec94
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: 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)
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 vendor_name == 'codex' and (key := body.get('prompt_cache_key')):
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))