python-fastllm 0.0.33__tar.gz → 0.0.34__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 (28) hide show
  1. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/PKG-INFO +2 -2
  2. python_fastllm-0.0.34/fastllm/__init__.py +1 -0
  3. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/_modidx.py +4 -5
  4. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/acomplete.py +22 -7
  5. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/chat.py +2 -30
  6. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/streaming.py +1 -1
  7. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/types.py +15 -5
  8. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/pyproject.toml +1 -1
  9. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/PKG-INFO +2 -2
  10. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/requires.txt +1 -1
  11. python_fastllm-0.0.33/fastllm/__init__.py +0 -1
  12. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/README.md +0 -0
  13. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/anthropic.py +0 -0
  14. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/codex.py +0 -0
  15. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/gemini.py +0 -0
  16. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/openai_chat.py +0 -0
  17. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/openai_responses.py +0 -0
  18. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/anthropic.json +0 -0
  19. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/anthropic.yml +0 -0
  20. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/gemini.json +0 -0
  21. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/openai.with-code-samples.json +0 -0
  22. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/openai.with-code-samples.yml +0 -0
  23. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/fastllm/specs/spec_manifest.json +0 -0
  24. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/SOURCES.txt +0 -0
  25. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/dependency_links.txt +0 -0
  26. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/entry_points.txt +0 -0
  27. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/python_fastllm.egg-info/top_level.txt +0 -0
  28. {python_fastllm-0.0.33 → python_fastllm-0.0.34}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.33
3
+ Version: 0.0.34
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
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: fastcore>=2.1.11
13
- Requires-Dist: aidialog>=0.0.3
13
+ Requires-Dist: aidialog>=0.0.5
14
14
  Requires-Dist: fastspec>=0.0.11
15
15
  Requires-Dist: toolslm
16
16
  Requires-Dist: pillow
@@ -0,0 +1 @@
1
+ __version__ = "0.0.34"
@@ -10,6 +10,7 @@ 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'),
13
14
  'fastllm.acomplete._debug_print': ('acomplete.html#_debug_print', 'fastllm/acomplete.py'),
14
15
  'fastllm.acomplete._is_ctx_exceeded': ('acomplete.html#_is_ctx_exceeded', 'fastllm/acomplete.py'),
15
16
  'fastllm.acomplete._raise_if_done': ('acomplete.html#_raise_if_done', 'fastllm/acomplete.py'),
