python-fastllm 0.0.27__py3-none-any.whl → 0.0.29__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.27"
1
+ __version__ = "0.0.29"
fastllm/_modidx.py CHANGED
@@ -140,6 +140,7 @@ d = { 'settings': { 'branch': 'main',
140
140
  'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
141
141
  'fastllm.chat.extract_fence_call': ('chat.html#extract_fence_call', 'fastllm/chat.py'),
142
142
  'fastllm.chat.fmt2hist': ('chat.html#fmt2hist', 'fastllm/chat.py'),
143
+ 'fastllm.chat.hist2fmt': ('chat.html#hist2fmt', 'fastllm/chat.py'),
143
144
  'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
144
145
  'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
145
146
  'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
@@ -235,6 +236,8 @@ d = { 'settings': { 'branch': 'main',
235
236
  'fastllm/openai_responses.py'),
236
237
  'fastllm.openai_responses.denorm_user': ( 'oai_responses.html#denorm_user',
237
238
  'fastllm/openai_responses.py'),
239
+ 'fastllm.openai_responses.denorm_video': ( 'oai_responses.html#denorm_video',
240
+ 'fastllm/openai_responses.py'),
238
241
  'fastllm.openai_responses.denorm_web_search': ( 'oai_responses.html#denorm_web_search',
239
242
  'fastllm/openai_responses.py'),
240
243
  'fastllm.openai_responses.get_hdrs': ( 'oai_responses.html#get_hdrs',
fastllm/acomplete.py CHANGED
@@ -39,12 +39,13 @@ vendor_mapping = {
39
39
  "codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN', _codex_json),
40
40
  "moonshot": ('openai_chat', "https://api.moonshot.ai/v1", "MOONSHOT_API_KEY"),
41
41
  "deepseek": ('openai_chat', "https://api.deepseek.com/v1", "DEEPSEEK_API_KEY"),
42
- "mimo": ('openai_chat', "https://api.xiaomimimo.com/v1", "MIMO_API_KEY"),
42
+ "mimo": ('anthropic', "https://api.xiaomimimo.com/anthropic", "MIMO_API_KEY"),
43
43
  "openrouter": ('openai_chat', "https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
44
44
  "together": ('openai_chat', "https://api.together.xyz/v1", "TOGETHER_API_KEY"),
45
45
  "fireworks_ai": ('openai_chat', "https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"),
46
46
  "qwen": ('openai_chat', "https://dashscope.aliyuncs.com/compatible-mode/v1", "QWEN_API_KEY"),
47
- "minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY")
47
+ "minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY"),
48
+ "meta_ai": ('openai', "https://api.meta.ai/v1", "META_API_KEY")
48
49
  }
49
50
 
50
51
  # %% ../nbs/06_acomplete.ipynb #77d27ea7
fastllm/chat.py CHANGED
@@ -9,17 +9,16 @@ __all__ = ['tool_dtls_tag', 're_tools', 'token_dtls_tag', 're_token', 'think_sta
9
9
  'structured', 'StopResponse', 'FullResponse', 'search_count', 'UsageStats', 'AsyncChat',
10
10
  'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback', 'add_warning',
11
11
  'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback', 'stop_sequences',
12
- 'StopSequencesCallback', 'mk_tr_details', 'StreamFormatter', 'AsyncStreamFormatter', 'adisplay_stream']
12
+ 'StopSequencesCallback', 'mk_tr_details', 'hist2fmt', 'StreamFormatter', 'AsyncStreamFormatter',
13
+ 'adisplay_stream']
13
14
 
14
15
  # %% ../nbs/07_chat.ipynb #d5a3bc1f
15
- import asyncio, base64, json, mimetypes, random, string, ast, warnings
16
+ import base64, json
16
17
  from typing import Optional,Callable
17
18
  from html import escape
18
19
  from toolslm.funccall import mk_ns, call_func, call_func_async, get_schema
19
20
  from fastcore.utils import *
20
21
  from fastcore.meta import delegates
21
- from fastcore import imghdr
22
- from fastcore.xml import Safe
23
22
  from dataclasses import dataclass
24
23
 
25
24
  from .types import *
@@ -51,8 +50,10 @@ def _url2content(o):
51
50
  return Part(type=_mime2part_type(mime), text=o.url, data=dict(mime=mime))
52
51
 
53
52
  # %% ../nbs/07_chat.ipynb #48c78e48
54
- def _add_cache_control(msg, # LiteLLM formatted msg
55
- ttl=None): # Cache TTL: '5m' (default) or '1h'
53
+ def _add_cache_control(
54
+ msg, # LiteLLM formatted msg
55
+ ttl=None # Cache TTL: '5m' (default) or '1h'
56
+ ):
56
57
  "cache `msg` with default time-to-live (ttl) of 5minutes ('5m'), but can be set to '1h'."
57
58
  cc = {"type": "ephemeral"} | ({"ttl": ttl} if ttl else {})
58
59
  cache_idx = None
@@ -107,10 +108,10 @@ def mk_msg(
107
108
  # %% ../nbs/07_chat.ipynb #db466e1c
108
109
  tool_dtls_tag = "<details class='tool-usage-details' markdown='1'>"
109
110
  re_tools = re.compile(fr"^({tool_dtls_tag}\n*(?:<summary>(?P<summary>.*?)</summary>\n*)?\n*```json\n+(.*?)\n+```\n+</details>)",
110
- flags=re.DOTALL|re.MULTILINE)
111
+ flags=re.DOTALL|re.MULTILINE)
111
112
  token_dtls_tag = "<details class='token-usage-details' markdown='1'>"
112
113
  re_token = re.compile(fr"^{re.escape(token_dtls_tag)}\n*<summary>.*?</summary>\n*\n*`.*?`\n*\n*</details>\n?",
113
- flags=re.DOTALL|re.MULTILINE)
114
+ flags=re.DOTALL|re.MULTILINE)
114
115
  think_start,think_end = '<!--think_start-->','<!--think_end-->'
115
116
  re_think = re.compile(rf'{re.escape(think_start)}.*?{re.escape(think_end)}\n?', re.DOTALL)
116
117
 
@@ -172,7 +173,7 @@ def _extract_tool_parts(text:str):
172
173
  call = d['call']
173
174
  # Skip server tool calls in deserialization (round trip issues with Gemini/Anthropic)
174
175
  if d.get('server'): return None
175
- tu = Part(type=PartType.tool_use, text=None, data={'id': d['id'], 'name': call['function'], 'arguments': call['arguments']})
176
+ tu = Part(type=PartType.tool_use, text=None, data=dict(id=d['id'], name=call['function'], arguments=call['arguments']))
176
177
  tr = Part(type=PartType.tool_result, text=str(d['result']), data={'id': d['id'], 'name': call['function']})
177
178
  return tu, tr
178
179
 
@@ -193,7 +194,8 @@ def fmt2hist(outp:str)->list[Msg]:
193
194
  if tool_parts:
194
195
  hist.append(Msg(role='assistant', content=asst_parts.copy()))
195
196
  hist.append(Msg(role='tool', content=tool_parts.copy()))
196
- asst_parts.clear(); tool_parts.clear()
197
+ asst_parts.clear()
198
+ tool_parts.clear()
197
199
  for txt,_,tj in split_tools(outp):
198
200
  if txt and txt.strip():
199
201
  if tool_parts: flush()
@@ -229,9 +231,7 @@ def mk_msgs(
229
231
  "Create a list of fastllm canonical Msgs."
230
232
  if not msgs: return []
231
233
  if not isinstance(msgs, list): msgs = [msgs]
232
- msgs = L(msgs).map(lambda m:
233
- fmt2hist(m) if isinstance(m,str) and (tool_dtls_tag in m or token_dtls_tag in m) else [m]
234
- ).concat()
234
+ msgs = L(msgs).map(lambda m: fmt2hist(m) if isinstance(m,str) and (tool_dtls_tag in m or token_dtls_tag in m) else [m]).concat()
235
235
  res, role = [], 'user'
236
236
  for m in msgs:
237
237
  res.append(msg := remove_cache_ckpts(mk_msg(m, role=role)))
@@ -290,10 +290,11 @@ def _lite_call_func(tc, tool_schemas, ns):
290
290
  @delegates(acomplete)
291
291
  async def structured(
292
292
  m:str, # LiteLLM model string
293
- msgs:list, # List of messages
293
+ msgs:list, # List of messages
294
294
  tool:Callable, # Tool to be used for creating the structured output (class, dataclass or Pydantic, function, etc)
295
295
  sp:str|Part='', # System message
296
- **kwargs):
296
+ **kwargs
297
+ ):
297
298
  "Return the value of the tool call (generally used for structured outputs)"
298
299
  t = lite_mk_func(tool)
299
300
  r = await acomplete(msgs, m, system=sp, tools=[t], tool_choice=nested_idx(t, 'function', 'name'), **kwargs)
@@ -317,14 +318,14 @@ def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in
317
318
 
318
319
  # %% ../nbs/07_chat.ipynb #f58ce348
319
320
  def _trunc_str(s, mx=2000, skip=10, replace="TRUNCATED"):
320
- "Truncate `s` to `mx` chars max, adding `replace` if truncated"
321
+ "Truncate `s` to `mx` chars max, adding `replace` if truncated; `mx=None` disables truncation"
321
322
  if not isinstance(s, str): s = str(s)
322
323
  s = type(s)(s.rstrip())
323
324
  if len(s)>2 and s[0]=='𝍁' and s[-1]=='𝍁':
324
325
  s = s[1:-1]
325
326
  if replace: return s
326
327
  if isinstance_str(s, ('FullResponse','Safe','PrettyString')): return s
327
- if len(s)<=mx: return s
328
+ if mx is None or len(s)<=mx: return s
328
329
  s = s[skip:mx-skip]
329
330
  ss = s.split(' ')
330
331
  if len(ss[-1])>150: ss[-1] = ss[-1][:5]
@@ -348,7 +349,8 @@ def search_count(r):
348
349
 
349
350
  # %% ../nbs/07_chat.ipynb #61395e0d
350
351
  class UsageStats:
351
- def __init__(self, model='', prompt_tokens=0, completion_tokens=0, total_tokens=0, cached_tokens=0, cache_creation_tokens=0, reasoning_tokens=0, web_search_requests=0, cost=0.0): store_attr()
352
+ def __init__(self, model='', prompt_tokens=0, completion_tokens=0, total_tokens=0, cached_tokens=0,
353
+ cache_creation_tokens=0, reasoning_tokens=0, web_search_requests=0, cost=0.0): store_attr()
352
354
 
353
355
  @classmethod
354
356
  def from_response(cls, r):
@@ -384,13 +386,13 @@ class UsageStats:
384
386
  class AsyncChat:
385
387
  def __init__(
386
388
  self,
387
- model:str, # LiteLLM compatible model name
389
+ model:str, # LiteLLM compatible model name
388
390
  sp='', # System prompt
389
391
  temp=None, # Temperature
390
392
  search=False, # Search (l,m,h), if model supports it
391
393
  tools:list=None, # Add tools
392
394
  hist:list=None, # Chat history
393
- ns:Optional[dict]=None, # Custom namespace for tool calling
395
+ ns:Optional[dict]=None, # Custom namespace for tool calling
394
396
  cache=False, # Anthropic prompt caching
395
397
  cache_idxs:list=[-1], # Anthropic cache breakpoint idxs, use `0` for sys prompt if provided
396
398
  ttl=None, # Anthropic prompt caching ttl
@@ -421,8 +423,7 @@ class AsyncChat:
421
423
  if sp:
422
424
  if 0 in self.cache_idxs: sp = _add_cache_control(Msg('',[Part(PartType.text, sp)]))
423
425
  cache_idxs = L(self.cache_idxs).filter().map(lambda o: o-1 if o>0 else o)
424
- else:
425
- cache_idxs = self.cache_idxs
426
+ else: cache_idxs = self.cache_idxs
426
427
  if msg: self.hist = self.hist+[msg]
427
428
  self.hist = mk_msgs(self.hist, self.cache and 'claude' in self.model, cache_idxs, self.ttl)
428
429
  msgs = self.hist
@@ -506,7 +507,7 @@ async def astream_with_complete(self, agen, postproc=noop):
506
507
  @patch
507
508
  @delegates(acomplete)
508
509
  async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, search=None, stream=False, max_steps=2, step=1,
509
- final_prompt=None, tool_choice=None, max_tokens=None, n_workers=8, pause=0.001, tc_timeout=7200, **kwargs):
510
+ final_prompt=None, tool_choice=None, max_tokens=None, n_workers=8, pause=0.001, tc_timeout=7200, **kwargs):
510
511
  if step>max_steps+1: return
