python-fastllm 0.0.22__tar.gz → 0.0.24__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.22 → python_fastllm-0.0.24}/PKG-INFO +1 -1
  2. python_fastllm-0.0.24/fastllm/__init__.py +1 -0
  3. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/acomplete.py +7 -1
  4. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/chat.py +28 -17
  5. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/types.py +4 -0
  6. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/PKG-INFO +1 -1
  7. python_fastllm-0.0.22/fastllm/__init__.py +0 -1
  8. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/README.md +0 -0
  9. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/_modidx.py +0 -0
  10. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/anthropic.py +0 -0
  11. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/codex.py +0 -0
  12. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/gemini.py +0 -0
  13. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/openai_chat.py +0 -0
  14. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/openai_responses.py +0 -0
  15. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/anthropic.json +0 -0
  16. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/anthropic.yml +0 -0
  17. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/gemini.json +0 -0
  18. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/openai.with-code-samples.json +0 -0
  19. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/openai.with-code-samples.yml +0 -0
  20. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/specs/spec_manifest.json +0 -0
  21. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/fastllm/streaming.py +0 -0
  22. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/pyproject.toml +0 -0
  23. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/SOURCES.txt +0 -0
  24. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/dependency_links.txt +0 -0
  25. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/entry_points.txt +0 -0
  26. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/requires.txt +0 -0
  27. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/python_fastllm.egg-info/top_level.txt +0 -0
  28. {python_fastllm-0.0.22 → python_fastllm-0.0.24}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.22
3
+ Version: 0.0.24
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
@@ -0,0 +1 @@
1
+ __version__ = "0.0.24"
@@ -144,6 +144,13 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
144
144
  base_url=None, xtra_body=None, xtra_hdrs=None, stream=False,
145
145
  stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
146
146
  "Unified completion across different APIs."
147
+ if api_name == 'claude_code':
148
+ if not stream: raise NotImplementedError("claude_code backend supports stream=True only for now")
149
+ api = api_registry.apis[api_name]
150
+ payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
151
+ async def _mk_gen():
152
+ async for o in api.acollect_stream(payload, model=model, vendor_name='claude_code', stop_callables=stop_callables): yield o
153
+ return _retry_stream(_mk_gen, retries, retry_delay)
147
154
  cli, api_name, vendor_name = mk_client(model, vendor_name, api_name, api_key, base_url, xtra_hdrs)
148
155
  api = api_registry.apis[api_name]
149
156
  payload = api.mk_payload(msgs, model, stream=stream, **kwargs)
@@ -164,5 +171,4 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
164
171
  async def _mk_gen():
165
172
  resp = await _call()
166
173
  async for o in api.acollect_stream(resp, model=model, vendor_name=vendor_name, stop_callables=stop_callables): yield o
167
-
168
174
  return _retry_stream(_mk_gen, retries, retry_delay)
@@ -3,13 +3,13 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/07_chat.ipynb.
4
4
 
5
5
  # %% auto #0
6
- __all__ = ['tool_dtls_tag', 're_tools', 'token_dtls_tag', 're_token', 'effort', 'MediaUrl', 'remove_cache_ckpts', 'contents',
7
- 'stop_reason', 'mk_msg', 'FenceToolStop', 'extract_fence_call', 'split_tools', 'fmt2hist', 'mk_msgs',
8
- 'cite_footnote', 'postproc', 'lite_mk_func', 'ToolResponse', 'structured', 'StopResponse', 'FullResponse',
9
- 'search_count', 'UsageStats', 'AsyncChat', 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback',
10
- 'DeepseekPrefillCallback', 'add_warning', 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback',
11
- 'ToolReminderCallback', 'stop_sequences', 'StopSequencesCallback', 'mk_tr_details', 'StreamFormatter',
12
- 'AsyncStreamFormatter', 'adisplay_stream']
6
+ __all__ = ['tool_dtls_tag', 're_tools', 'token_dtls_tag', 're_token', 'think_start', 'think_end', 're_think', 'effort',
7
+ 'MediaUrl', 'remove_cache_ckpts', 'contents', 'stop_reason', 'mk_msg', 'FenceToolStop', 'extract_fence_call',
8
+ 'split_tools', 'fmt2hist', 'mk_msgs', 'cite_footnote', 'postproc', 'lite_mk_func', 'ToolResponse',
9
+ 'structured', 'StopResponse', 'FullResponse', 'search_count', 'UsageStats', 'AsyncChat',
10
+ 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback', 'add_warning',
11
+ 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback', 'stop_sequences',
12
+ 'StopSequencesCallback', 'mk_tr_details', 'StreamFormatter', 'AsyncStreamFormatter', 'adisplay_stream']
13
13
 
