python-fastllm 0.0.34__py3-none-any.whl → 0.0.36__py3-none-any.whl

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.
fastllm/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.34"
1
+ __version__ = "0.0.36"
fastllm/_modidx.py CHANGED
@@ -95,7 +95,6 @@ d = { 'settings': { 'branch': 'main',
95
95
  'fastllm.chat.ToolReminderCallback.__init__': ('chat.html#toolremindercallback.__init__', 'fastllm/chat.py'),
96
96
  'fastllm.chat.ToolReminderCallback.after_msgs': ( 'chat.html#toolremindercallback.after_msgs',
97
97
  'fastllm/chat.py'),
98
- 'fastllm.chat.ToolResponse': ('chat.html#toolresponse', 'fastllm/chat.py'),
99
98
  'fastllm.chat.UsageStats': ('chat.html#usagestats', 'fastllm/chat.py'),
100
99
  'fastllm.chat.UsageStats.__add__': ('chat.html#usagestats.__add__', 'fastllm/chat.py'),
101
100
  'fastllm.chat.UsageStats.__init__': ('chat.html#usagestats.__init__', 'fastllm/chat.py'),
@@ -258,6 +257,7 @@ d = { 'settings': { 'branch': 'main',
258
257
  'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
259
258
  'fastllm.types.Completion.cost': ('types.html#completion.cost', 'fastllm/types.py'),
260
259
  'fastllm.types.Usage': ('types.html#usage', 'fastllm/types.py'),
260
+ 'fastllm.types._rm_ctx_tiers': ('types.html#_rm_ctx_tiers', 'fastllm/types.py'),
261
261
  'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
262
262
  'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
263
263
  'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
@@ -269,5 +269,7 @@ d = { 'settings': { 'branch': 'main',
269
269
  'fastllm.types.mk_completion': ('types.html#mk_completion', 'fastllm/types.py'),
270
270
  'fastllm.types.model_prices_meta': ('types.html#model_prices_meta', 'fastllm/types.py'),
271
271
  'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
272
+ 'fastllm.types.price_tier': ('types.html#price_tier', 'fastllm/types.py'),
272
273
  'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
273
- 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py')}}}
274
+ 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
275
+ 'fastllm.types.tier_rate': ('types.html#tier_rate', 'fastllm/types.py')}}}
fastllm/chat.py CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  # %% auto #0
6
6
  __all__ = ['effort', 'remove_cache_ckpts', 'contents', 'stop_reason', 'mk_msg', 'FenceToolStop', 'extract_fence_call', 'mk_msgs',
7
- 'cite_footnote', 'postproc', 'lite_mk_func', 'ToolResponse', 'structured', 'search_count', 'UsageStats',
8
- 'AsyncChat', 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback',
9
- 'add_warning', 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback',
10
- 'stop_sequences', 'StopSequencesCallback', 'StreamFormatter', 'AsyncStreamFormatter', 'adisplay_stream']
7
+ 'cite_footnote', 'postproc', 'lite_mk_func', 'structured', 'search_count', 'UsageStats', 'AsyncChat',
8
+ 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback', 'add_warning',
9
+ 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback', 'stop_sequences',
10
+ 'StopSequencesCallback', 'StreamFormatter', 'AsyncStreamFormatter', 'adisplay_stream']
11
11
 
12
12
  # %% ../nbs/07_chat.ipynb #d5a3bc1f
13
13
  from typing import Optional,Callable
@@ -18,7 +18,7 @@ from dataclasses import dataclass
18
18
 
19
19
  from .types import *
20
20
  from aidialog.msg_parts import (Msg, Part, PartType, ToolCall, mk_tool_res_msg, mk_tr_details,
21
- tool_info, usage_info, think_start, think_end, StopResponse, display_list, fmt2hist,
21
+ tool_info, usage_info, think_start, think_end, StopResponse, ToolResponse, display_list, fmt2hist,
22
22
  _mk_content, _mk_result_fence, _split_fence_msgs, _fence_back, _trunc_str)
23
23
  from .acomplete import *
24
24
 
@@ -141,11 +141,6 @@ def lite_mk_func(f):
141
141
  if isinstance(f, dict): return f
142
142
  return {'type':'function', 'function':get_schema(f, pname='parameters')}
143
143
 
144
- # %% ../nbs/07_chat.ipynb #3e0afa31
145
- @dataclass
146
- class ToolResponse:
147
- content: list[str,str]
148
-
149
144
  # %% ../nbs/07_chat.ipynb #bba6fd58
150
145
  def _mk_tool_result(res):
151
146
  "Unwrap `ToolResponse`, and format tool result message"
fastllm/gemini.py CHANGED
@@ -273,21 +273,18 @@ def get_hdrs(api_key=None):
273
273
  def cost(usage, m):
274
274
  raw = usage.raw
275
275
  prompt_tot = raw.get('promptTokenCount', 0)
276
- tier = '_above_200k_tokens' if prompt_tot > 200_000 else ''
277
- in_rate = m.get(f'input_cost_per_token{tier}') or m.input_cost_per_token
278
- out_rate = m.get(f'output_cost_per_token{tier}') or m.output_cost_per_token
279
- cache_rate = m.get(f'cache_read_input_token_cost{tier}')or m.get('cache_read_input_token_cost', 0)
280
- audio_rate = m.get('input_cost_per_audio_token') # None if not priced separately
281
-
276
+ tier = price_tier(m, prompt_tot)
277
+ in_rate = tier_rate(m, 'input_cost_per_token', tier)
278
+ out_rate = tier_rate(m, 'output_cost_per_token', tier)
279
+ cache_rate = tier_rate(m, 'cache_read_input_token_cost', tier)
280
+ # Models with no `input_cost_per_audio_token` (e.g. Gemini 3 Pro) bill audio at the standard input rate, so it stays in `in_txt`
281
+ audio_rate = m.get('input_cost_per_audio_token')
282
282
  cached = raw.get('cachedContentTokenCount', 0)
283
- # Gemini 3 Pro supports bills audio at the standard input rate (no separate input_cost_per_audio_token key in the metadata)
284
283
  audio = sum(d['tokenCount'] for d in raw.get('promptTokensDetails', []) if d.get('modality')=='AUDIO') if audio_rate else 0
285
284
  in_txt = prompt_tot - cached - audio
286
-
287
285
  thoughts = raw.get('thoughtsTokenCount', 0) or 0
288
286
  cands = raw.get('candidatesTokenCount', 0) or 0
289
287
  reason_rate = m.get('output_cost_per_reasoning_token') or out_rate
290
-
291
288
  cost = in_txt * in_rate + cands * out_rate
292
289
  cost += cached * cache_rate
293
290
  cost += audio * (audio_rate or 0)
@@ -251,9 +251,10 @@ def get_hdrs(api_key=None):
251
251
  def cost(usage, m):
252
252
  raw = usage.raw
253
253
  cached = raw.get('input_tokens_details', {}).get('cached_tokens', 0)
254
- in_txt = raw['input_tokens'] - cached
255
- cost = in_txt * m.input_cost_per_token + raw['output_tokens'] * m.output_cost_per_token
256
- cost += cached * m.get('cache_read_input_token_cost', 0)
254
+ tier = price_tier(m, raw['input_tokens'])
255
+ cost = (raw['input_tokens'] - cached) * tier_rate(m, 'input_cost_per_token', tier)
256
+ cost += raw['output_tokens'] * tier_rate(m, 'output_cost_per_token', tier)
257
+ cost += cached * tier_rate(m, 'cache_read_input_token_cost', tier)
257
258
  return cost
258
259
 
259
260
  # %% ../nbs/02_oai_responses.ipynb #07114b55
fastllm/types.py CHANGED
@@ -9,7 +9,7 @@ __all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45
9
9
  'codex_pricing', 'sol', 'terra', 'luna', 'gpt56s', 'Usage', 'Completion', 'APIRegistry', 'mk_completion',
10
10
  'fn_schema', 'payload_kwargs', 'get_api_key', 'resize_b64', 'model_prices_meta', 'infer_api_name',
11
11
  'get_model_meta', 'register_model_info', 'get_model_info', 'get_model_pricing', 'approx_pricing',
12
- 'is_deepseek_peak_hour']
12
+ 'is_deepseek_peak_hour', 'price_tier', 'tier_rate']
13
13
 