511
512
  self.prefill, max_tokens = self._prep_call(prefill, search, max_tokens, kwargs, stream=stream, think=think)
512
513
  self.turn_sysp, self.turn_msgs = self._prep_msg(msg, prefill)
@@ -514,9 +515,8 @@ async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, s
514
515
 
515
516
  self.turn_kwargs, self.stream = kwargs, stream
516
517
  async for o in self._call_cbs('before_acomplete'): yield o
517
- res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream,
518
- tools=self.tool_schemas, tool_choice=tool_choice, max_tokens=int(max_tokens),
519
- temperature=None if think else ifnone(temp,self.temp), **self.turn_kwargs)
518
+ res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream, tools=self.tool_schemas,
519
+ tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if think else ifnone(temp,self.temp), **self.turn_kwargs)
520
520
  if stream:
521
521
  if self.prefill: yield _mk_prefill(self.prefill)
522
522
  res = astream_with_complete(res, postproc=postproc)
@@ -565,7 +565,7 @@ async def __call__(
565
565
  search=None, # Override search set on chat initialization (l,m,h)
566
566
  stream=False, # Stream results
567
567
  max_steps=2, # Maximum number of tool calls
568
- final_prompt=_final_prompt, # Final prompt when tool calls have ran out
568
+ final_prompt=_final_prompt, # Final prompt when tool calls have ran out
569
569
  return_all=False, # Returns all intermediate ModelResponses if not streaming and has tool calls
570
570
  **kwargs
571
571
  ):