@@ -106,7 +107,6 @@ d = { 'settings': { 'branch': 'main',
106
107
  'fastllm.chat._add_cache_control': ('chat.html#_add_cache_control', 'fastllm/chat.py'),
107
108
  'fastllm.chat._alite_call_func': ('chat.html#_alite_call_func', 'fastllm/chat.py'),
108
109
  'fastllm.chat._apply_cache_idxs': ('chat.html#_apply_cache_idxs', 'fastllm/chat.py'),
109
- 'fastllm.chat._bytes2content': ('chat.html#_bytes2content', 'fastllm/chat.py'),
110
110
  'fastllm.chat._call_func': ('chat.html#_call_func', 'fastllm/chat.py'),
111
111
  'fastllm.chat._handle_stop_reason': ('chat.html#_handle_stop_reason', 'fastllm/chat.py'),
112
112
  'fastllm.chat._has_cache': ('chat.html#_has_cache', 'fastllm/chat.py'),
@@ -114,14 +114,11 @@ d = { 'settings': { 'branch': 'main',
114
114
  'fastllm.chat._has_stop': ('chat.html#_has_stop', 'fastllm/chat.py'),
115
115
  'fastllm.chat._inject_tool_reminder': ('chat.html#_inject_tool_reminder', 'fastllm/chat.py'),
116
116
  'fastllm.chat._lite_call_func': ('chat.html#_lite_call_func', 'fastllm/chat.py'),
117
- 'fastllm.chat._mime2part_type': ('chat.html#_mime2part_type', 'fastllm/chat.py'),
118
- 'fastllm.chat._mk_content': ('chat.html#_mk_content', 'fastllm/chat.py'),
119
117
  'fastllm.chat._mk_prefill': ('chat.html#_mk_prefill', 'fastllm/chat.py'),
120
118
  'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
121
119
  'fastllm.chat._tc_summary': ('chat.html#_tc_summary', 'fastllm/chat.py'),
122
120
  'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
123
121
  'fastllm.chat._trunc_param': ('chat.html#_trunc_param', 'fastllm/chat.py'),
124
- 'fastllm.chat._url2content': ('chat.html#_url2content', 'fastllm/chat.py'),
125
122
  'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
126
123
  'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
127
124
  'fastllm.chat.adisplay_stream': ('chat.html#adisplay_stream', 'fastllm/chat.py'),
@@ -253,7 +250,9 @@ d = { 'settings': { 'branch': 'main',
253
250
  'fastllm.streaming.norm_and_yield': ('streaming.html#norm_and_yield', 'fastllm/streaming.py'),
254
251
  'fastllm.streaming.stop_and_trim': ('streaming.html#stop_and_trim', 'fastllm/streaming.py')},
255
252
  'fastllm.types': { 'fastllm.types.APIRegistry': ('types.html#apiregistry', 'fastllm/types.py'),
256
- 'fastllm.types.APIRegistry.__init__': ('types.html#apiregistry.__init__', 'fastllm/types.py'),
253
+ 'fastllm.types.APIRegistry.__contains__': ('types.html#apiregistry.__contains__', 'fastllm/types.py'),
254
+ 'fastllm.types.APIRegistry.__missing__': ('types.html#apiregistry.__missing__', 'fastllm/types.py'),
255
+ 'fastllm.types.APIRegistry._load': ('types.html#apiregistry._load', 'fastllm/types.py'),
257
256
  'fastllm.types.APIRegistry.register': ('types.html#apiregistry.register', 'fastllm/types.py'),
258
257
  'fastllm.types.Completion': ('types.html#completion', 'fastllm/types.py'),
259
258
  'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
@@ -54,14 +54,29 @@ api2spec = {'openai':oai_spec, 'openai_chat':oai_spec, 'anthropic':ant_spec, 'ge
54
54
 
55
55
  # %% ../nbs/06_acomplete.ipynb #e3ed40fb
56
56
  def split_vendor(model):
57
- "Split a `'vendor/model'` path into `(vendor_name, model)`: the prefix must name a known vendor or a registered api, else `(None, model)`."
57
+ "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)`."
58
58
  if model and '/' in model:
59
59
  v, m = model.split('/', 1)
60
- if v in vendor_mapping or v in api_registry.apis: return v, m
60
+ if v in vendor_mapping or v in api_registry: return v, m
61
61
  return None, model
62
62
 
63
+ # %% ../nbs/06_acomplete.ipynb #977a115b
64
+ def _codex_mtime_policy():
65
+ "`flexicache` policy: expire codex clients when the codex auth file changes"
66
+ def policy(state, args, kwargs):
67
+ v = kwargs.get('vendor_name') or (args[1] if len(args)>1 else None)
68
+ if not v:
69
+ m = kwargs.get('model') or (args[0] if args else None)
70
+ v = split_vendor(m)[0] if m else None
71
+ if v != 'codex': return None
72
+ try: mt = Path(_codex_path).expanduser().stat().st_mtime
73
+ except OSError: mt = None
74
+ return mt if state != mt else None
75
+ policy.needs_args = True
76
+ return policy
77
+
63
78
  # %% ../nbs/06_acomplete.ipynb #79075d95
64
- @flexicache()
79
+ @flexicache(_codex_mtime_policy())
65
80
  def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_url=None, xtra_hdrs=None,
66
81
  timeout=httpx.Timeout(connect=30, read=300, write=30, pool=10)):
67
82
  err_msg = f"please pass a valid one vendor: {', '.join(list(vendor_mapping))} or pass `api_name`,`base_url` and `api_key`"
@@ -82,7 +97,7 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_ur
82
97
  elif base_url and api_key: vendor_name, api_name = ifnone(vendor_name, 'custom'), ifnone(api_name, 'openai_chat')
83
98
  elif (api_name:=infer_api_name(model)): base_url, vendor_name = None, api_name
84
99
  else: raise ValueError(f"Model {model} can't be auto resolved, {err_msg}")
85
- api = api_registry.apis[api_name]
100
+ api = api_registry[api_name]
86
101
  spec, hdrs = api2spec[api_name], api.get_hdrs(api_key)
87
102
  cli = OpenAPIClient(spec, headers=merge(hdrs, ifnone(xtra_hdrs, {})), timeout=timeout)
88
103
  if base_url is not None:
@@ -163,13 +178,13 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
163
178
  elif v: api_name, model = v, m # a registered transport api (e.g. claude_code): not an HTTP vendor
164
179
  if api_name == 'claude_code':
165
180
  if not stream: raise NotImplementedError("claude_code backend supports stream=True only for now")
166
- api = api_registry.apis[api_name]
181
+ api = api_registry[api_name]
167
182
  payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
168
183
  async def _mk_gen():
169
184
  async for o in api.acollect_stream(payload, model=model, vendor_name='claude_code', stop_callables=stop_callables): yield o
170
185
  return _retry_stream(_mk_gen, retries, retry_delay)
171
- cli, api_name, vendor_name = mk_client(model, vendor_name, api_name, api_key, base_url, xtra_hdrs)
172
- api = api_registry.apis[api_name]
186
+ cli, api_name, vendor_name = mk_client(model=model, vendor_name=vendor_name, api_name=api_name, api_key=api_key, base_url=base_url, xtra_hdrs=xtra_hdrs)
187
+ api = api_registry[api_name]
173
188
  payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
174
189
  payload = merge(payload, ifnone(xtra_body, {}))
175
190
  if vendor_name == 'codex':
@@ -15,35 +15,13 @@ from toolslm.funccall import mk_ns, call_func, call_func_async, get_schema
15
15
  from fastcore.utils import *
16
16
  from fastcore.meta import delegates
17
17
  from dataclasses import dataclass
18
- import base64
19
18
 
20
19
  from .types import *
21
- from fastcore.xtras import detect_mime
22
20
  from aidialog.msg_parts import (Msg, Part, PartType, ToolCall, mk_tool_res_msg, mk_tr_details,
23
- tool_info, usage_info, think_start, think_end, StopResponse, display_list, MediaUrl, url_mime, fmt2hist,
24
- _mk_result_fence, _split_fence_msgs, _fence_back, _trunc_str)
21
+ tool_info, usage_info, think_start, think_end, StopResponse, display_list, fmt2hist,
22
+ _mk_content, _mk_result_fence, _split_fence_msgs, _fence_back, _trunc_str)
25
23
  from .acomplete import *
26
24
 
27
- # %% ../nbs/07_chat.ipynb #4a37bae5
28
- def _mime2part_type(mime):
29
- "Map MIME string to canonical PartType"
30
- if mime.startswith('image/'): return PartType.input_image
31
- if mime.startswith('audio/'): return PartType.input_audio
32
- if mime.startswith('video/'): return PartType.input_video
33
- return PartType.input_file
34
-
35
- def _bytes2content(data):
36
- "Convert bytes to fastllm canonical content"
37
- mtype = detect_mime(data)
38
- if not mtype: raise ValueError(f'Data must be a supported file type, got {data[:10]}')
39
- encoded = base64.b64encode(data).decode("utf-8")
40
- return Part(type=_mime2part_type(mtype), text=f'data:{mtype};base64,{encoded}')
41
-
42
- def _url2content(o):
43
- "Convert MediaUrl to fastllm canonical content"
44
- mime = o.mime or url_mime(o.url)
45
- return Part(type=_mime2part_type(mime), text=o.url, data=dict(mime=mime))
46
-
47
25
  # %% ../nbs/07_chat.ipynb #43a55cda
48
26
  def _add_cache_control(
49
27
  msg, # LiteLLM formatted msg
@@ -67,12 +45,6 @@ def remove_cache_ckpts(msg):
67
45
  if part.data: part.data.pop('cache_control', None)
68
46
  return msg
69
47
 
70
- def _mk_content(o):
71
- if isinstance(o, str): return Part(type=PartType.text, text=o)
72
- elif isinstance(o, bytes): return _bytes2content(o)
73
- elif isinstance(o, MediaUrl): return _url2content(o)
74
- return o
75
-
76
48
 
77
49
  # %% ../nbs/07_chat.ipynb #48c78e48
78
50
  def contents(c):
@@ -150,7 +150,7 @@ async def mk_acollect_stream(it, index_fn, model=None, api_name=None, vendor_nam
150
150
  deltas.append(d)
151
151
  part_accum.finalize()
152
152
  tcs = part_accum.tool_calls
153
- if api_name: usg = api_registry.apis[api_name].finalize_usage(usg, part_accum.parts)
153
+ if api_name: usg = api_registry[api_name].finalize_usage(usg, part_accum.parts)
154
154
  if stop: fin = FinishReason.stop
155
155
  fin = FinishReason.tool_calls if fin==FinishReason.stop and any(~L(tcs).attrgot('server')) else fin # recheck tool calls post collation
156
156
  # tool calls and non-anthropic citations are yielded at the end
@@ -13,6 +13,7 @@ __all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45
13
13
 
14
14
  # %% ../nbs/00_types.ipynb #b4d047fd
15
15
  import httpx, base64, io
16
+ from importlib.metadata import entry_points
16
17
  from dataclasses import dataclass, field
17
18
  from datetime import datetime, timezone
18
19
  from fastcore.net import urljson
@@ -74,9 +75,18 @@ def _repr_markdown_(self: Completion):
74
75
  FinishReason = str_enum('finish_reason', 'stop', 'tool_calls', 'length', 'content_filter')
75
76
 
76
77
  # %% ../nbs/00_types.ipynb #fc681c52
77
- class APIRegistry:
78
- def __init__(self): self.apis = {}
79
- def register(self, name, finalize_usage=noop, **kwargs): self.apis[name] = SimpleNamespace(finalize_usage=finalize_usage, **kwargs)
78
+ class APIRegistry(dict):
79
+ _eps = None
80
+ def register(self, name, finalize_usage=noop, **kwargs): self[name] = SimpleNamespace(finalize_usage=finalize_usage, **kwargs)
81
+ def _load(self, name):
82
+ "Import the plugin providing api `name` from the `fastllm.apis` entry-point group; its import registers the api."
83
+ if self._eps is None: self._eps = {o.name:o for o in entry_points(group='fastllm.apis')}
84
+ if name in self._eps: self._eps[name].load()
85
+ return dict.__contains__(self, name)
86
+ def __contains__(self, name): return dict.__contains__(self, name) or self._load(name)
87
+ def __missing__(self, name):
88
+ if self._load(name): return self[name]
89
+ raise KeyError(name)
80
90
 
81
91
  api_registry = APIRegistry()
82
92
 
@@ -85,7 +95,7 @@ api_registry = APIRegistry()
85
95
  def mk_completion(resp, model, api_name, vendor_name):
86
96
  "Normalize an api response into Completion."
87
97
  resp = obj2dict(resp)
88
- api = api_registry.apis[api_name]
98
+ api = api_registry[api_name]
89
99
  tcs = api.norm_tool_calls(resp)
90
100
  parts = api.norm_parts(resp)
91
101
  usg = api.finalize_usage(api.norm_usage(resp), parts)
@@ -314,7 +324,7 @@ def is_deepseek_peak_hour(dt=None):
314
324
  @patch(as_prop=True)
315
325
  def cost(self:Completion):
316
326
  meta = dict2obj(get_model_info(self.model, self.vendor_name))
317
- api = api_registry.apis[self.api_name]
327
+ api = api_registry[self.api_name]
318
328
  if not hasattr(api, 'cost'): raise NotImplementedError(f"API: {self.api_name} doesn't have a registered `cost` function in ns")
319
329
  res = api.cost(self.usage, meta)
320
330
  return res*2 if self.vendor_name=='deepseek' and self.model.startswith('deepseek-v4') and is_deepseek_peak_hour() else res
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "Programming Language :: Python :: 3 :: Only",
17
17
  ]
18
- dependencies = ['fastcore>=2.1.11', 'aidialog>=0.0.3', 'fastspec>=0.0.11', 'toolslm', 'pillow']
18
+ dependencies = ['fastcore>=2.1.11', 'aidialog>=0.0.5', 'fastspec>=0.0.11', 'toolslm', 'pillow']
19
19
 
20
20
  [project.urls]
21
21
  Repository = "https://github.com/AnswerDotAI/fastllm"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.33
3
+ Version: 0.0.34
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
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: fastcore>=2.1.11
13
- Requires-Dist: aidialog>=0.0.3
13
+ Requires-Dist: aidialog>=0.0.5
14
14
  Requires-Dist: fastspec>=0.0.11
15
15
  Requires-Dist: toolslm
16
16
  Requires-Dist: pillow
@@ -1,5 +1,5 @@
1
1
  fastcore>=2.1.11
2
- aidialog>=0.0.3
2
+ aidialog>=0.0.5
3
3
  fastspec>=0.0.11
4
4
  toolslm
5
5
  pillow
@@ -1 +0,0 @@
1
- __version__ = "0.0.33"