14
14
  # %% ../nbs/00_types.ipynb #b4d047fd
15
15
  import httpx, base64, io
@@ -225,10 +225,11 @@ register_model_info('gemini-3.5-flash', vendor_name='gemini', base='gemini-3-fla
225
225
  input_cost_per_token=1.5e-6, output_cost_per_token=9e-6,
226
226
  output_cost_per_reasoning_token=9e-6, cache_read_input_token_cost=1.5e-7)
227
227
 
228
- for model in ('gpt-5.4', 'gpt-5.4-mini'):
229
- register_model_info(model, vendor_name='openai', base=model, supports_web_search=True, mode=None, max_input_tokens=250000)
228
+ register_model_info('gpt-5.4', vendor_name='openai', base='gpt-5.4', supports_web_search=True, mode=None)
229
+ # Upstream metadata says 1,050,000 input tokens for gpt-5.4-mini, but OpenAI documents a 272k limit
230
+ register_model_info('gpt-5.4-mini', vendor_name='openai', base='gpt-5.4-mini', supports_web_search=True, mode=None, max_input_tokens=272_000)
230
231
 
231
- for model in ('kimi-k2.5', 'kimi-k2.6'):
232
+ for model in ('kimi-k2.5', 'kimi-k2.6' , 'kimi-k3'):
232
233
  register_model_info(model, vendor_name='moonshot', base=f'moonshot/{model}', base_vendor_name=None,
233
234
  supports_reasoning=True, supports_vision=True, supports_assistant_prefill=True)