@@ -593,8 +593,7 @@ class DeepseekMsgsCallback(ChatCallback):
593
593
  async def after_msgs(self):
594
594
  if 'deepseek' not in self.model: return
595
595
  for m in self.turn_msgs:
596
- if m.role=='assistant' and not any(p.type==PartType.thinking for p in m.content):
597
- m.content.append(Part(PartType.thinking, ''))
596
+ if m.role=='assistant' and not any(p.type==PartType.thinking for p in m.content): m.content.append(Part(PartType.thinking, ''))
598
597
  if False: yield
599
598
 
600
599
  # %% ../nbs/07_chat.ipynb #14baac3e
@@ -666,7 +665,9 @@ class FenceToolCallback(ChatCallback):
666
665
  lang, code = fence
667
666
  out = await run_fence_tool(lang, code, self.ns)
668
667
  for p in reversed(m.content):
669
- if p.type == PartType.text: p.text += out; break
668
+ if p.type == PartType.text:
669
+ p.text += out
670
+ break
670
671
  self.chat.toolloop = True
671
672
  if self.stream: yield {'text': out}
672
673
 
@@ -733,14 +734,32 @@ def _trunc_content(content, mx):
733
734
 
734
735
  # %% ../nbs/07_chat.ipynb #3602a033
735
736
  def mk_tr_details(tr, mx=2000):