14
14
  # %% ../nbs/07_chat.ipynb #d5a3bc1f
15
15
  import asyncio, base64, json, mimetypes, random, string, ast, warnings
@@ -111,6 +111,8 @@ re_tools = re.compile(fr"^({tool_dtls_tag}\n*(?:<summary>(?P<summary>.*?)</summa
111
111
  token_dtls_tag = "<details class='token-usage-details' markdown='1'>"
112
112
  re_token = re.compile(fr"^{re.escape(token_dtls_tag)}\n*<summary>.*?</summary>\n*\n*`.*?`\n*\n*</details>\n?",
113
113
  flags=re.DOTALL|re.MULTILINE)
114
+ think_start,think_end = '<!--think_start-->','<!--think_end-->'
115
+ re_think = re.compile(rf'{re.escape(think_start)}.*?{re.escape(think_end)}\n?', re.DOTALL)
114
116
 
115
117
  # %% ../nbs/07_chat.ipynb #be998131
116
118
  _fence_back = '`````'
@@ -182,6 +184,7 @@ def split_tools(s):
182
184
  def fmt2hist(outp:str)->list[Msg]:
183
185
  "Transform a formatted output string into fastllm canonical Msgs"
184
186
  if token_dtls_tag in outp: outp = re_token.sub('', outp)
187
+ if think_start in outp: outp = re_think.sub('', outp)
185
188
  if tool_dtls_tag not in outp:
186
189
  msg = Msg(role='assistant', content=[Part(type=PartType.text, text=outp.strip() or '.')])
187
190
  return _split_msg_on_fences(msg)
@@ -345,13 +348,13 @@ def search_count(r):
345
348
 
346
349
  # %% ../nbs/07_chat.ipynb #61395e0d
347
350
  class UsageStats:
348
- def __init__(self, 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()
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()
349
352
 
350
353
  @classmethod
351
354
  def from_response(cls, r):
352
355
  u = r.usage
353
356
  return cls(
354
- prompt_tokens=u.prompt_tokens or 0, completion_tokens=u.completion_tokens or 0, total_tokens=u.total_tokens or 0,
357
+ model=r.model or '', prompt_tokens=u.prompt_tokens or 0, completion_tokens=u.completion_tokens or 0, total_tokens=u.total_tokens or 0,
355
358
  cached_tokens=u.cached_tokens or 0, cache_creation_tokens=u.cache_creation_tokens or 0, reasoning_tokens=u.reasoning_tokens or 0,
356
359
  web_search_requests=search_count(r), cost=r.cost)
357
360
 
@@ -359,7 +362,7 @@ class UsageStats:
359
362
  if other is None: return self
360
363
  return UsageStats(**{k: getattr(self, k, 0) + getattr(other, k, 0)
361
364
  for k in ('prompt_tokens', 'completion_tokens', 'total_tokens', 'cached_tokens', 'cache_creation_tokens', 'reasoning_tokens', 'web_search_requests', 'cost')
362
- })
365
+ }, model=other.model or self.model)
363
366
  def __radd__(self, other): return self if other is None or other == 0 else self.__add__(other)
364
367
 
365
368
  def __repr__(self):
@@ -369,6 +372,7 @@ class UsageStats:
369
372
  if self.reasoning_tokens: parts.append(f"reasoning={self.reasoning_tokens:,}")