234
235
 
@@ -241,9 +242,11 @@ register_model_info('accounts/fireworks/models/kimi-k2p5', vendor_name='firework
241
242
  register_model_info('accounts/fireworks/models/kimi-k2p6', vendor_name='fireworks_ai', base='accounts/fireworks/models/kimi-k2p5',
242
243
  supports_reasoning=True, supports_vision=True,
243
244
  input_cost_per_token=0.95e-6, cache_read_input_token_cost=0.16e-6, output_cost_per_token=4.0e-6)
244
-
245
245
  register_model_info('kimi-k2.7-code', vendor_name='moonshot', base='kimi-k2.6',
246
246
  input_cost_per_token=0.95e-6, cache_read_input_token_cost=0.19e-6, output_cost_per_token=4.0e-6)
247
+ register_model_info('kimi-k3', vendor_name='moonshot', base='kimi-k2.6',
248
+ supports_reasoning=True, supports_vision=True, max_input_tokens=1_000_000,
249
+ input_cost_per_token=3e-6, cache_read_input_token_cost=0.30e-6, output_cost_per_token=15.0e-6)
247
250
 
248
251
  # %% ../nbs/00_types.ipynb #948d55d0
249
252
  deepseek_v4_common = dict(**modern_llm, supports_assistant_prefill=True,
@@ -281,8 +284,14 @@ codex_pricing = dict(
281
284
  input_cost_per_token = 0.10/1_000_000, output_cost_per_token = 0.50/1_000_000,
282
285
  cache_creation_input_token_cost = 0.10/1_000_000, cache_read_input_token_cost = 0.10/1_000_000)
283
286
 
287
+ def _rm_ctx_tiers(vendor_name, model):
288
+ "Drop upstream context-tier rates, which would otherwise override flat subscription pricing"
289
+ info = model_info_registry[vendor_name, model]
290
+ for k in [k for k in info if re.search(r'_above_\d+k_tokens', k)]: del info[k]
291
+
284
292
  for model in (codex54, codex54m, codex55):
285
293
  register_model_info(model, 'codex', base=model, base_vendor_name='chatgpt', supports_web_search=True, max_input_tokens=256000, **codex_pricing)
294
+ _rm_ctx_tiers('codex', model)
286
295
 
287
296
  register_model_info(codex53spark, 'codex', **codex_pricing,
288
297
  supports_vision=False, supports_image_input=False, supports_web_search=True, supports_reasoning=True, supports_function_calling=True,
@@ -295,8 +304,12 @@ for model in (haik45, sonn45, sonn46, sonn5, opus46, opus48, opus5, fable5):
295
304
  # %% ../nbs/00_types.ipynb #bb0c4c2a
296
305
  sol,terra,luna = gpt56s = 'gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna'.split()
297
306
  for model in ['gpt-5.6']+gpt56s:
298
- register_model_info(model, 'openai', base=model, base_vendor_name='openai', max_input_tokens=272_000)
299
- register_model_info(model, 'codex', base=model, base_vendor_name='openai', max_input_tokens=272_000, **codex_pricing)
307
+ register_model_info(model, 'openai', base=model, base_vendor_name='openai')
308
+ # Codex serves only the suffixed names; bare `gpt-5.6` is rejected with a ChatGPT account.
309
+ # Its window is smaller than the API's: 371,331 input tokens is accepted and 371,981 is not, on all three.
310
+ for model in gpt56s:
311
+ register_model_info(model, 'codex', base=model, base_vendor_name='openai', max_input_tokens=371_000, **codex_pricing)
312
+ _rm_ctx_tiers('codex', model)
300
313
 
301
314
  # %% ../nbs/00_types.ipynb #24cc47ec
302
315
  def get_model_pricing(mn, vendor_name, million=True):
@@ -320,6 +333,17 @@ def is_deepseek_peak_hour(dt=None):
320
333
  h = dt.hour + dt.minute/60
321
334
  return 1 <= h < 4 or 6 <= h < 10
322
335
 
336
+ # %% ../nbs/00_types.ipynb #4c701619
337
+ def price_tier(meta, n):
338
+ "Suffix of the pricing tier applying to `n` prompt tokens, or '' for base rates."
339
+ o = first(o for k in meta if (o:=re.match(r'input_cost_per_token(_above_(\d+)k_tokens)$', k)))
340
+ return o[1] if o and n>int(o[2])*1000 else ''
341
+
342
+ # %% ../nbs/00_types.ipynb #956d2495
343
+ def tier_rate(meta, key, tier):
344
+ "Rate for `key` at `tier`, falling back to the base key."
345
+ return meta.get(f'{key}{tier}') or meta[key]
346
+
323
347
  # %% ../nbs/00_types.ipynb #8bfca02d
324
348
  @patch(as_prop=True)
325
349
  def cost(self:Completion):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.34
3
+ Version: 0.0.36
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
@@ -1,22 +1,22 @@
1
- fastllm/__init__.py,sha256=IeOAFvenopBWYv9ajaYlgGYiPXMiGT008qnpbTQ0HPU,23
2
- fastllm/_modidx.py,sha256=DguCrc5Sl2pPsLC5n6x6TP_lUWkmgmV87_-C3ZQhlaQ,32033
1
+ fastllm/__init__.py,sha256=oSKhQHo_8dYEVv3A19nCmQysoh4TbOzHl508xX9iHoo,23
2
+ fastllm/_modidx.py,sha256=dNBxF5eGF-gzHMrb9WKNP9-wgEggQstu1ulUZ-D-v50,32249
3
3
  fastllm/acomplete.py,sha256=pfIvpzZJLaHqfsU1BP1iPVHeJVEJyFMcfOAgGSxqrqE,10900
4
4
  fastllm/anthropic.py,sha256=vlfXK9KotK0pUUwilSRMYDYS8qmE8n1ReQVu6fR0J-k,15524
5
- fastllm/chat.py,sha256=VWU8gZLrk4RKZFzuZq5mSnUA8gnjY7n2e_fDWklRrBY,29825
5
+ fastllm/chat.py,sha256=8MYn99uiRDp8dgkD0Mv2zUHiUqlUqcEDQu6m0VBBzcI,29728
6
6
  fastllm/codex.py,sha256=HZchfrGUgdf8ayhtOFbIRmh9YmIqfQBwqviAEeir4Uo,161
7
- fastllm/gemini.py,sha256=DXEKu7tlYJ0MCSHF2RKEjrT0-JDRVgZXmjt4JLYDR1A,15145
7
+ fastllm/gemini.py,sha256=nHPeE_afU8wRjXPN3BZu7syIOfkNXCSpKtRDACsujn8,14999
8
8
  fastllm/openai_chat.py,sha256=mvmQQHgyZm7Cs1K2YQWzZQyXzA6gwximmwbYpAqk5-E,11056
9
- fastllm/openai_responses.py,sha256=nWrGzPAmGauPHE52MeFSyBUu-HMB5dSmaCJR7jigbP0,13415
9
+ fastllm/openai_responses.py,sha256=c32IWfE-G8gyWJeD7GX6UMJRmc6LZSehvbEEVV6Itps,13503
10
10
  fastllm/streaming.py,sha256=K4nCa8w3Zpw3cEAdaKUXlCMn7RElKN5IFfHcLr63zSI,7666
11
- fastllm/types.py,sha256=6b9qYzly5PqK5YgfH8scNvWe4lw4EFfOFEjLYbmWgr8,16746
11
+ fastllm/types.py,sha256=n-X5ulpRjdctRPvFZvFuMqf0xtM7gzzASEwn-H1KiFc,18225
12
12
  fastllm/specs/anthropic.json,sha256=VCgTjM2_HoDpCkeu3q_TCOEZLMHriJZLAG3LnDBAgGM,541035
13
13
  fastllm/specs/anthropic.yml,sha256=3S3NAKdXB1Nwp-Sn9Gmh4tBnwhGGhMO3DXkGqPXPUYs,724122
14
14
  fastllm/specs/gemini.json,sha256=zJGOdvZ2BvCiTENZt0-BDEvNBMl8h6EBmEskle_WBto,309331
15
15
  fastllm/specs/openai.with-code-samples.json,sha256=Kto19AW1u8MfxVDJ4cFVBIdZQOIyy8NWylswo57eABU,1995929
16
16
  fastllm/specs/openai.with-code-samples.yml,sha256=DlcWGdaeP4k7smVjt6UbyehJ-2XGU3rn3nCIBMDRfYU,2553630
17
17
  fastllm/specs/spec_manifest.json,sha256=9tVFwojXFnNqsAxQzCRTP1lgSIM0fXixnrXdv4Cmb0c,653
18
- python_fastllm-0.0.34.dist-info/METADATA,sha256=S_cxXGz4jZQAneOGGAziYYzFANBDk5pv-3HZHfYERJI,19640
19
- python_fastllm-0.0.34.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
20
- python_fastllm-0.0.34.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
- python_fastllm-0.0.34.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
- python_fastllm-0.0.34.dist-info/RECORD,,
18
+ python_fastllm-0.0.36.dist-info/METADATA,sha256=JnxYfq74TdVkDaIvhr30cFnqJBfr5Z-AuAxVxQOUcE4,19640
19
+ python_fastllm-0.0.36.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
20
+ python_fastllm-0.0.36.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
+ python_fastllm-0.0.36.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
+ python_fastllm-0.0.36.dist-info/RECORD,,