736
- "Create <details> block for tool call as JSON"
737
- args = {k:_trunc_str(v, mx=mx*5) for k,v in tr.data['arguments'].items()}
737
+ "Create <details> block for tool call as JSON; `mx=None` disables truncation"
738
+ args = {k:_trunc_str(v, mx=None if mx is None else mx*5) if isinstance(v, str) else v for k,v in tr.data['arguments'].items()}
738
739
  res = {'id':tr.data['id'], 'server':tr.data.get('server', False),
739
740
  'call':{'function': tr.data['name'], 'arguments': args},
740
741
  'result':_trunc_content(tr.text, mx=mx),}
741
742
  summ = f"<summary>{_tc_summary(tr)}</summary>"
742
743
  return f"\n\n{tool_dtls_tag}\n{summ}\n\n```json\n{dumps(res, indent=2, ensure_ascii=False)}\n```\n\n</details>\n\n"
743
744
 
745
+ # %% ../nbs/07_chat.ipynb #ae9dfdb5
746
+ def hist2fmt(msgs:list[Msg], mx=2000)->str:
747
+ "Render assistant/tool `msgs` as one formatted output string, the inverse of `fmt2hist`"
748
+ tus, out = {}, []
749
+ for m in msgs:
750
+ if m.role == 'assistant':
751
+ for p in m.content:
752
+ if p.type == PartType.text and p.text: out.append(p.text.strip())
753
+ elif p.type == PartType.tool_use: tus[p.data['id']] = p
754
+ elif m.role == 'tool':
755
+ for p in m.content:
756
+ if p.type != PartType.tool_result: continue
757
+ tu = tus.get(p.data['id'])
758
+ d = dict(p.data, arguments=tu.data.get('arguments', {}) if tu else {})
759
+ out.append(mk_tr_details(Part(type=PartType.tool_result, text=p.text, data=d), mx=mx).strip())
760
+ else: raise ValueError(f"hist2fmt renders assistant and tool messages only, got {m.role!r}")
761
+ return '\n\n'.join(o for o in out if o)
762
+
744
763
  # %% ../nbs/07_chat.ipynb #f0d984ec