370
373
  if getattr(self, 'web_search_requests', None): parts.append(f"searches={self.web_search_requests}")
371
374
  if self.cost: parts.append(f"${self.cost:.4f}")
375
+ if self.model: parts.append(self.model.split('/')[-1])
372
376
  return ' | '.join(parts)
373
377
 
374
378
  def fmt(self):
@@ -740,35 +744,42 @@ def mk_tr_details(tr, mx=2000):
740
744
  # %% ../nbs/07_chat.ipynb #f0d984ec
741
745
  class StreamFormatter:
742
746
  def __init__(self, mx=2000, debug=False, showthink=False):
743
- self.outp,self.tcs = '',{}
747
+ self.outp,self.tcs,self._in_think = '',{},False
744
748
  store_attr()
745
749
 
746
750
  def format_item(self, o):
747
751
  "Format a single item from the response stream."
748
752
  res = ''
749
753
  if self.debug: print(o)
754
+ 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
750
756
  if isinstance(o, dict):
751
757
  if thk:=o.get('thinking'):
752
- if self.showthink: res += thk
753
- res+= '🧠' if not self.outp or self.outp[-1]=='🧠' else '\n\n🧠'
758
+ if self.showthink:
759
+ if not self._in_think:
760
+ res += f'\n{think_start}\n<details><summary>Thinking</summary>\n\n'
761
+ self._in_think = True
762
+ res += thk
763
+ else: res+= '🧠' if not self.outp or self.outp[-1]=='🧠' else '\n\n🧠'
754
764
  elif self.outp and self.outp[-1] == '🧠': res+= '\n\n'
755
765
  if txt:=o.get('text'): res+=f"\n\n{txt}" if res and res[-1] == '🧠' else txt
756
- if isinstance(o, Part) and o.type==PartType.tool_use:
757
- res += f"\n- ⏳ {_tc_summary(o)} ⏳\n"
758
- if isinstance(o, Part) and o.type == PartType.tool_result:
759
- res += mk_tr_details(o,mx=self.mx)
766
+ if isinstance(o, Part):
767
+ if o.type==PartType.tool_use: res += f"\n- ⏳ {_tc_summary(o)} ⏳\n"
768
+ elif o.type==PartType.tool_result: res += mk_tr_details(o,mx=self.mx)
760
769
  self.outp+=res
761
770
  return res
762
771
 
763
772
  def format_stream(self, rs):
764
773
  "Format the response stream for markdown display."
765
774
  for o in rs: yield self.format_item(o)
775
+ if self._in_think: yield f'\n\n</details>\n{think_end}\n\n'
766
776
 
767
777
  # %% ../nbs/07_chat.ipynb #0cdd4d7c
768
778
  class AsyncStreamFormatter(StreamFormatter):
769
779
  async def format_stream(self, rs):
770
780
  "Format the response stream for markdown display."
771
781
  async for o in rs: yield self.format_item(o)
782
+ if self._in_think: yield f'\n\n</details>\n{think_end}\n\n'
772
783
 
773
784
  # %% ../nbs/07_chat.ipynb #944bcd25
774
785
  @delegates(AsyncStreamFormatter)
@@ -397,6 +397,10 @@ register_model_info(codex53spark, 'codex', **codex_pricing,
397
397
  supports_vision=False, supports_image_input=False, supports_web_search=True, supports_reasoning=True, supports_function_calling=True,
398
398
  max_tokens=128000, max_input_tokens=128000, max_output_tokens=128000)
399
399
 
400
+ # %% ../nbs/00_types.ipynb #5d3e4720
401
+ for model in (haik45, sonn45, sonn46, opus46, opus):
402
+ register_model_info(model, 'claude_code', base=model, base_vendor_name='anthropic', **codex_pricing)
403
+
400
404
  # %% ../nbs/00_types.ipynb #24cc47ec
401
405
  def get_model_pricing(mn, vendor_name, million=True):
402
406
  return {k:round(v * (1e6 if million else 1), 6)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.22
3
+ Version: 0.0.24
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 +0,0 @@
1
- __version__ = "0.0.22"