745
764
  class StreamFormatter:
746
765
  def __init__(self, mx=2000, debug=False, showthink=False):
@@ -752,7 +771,9 @@ class StreamFormatter:
752
771
  res = ''
753
772
  if self.debug: print(o)
754
773
  is_think = isinstance(o, dict) and o.get('thinking')
755
- if not is_think and self._in_think: res += f'\n\n</details>\n{think_end}\n\n'; self._in_think = False
774
+ if not is_think and self._in_think:
775
+ res += f'\n\n</details>\n{think_end}\n\n'
776
+ self._in_think = False
756
777
  if isinstance(o, dict):
757
778
  if thk:=o.get('thinking'):
758
779
  if self.showthink:
@@ -4,7 +4,8 @@
4
4
  __all__ = ['api_ns', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'norm_finish', 'norm_parts', 'norm_sse_event',
5
5
  'delta_index_fn', 'acollect_stream', 'denorm_tool_use', 'denorm_tool', 'denorm_assistant', 'denorm_msgs',
6
6
  'denorm_tool_schs', 'denorm_tool_choice', 'denorm_reasoning', 'denorm_web_search', 'denorm_system',
7
- 'denorm_user', 'denorm_image', 'denorm_file', 'denorm_tool_result', 'mk_payload', 'get_hdrs', 'cost']
7
+ 'denorm_user', 'denorm_image', 'denorm_video', 'denorm_file', 'denorm_tool_result', 'mk_payload', 'get_hdrs',
8
+ 'cost']
8
9
 
9
10
  # %% ../nbs/02_oai_responses.ipynb #591f55b5
10
11
  import json
@@ -197,13 +198,16 @@ def denorm_user(m:Msg):
197
198
  if p.type == PartType.text: parts.append({"type": "input_text", "text": p.text or ""})
198
199
  elif p.type == PartType.input_image: parts.append(denorm_image(p))
199
200
  elif p.type == PartType.input_audio: raise ValueError("OpenAI Responses API does not support audio input; Coming Soon.")
200
- elif p.type == PartType.input_video: raise ValueError("OpenAI Responses API does not support video input")
201
+ elif p.type == PartType.input_video: parts.append(denorm_video(p))
201
202
  elif p.type == PartType.input_file: parts.append(denorm_file(p))
202
203
  return dict(type='message', role='user', content=parts)
203
204
 
204
205
  # %% ../nbs/02_oai_responses.ipynb #4ced282f
205
206
  def denorm_image(p): return {"type": "input_image", "image_url": p.text}
206
207
 
208
+ # %% ../nbs/02_oai_responses.ipynb #e0ca62d6
209
+ def denorm_video(p): return {"type": "input_video", "video_url": p.text}
210
+
207
211
  # %% ../nbs/02_oai_responses.ipynb #6e79a133
208
212
  def denorm_file(p):
209
213
  if (b64:=data_url(p.text)): return {"type": "input_file", "file_data": p.text, "filename": f"upload.{b64[0].split('/')[-1]}"}
fastllm/types.py CHANGED
@@ -5,11 +5,11 @@
5
5
  # %% auto #0
6
6
  __all__ = ['PartType', 'FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45', 'sonn', 'sonn46', 'sonn5',
7
7
  'opus46', 'opus', 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark',
8
- 'model_info_registry', 'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', 'Part', 'Msg',
9
- 'ToolCall', 'display_list', 'Usage', 'Completion', 'APIRegistry', 'mk_completion', 'mk_tool_res_msg',
10
- 'fn_schema', 'sys_text', 'part_txt', 'data_url', 'url_mime', 'payload_kwargs', 'get_api_key', 'resize_b64',
11
- 'model_prices_meta', 'infer_api_name', 'get_model_meta', 'register_model_info', 'get_model_info',
12
- 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour']
8
+ 'model_info_registry', 'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', 'sol',
9
+ 'terra', 'luna', 'gpt56s', 'Part', 'Msg', 'ToolCall', 'display_list', 'Usage', 'Completion', 'APIRegistry',
10
+ 'mk_completion', 'mk_tool_res_msg', 'fn_schema', 'sys_text', 'part_txt', 'data_url', 'url_mime',
11
+ 'payload_kwargs', 'get_api_key', 'resize_b64', 'model_prices_meta', 'infer_api_name', 'get_model_meta',
12
+ 'register_model_info', 'get_model_info', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour']
13
13
 
14
14
  # %% ../nbs/00_types.ipynb #b4d047fd
15
15
  import httpx, base64, io
@@ -391,6 +391,13 @@ register_model_info('claude-fable-5', vendor_name='anthropic', base="claude-opus
391
391
  input_cost_per_token=10e-6, cache_creation_input_token_cost=12.5e-6, output_cost_per_token=50e-6,
392
392
  cache_read_input_token_cost=1e-6, search_context_cost_per_query=0.005)
393
393
 
394
+ # %% ../nbs/00_types.ipynb #4e1b40b3
395
+ register_model_info('muse-spark-1.1', vendor_name='meta_ai', **modern_llm,
396
+ max_input_tokens=1_048_576, max_output_tokens=128000, max_tokens=128000,
397
+ supports_vision=True, supports_image_input=True, supports_video_input=True, supports_pdf_input=True,
398
+ supports_web_search=True, search_context_cost_per_query=0.0025,
399
+ input_cost_per_token=1.25e-6, output_cost_per_token=4.25e-6, cache_read_input_token_cost=0.15e-6)
400
+
394
401
  # %% ../nbs/00_types.ipynb #2c23d11e
395
402
  codex_pricing = dict(
396
403
  input_cost_per_token = 0.10/1_000_000, output_cost_per_token = 0.50/1_000_000,
@@ -410,6 +417,12 @@ for model in (haik45, sonn45, sonn46, sonn5, opus46, opus):
410
417
  # %% ../nbs/00_types.ipynb #60630540
411
418
  register_model_info(sonn5, base=sonn5, max_input_tokens=760_000)
412
419
 
420
+ # %% ../nbs/00_types.ipynb #bb0c4c2a
421
+ sol,terra,luna = gpt56s = 'gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna'.split()
422
+ for model in ['gpt-5.6']+gpt56s:
423
+ register_model_info(model, 'openai', base=model, base_vendor_name='openai', max_input_tokens=272_000)
424
+ register_model_info(model, 'codex', base=model, base_vendor_name='openai', max_input_tokens=272_000, **codex_pricing)
425
+
413
426
  # %% ../nbs/00_types.ipynb #24cc47ec
414
427
  def get_model_pricing(mn, vendor_name, million=True):
415
428
  return {k:round(v * (1e6 if million else 1), 6)
@@ -422,7 +435,7 @@ def approx_pricing(nm, vendor_name, out=10, cache=80, inp=10, markup=0):
422
435
  p = get_model_pricing(nm, vendor_name)
423
436
  ic = p.get('cache_creation_input_token_cost', p['input_cost_per_token'])
424
437
  res = (p['output_cost_per_token']*out + p['cache_read_input_token_cost']*cache + ic*inp) / (out+cache+inp)
425
- if nm in ('claude-opus-4-7','claude-opus-4-8','claude-fable-5'): res *= 1.5
438
+ if vendor_name=='anthropic': res *= 1.5
426
439
  return res*(1+markup)
427
440
 
428
441
  # %% ../nbs/00_types.ipynb #d2a310fb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.27
3
+ Version: 0.0.29
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=XJ5NZRv5ahaSlYm7_PUNTixULma76xIxwxi2Yy6Eb8w,23
2
- fastllm/_modidx.py,sha256=oxXl2J4lYdszk5QOXtnZiVap0SMTFjL0Z_2Tw24BnVA,34756
3
- fastllm/acomplete.py,sha256=nJRoNvHhb90OslGVU2MXX4ypdH827ebSZzi-GQxQlJQ,9200
1
+ fastllm/__init__.py,sha256=x-mEbDNfu7r2SKAGR0A7P0FwPyhhSxJRlutHkucLsHk,23
2
+ fastllm/_modidx.py,sha256=CSM0Rinlot1qnSPrsvAAyaT-4VdalydB85Nyp9V8eOI,35091
3
+ fastllm/acomplete.py,sha256=XR_wEHBGy0jWXrKbqM1AsnpJlVh8Coq6O5CBBsUNY5Y,9279
4
4
  fastllm/anthropic.py,sha256=OHyazPPsXro3ooS0dBy-tk2g8fwXaAS_Cq-7hnc4E6M,15453
5
- fastllm/chat.py,sha256=iThayCmkZnvz4zJSEpL3ECr21cN33VEOZ-1t8eJAH-w,36531
5
+ fastllm/chat.py,sha256=Dii32SaLou9H-qBIcKXQtFvxT8dLRHNVS00j_-vYBK8,37493
6
6
  fastllm/codex.py,sha256=HZchfrGUgdf8ayhtOFbIRmh9YmIqfQBwqviAEeir4Uo,161
7
7
  fastllm/gemini.py,sha256=Zw6zIsaQ0yht2qNyuntcaNFDPpGPO3sfi6v_izzXd_4,15044
8
8
  fastllm/openai_chat.py,sha256=b3JHumjX-DAxLB02IeKjcUeUkpM7k4SLQ_qdvcfDcwk,10965
9
- fastllm/openai_responses.py,sha256=UTuY10B2Np3FE6zpyHe02U2Wod5EpFIirGV9h9mDJao,13201
9
+ fastllm/openai_responses.py,sha256=5oUtrYtSd4WChkGhwmVC7LilPAguTx0EnpH4lEqYP40,13307
10
10
  fastllm/streaming.py,sha256=mDyaK2JJ5mIjtS0SkAx-iFFL0cN4XkXcdWHaY8w5H3M,7597
11
- fastllm/types.py,sha256=Dlm1lDVVS9e2z32LozWF6swvApJMRghvjHZ877qpMhg,19953
11
+ fastllm/types.py,sha256=K0kg_ZCZXteSgrdhOF_K9S9dgcSKjAUhKUGhxRYrtmY,20790
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.27.dist-info/METADATA,sha256=L89uObIZ2EG6kOJhXw9iRrwOxACz8iO6Vcd3GjF4y1U,19577
19
- python_fastllm-0.0.27.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
20
- python_fastllm-0.0.27.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
- python_fastllm-0.0.27.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
- python_fastllm-0.0.27.dist-info/RECORD,,
18
+ python_fastllm-0.0.29.dist-info/METADATA,sha256=AlueOLo7VXhyfhXJXwzn8IdLC-LMykqcTBLM9Ac0Ekw,19577
19
+ python_fastllm-0.0.29.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
20
+ python_fastllm-0.0.29.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
+ python_fastllm-0.0.29.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
+ python_fastllm-0.0.29.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5