python-fastllm 0.0.30__tar.gz → 0.0.32__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 (29) hide show
  1. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/PKG-INFO +3 -1
  2. python_fastllm-0.0.32/fastllm/__init__.py +1 -0
  3. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/_modidx.py +3 -31
  4. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/acomplete.py +18 -2
  5. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/anthropic.py +1 -0
  6. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/chat.py +31 -169
  7. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/gemini.py +1 -0
  8. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/openai_chat.py +1 -0
  9. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/openai_responses.py +1 -0
  10. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/streaming.py +1 -0
  11. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/types.py +7 -135
  12. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/pyproject.toml +1 -1
  13. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/PKG-INFO +3 -1
  14. python_fastllm-0.0.32/python_fastllm.egg-info/requires.txt +5 -0
  15. python_fastllm-0.0.30/fastllm/__init__.py +0 -1
  16. python_fastllm-0.0.30/python_fastllm.egg-info/requires.txt +0 -3
  17. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/README.md +0 -0
  18. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/codex.py +0 -0
  19. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/anthropic.json +0 -0
  20. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/anthropic.yml +0 -0
  21. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/gemini.json +0 -0
  22. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/openai.with-code-samples.json +0 -0
  23. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/openai.with-code-samples.yml +0 -0
  24. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/spec_manifest.json +0 -0
  25. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/SOURCES.txt +0 -0
  26. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/dependency_links.txt +0 -0
  27. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/entry_points.txt +0 -0
  28. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/top_level.txt +0 -0
  29. {python_fastllm-0.0.30 → python_fastllm-0.0.32}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.30
3
+ Version: 0.0.32
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
@@ -9,6 +9,8 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
+ Requires-Dist: fastcore>=2.1.11
13
+ Requires-Dist: aidialog>=0.0.3
12
14
  Requires-Dist: fastspec>=0.0.11
13
15
  Requires-Dist: toolslm
14
16
  Requires-Dist: pillow
@@ -0,0 +1 @@
1
+ __version__ = "0.0.32"
@@ -16,7 +16,8 @@ d = { 'settings': { 'branch': 'main',
16
16
  'fastllm.acomplete._retry_call': ('acomplete.html#_retry_call', 'fastllm/acomplete.py'),
17
17
  'fastllm.acomplete._retry_stream': ('acomplete.html#_retry_stream', 'fastllm/acomplete.py'),
18
18
  'fastllm.acomplete.acomplete': ('acomplete.html#acomplete', 'fastllm/acomplete.py'),
19
- 'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py')},
19
+ 'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py'),
20
+ 'fastllm.acomplete.split_vendor': ('acomplete.html#split_vendor', 'fastllm/acomplete.py')},
20
21
  'fastllm.anthropic': { 'fastllm.anthropic._ant_cc': ('anthropic.html#_ant_cc', 'fastllm/anthropic.py'),
21
22
  'fastllm.anthropic._ant_part_type': ('anthropic.html#_ant_part_type', 'fastllm/anthropic.py'),
22
23
  'fastllm.anthropic._sanid': ('anthropic.html#_sanid', 'fastllm/anthropic.py'),
@@ -77,14 +78,9 @@ d = { 'settings': { 'branch': 'main',
77
78
  'fastllm.chat.FenceToolStop': ('chat.html#fencetoolstop', 'fastllm/chat.py'),
78
79
  'fastllm.chat.FenceToolStop.__call__': ('chat.html#fencetoolstop.__call__', 'fastllm/chat.py'),
79
80
  'fastllm.chat.FenceToolStop.__init__': ('chat.html#fencetoolstop.__init__', 'fastllm/chat.py'),
80
- 'fastllm.chat.FullResponse': ('chat.html#fullresponse', 'fastllm/chat.py'),
81
- 'fastllm.chat.MediaUrl': ('chat.html#mediaurl', 'fastllm/chat.py'),
82
- 'fastllm.chat.MediaUrl.__init__': ('chat.html#mediaurl.__init__', 'fastllm/chat.py'),
83
- 'fastllm.chat.Msg.text': ('chat.html#msg.text', 'fastllm/chat.py'),
84
81
  'fastllm.chat.StopReasonCallback': ('chat.html#stopreasoncallback', 'fastllm/chat.py'),
85
82
  'fastllm.chat.StopReasonCallback.after_acomplete': ( 'chat.html#stopreasoncallback.after_acomplete',
86
83
  'fastllm/chat.py'),
87
- 'fastllm.chat.StopResponse': ('chat.html#stopresponse', 'fastllm/chat.py'),
88
84
  'fastllm.chat.StopSequencesCallback': ('chat.html#stopsequencescallback', 'fastllm/chat.py'),
89
85
  'fastllm.chat.StopSequencesCallback.__init__': ( 'chat.html#stopsequencescallback.__init__',
90
86
  'fastllm/chat.py'),
@@ -112,7 +108,6 @@ d = { 'settings': { 'branch': 'main',
112
108
  'fastllm.chat._apply_cache_idxs': ('chat.html#_apply_cache_idxs', 'fastllm/chat.py'),
113
109
  'fastllm.chat._bytes2content': ('chat.html#_bytes2content', 'fastllm/chat.py'),
114
110
  'fastllm.chat._call_func': ('chat.html#_call_func', 'fastllm/chat.py'),
115
- 'fastllm.chat._extract_tool_parts': ('chat.html#_extract_tool_parts', 'fastllm/chat.py'),
116
111
  'fastllm.chat._handle_stop_reason': ('chat.html#_handle_stop_reason', 'fastllm/chat.py'),
117
112
  'fastllm.chat._has_cache': ('chat.html#_has_cache', 'fastllm/chat.py'),
118
113
  'fastllm.chat._has_search': ('chat.html#_has_search', 'fastllm/chat.py'),
@@ -122,15 +117,10 @@ d = { 'settings': { 'branch': 'main',
122
117
  'fastllm.chat._mime2part_type': ('chat.html#_mime2part_type', 'fastllm/chat.py'),
123
118
  'fastllm.chat._mk_content': ('chat.html#_mk_content', 'fastllm/chat.py'),
124
119
  'fastllm.chat._mk_prefill': ('chat.html#_mk_prefill', 'fastllm/chat.py'),
125
- 'fastllm.chat._mk_result_fence': ('chat.html#_mk_result_fence', 'fastllm/chat.py'),
126
120
  'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
127
- 'fastllm.chat._split_fence_msgs': ('chat.html#_split_fence_msgs', 'fastllm/chat.py'),
128
- 'fastllm.chat._split_msg_on_fences': ('chat.html#_split_msg_on_fences', 'fastllm/chat.py'),
129
121
  'fastllm.chat._tc_summary': ('chat.html#_tc_summary', 'fastllm/chat.py'),
130
122
  'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
131
- 'fastllm.chat._trunc_content': ('chat.html#_trunc_content', 'fastllm/chat.py'),
132
123
  'fastllm.chat._trunc_param': ('chat.html#_trunc_param', 'fastllm/chat.py'),
133
- 'fastllm.chat._trunc_str': ('chat.html#_trunc_str', 'fastllm/chat.py'),
134
124
  'fastllm.chat._url2content': ('chat.html#_url2content', 'fastllm/chat.py'),
135
125
  'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
136
126
  'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
@@ -139,17 +129,13 @@ d = { 'settings': { 'branch': 'main',
139
129
  'fastllm.chat.cite_footnote': ('chat.html#cite_footnote', 'fastllm/chat.py'),
140
130
  'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
141
131
  'fastllm.chat.extract_fence_call': ('chat.html#extract_fence_call', 'fastllm/chat.py'),
142
- 'fastllm.chat.fmt2hist': ('chat.html#fmt2hist', 'fastllm/chat.py'),
143
- 'fastllm.chat.hist2fmt': ('chat.html#hist2fmt', 'fastllm/chat.py'),
144
132
  'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
145
133
  'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
146
134
  'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
147
- 'fastllm.chat.mk_tr_details': ('chat.html#mk_tr_details', 'fastllm/chat.py'),
148
135
  'fastllm.chat.postproc': ('chat.html#postproc', 'fastllm/chat.py'),
149
136
  'fastllm.chat.remove_cache_ckpts': ('chat.html#remove_cache_ckpts', 'fastllm/chat.py'),
150
137
  'fastllm.chat.run_fence_tool': ('chat.html#run_fence_tool', 'fastllm/chat.py'),
151
138
  'fastllm.chat.search_count': ('chat.html#search_count', 'fastllm/chat.py'),
152
- 'fastllm.chat.split_tools': ('chat.html#split_tools', 'fastllm/chat.py'),
153
139
  'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
154
140
  'fastllm.chat.stop_sequences': ('chat.html#stop_sequences', 'fastllm/chat.py'),
155
141
  'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py')},
@@ -272,18 +258,8 @@ d = { 'settings': { 'branch': 'main',
272
258
  'fastllm.types.Completion': ('types.html#completion', 'fastllm/types.py'),
273
259
  'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
274
260
  'fastllm.types.Completion.cost': ('types.html#completion.cost', 'fastllm/types.py'),
275
- 'fastllm.types.Msg': ('types.html#msg', 'fastllm/types.py'),
276
- 'fastllm.types.Msg._repr_markdown_': ('types.html#msg._repr_markdown_', 'fastllm/types.py'),
277
- 'fastllm.types.Part': ('types.html#part', 'fastllm/types.py'),
278
- 'fastllm.types.Part._repr_markdown_': ('types.html#part._repr_markdown_', 'fastllm/types.py'),
279
- 'fastllm.types.ToolCall': ('types.html#toolcall', 'fastllm/types.py'),
280
- 'fastllm.types.ToolCall._repr_markdown_': ('types.html#toolcall._repr_markdown_', 'fastllm/types.py'),
281
261
  'fastllm.types.Usage': ('types.html#usage', 'fastllm/types.py'),
282
- 'fastllm.types._fetch_url_partial': ('types.html#_fetch_url_partial', 'fastllm/types.py'),
283
- 'fastllm.types._trunc_strs': ('types.html#_trunc_strs', 'fastllm/types.py'),
284
262
  'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
285
- 'fastllm.types.data_url': ('types.html#data_url', 'fastllm/types.py'),
286
- 'fastllm.types.display_list': ('types.html#display_list', 'fastllm/types.py'),
287
263
  'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
288
264
  'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
289
265
  'fastllm.types.get_model_info': ('types.html#get_model_info', 'fastllm/types.py'),
@@ -292,11 +268,7 @@ d = { 'settings': { 'branch': 'main',
292
268
  'fastllm.types.infer_api_name': ('types.html#infer_api_name', 'fastllm/types.py'),
293
269
  'fastllm.types.is_deepseek_peak_hour': ('types.html#is_deepseek_peak_hour', 'fastllm/types.py'),
294
270
  'fastllm.types.mk_completion': ('types.html#mk_completion', 'fastllm/types.py'),
295
- 'fastllm.types.mk_tool_res_msg': ('types.html#mk_tool_res_msg', 'fastllm/types.py'),
296
271
  'fastllm.types.model_prices_meta': ('types.html#model_prices_meta', 'fastllm/types.py'),
297
- 'fastllm.types.part_txt': ('types.html#part_txt', 'fastllm/types.py'),
298
272
  'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
299
273
  'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
300
- 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
301
- 'fastllm.types.sys_text': ('types.html#sys_text', 'fastllm/types.py'),
302
- 'fastllm.types.url_mime': ('types.html#url_mime', 'fastllm/types.py')}}}
274
+ 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py')}}}
@@ -3,8 +3,8 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_acomplete.ipynb.
4
4
 
5
5
  # %% auto #0
6
- __all__ = ['specs_path', 'ant_spec', 'oai_spec', 'gem_spec', 'vendor_mapping', 'api2spec', 'defaults', 'mk_client',
7
- 'ContextWindowExceededError', 'acomplete']
6
+ __all__ = ['specs_path', 'ant_spec', 'oai_spec', 'gem_spec', 'vendor_mapping', 'api2spec', 'defaults', 'split_vendor',
7
+ 'mk_client', 'ContextWindowExceededError', 'acomplete']
8
8
 
9
9
  # %% ../nbs/06_acomplete.ipynb #f2f57253
10
10
  import asyncio,json,httpx
@@ -16,6 +16,7 @@ from fastspec.oapi import *
16
16
  from fastspec.errors import APIError
17
17
 
18
18
  from .types import *
19
+ from aidialog.msg_parts import Part, PartType, Msg, mk_tool_res_msg
19
20
  from .streaming import *
20
21
  from .openai_responses import *
21
22
  from .openai_chat import *
@@ -51,11 +52,22 @@ vendor_mapping = {
51
52
  # %% ../nbs/06_acomplete.ipynb #77d27ea7
52
53
  api2spec = {'openai':oai_spec, 'openai_chat':oai_spec, 'anthropic':ant_spec, 'gemini':gem_spec}
53
54
 
55
+ # %% ../nbs/06_acomplete.ipynb #e3ed40fb
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)`."
58
+ if model and '/' in model:
59
+ v, m = model.split('/', 1)
60
+ if v in vendor_mapping or v in api_registry.apis: return v, m
61
+ return None, model
62
+
54
63
  # %% ../nbs/06_acomplete.ipynb #79075d95
55
64
  @flexicache()
56
65
  def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_url=None, xtra_hdrs=None,
57
66
  timeout=httpx.Timeout(connect=30, read=300, write=30, pool=10)):
58
67
  err_msg = f"please pass a valid one vendor: {', '.join(list(vendor_mapping))} or pass `api_name`,`base_url` and `api_key`"
68
+ if not vendor_name and not api_name and not (base_url and api_key):
69
+ v, m = split_vendor(model)
70
+ if v in vendor_mapping: vendor_name, model = v, m
59
71
  if vendor_name:
60
72
  override_base_url = base_url
61
73
  try:
@@ -145,6 +157,10 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
145
157
  base_url=None, xtra_body=None, xtra_hdrs=None, stream=False,
146
158
  stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
147
159
  "Unified completion across different APIs."
160
+ if not vendor_name and not api_name and not (base_url and api_key):
161
+ v, m = split_vendor(model)
162
+ if v in vendor_mapping: vendor_name, model = v, m
163
+ elif v: api_name, model = v, m # a registered transport api (e.g. claude_code): not an HTTP vendor
148
164
  if api_name == 'claude_code':
149
165
  if not stream: raise NotImplementedError("claude_code backend supports stream=True only for now")
150
166
  api = api_registry.apis[api_name]
@@ -15,6 +15,7 @@ from fastcore.meta import *
15
15
  from fastspec.errors import api_error_from_event
16
16
 
17
17
  from .types import *
18
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, data_url
18
19
  from .streaming import *
19
20
  from .streaming import mk_acollect_stream
20
21
 
@@ -3,33 +3,28 @@
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', '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', 'hist2fmt', 'StreamFormatter', 'AsyncStreamFormatter',
13
- 'adisplay_stream']
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']
14
11
 
15
12
  # %% ../nbs/07_chat.ipynb #d5a3bc1f
16
- import base64, json
17
13
  from typing import Optional,Callable
18
- from html import escape
19
14
  from toolslm.funccall import mk_ns, call_func, call_func_async, get_schema
20
15
  from fastcore.utils import *
21
16
  from fastcore.meta import delegates
22
17
  from dataclasses import dataclass
18
+ import base64
23
19
 
24
20
  from .types import *
21
+ from fastcore.xtras import detect_mime
22
+ 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)
25
25
  from .acomplete import *
26
26
 
27
- # %% ../nbs/07_chat.ipynb #1b75c262
28
- class MediaUrl(BasicRepr):
29
- "Direct URL media reference"
30
- def __init__(self, url, mime=None): self.url, self.mime = url, ifnone(mime, url_mime(url))
31
-
32
- # %% ../nbs/07_chat.ipynb #eb557831
27
+ # %% ../nbs/07_chat.ipynb #4a37bae5
33
28
  def _mime2part_type(mime):
34
29
  "Map MIME string to canonical PartType"
35
30
  if mime.startswith('image/'): return PartType.input_image
@@ -49,7 +44,7 @@ def _url2content(o):
49
44
  mime = o.mime or url_mime(o.url)
50
45
  return Part(type=_mime2part_type(mime), text=o.url, data=dict(mime=mime))
51
46
 
52
- # %% ../nbs/07_chat.ipynb #48c78e48
47
+ # %% ../nbs/07_chat.ipynb #43a55cda
53
48
  def _add_cache_control(
54
49
  msg, # LiteLLM formatted msg
55
50
  ttl=None # Cache TTL: '5m' (default) or '1h'
@@ -78,6 +73,8 @@ def _mk_content(o):
78
73
  elif isinstance(o, MediaUrl): return _url2content(o)
79
74
  return o
80
75
 
76
+
77
+ # %% ../nbs/07_chat.ipynb #48c78e48
81
78
  def contents(c):
82
79
  "Get Msg object from Completion."
83
80
  if not c.message: return ''
@@ -87,7 +84,7 @@ def stop_reason(c):
87
84
  if not c.finish_reason: return 'unk'
88
85
  return c.finish_reason
89
86
 
90
- # %% ../nbs/07_chat.ipynb #8bdd997c
87
+ # %% ../nbs/07_chat.ipynb #4a1c44ea
91
88
  def mk_msg(
92
89
  content, # Content: str, bytes (image), list of mixed content, or dict w 'role' and 'content' fields
93
90
  role="user", # Message role if content isn't already a dict/Message
@@ -105,20 +102,8 @@ def mk_msg(
105
102
  msg = Msg(role=role, content=parts)
106
103
  return _add_cache_control(msg, ttl=ttl) if cache else msg
107
104
 
108
- # %% ../nbs/07_chat.ipynb #db466e1c
109
- tool_dtls_tag = "<details class='tool-usage-details' markdown='1'>"
110
- re_tools = re.compile(fr"^({tool_dtls_tag}\n*(?:<summary>(?P<summary>.*?)</summary>\n*)?\n*```json\n+(.*?)\n+```\n+</details>)",
111
- flags=re.DOTALL|re.MULTILINE)
112
- token_dtls_tag = "<details class='token-usage-details' markdown='1'>"
113
- re_token = re.compile(fr"^{re.escape(token_dtls_tag)}\n*<summary>.*?</summary>\n*\n*`.*?`\n*\n*</details>\n?",
114
- flags=re.DOTALL|re.MULTILINE)
115
- think_start,think_end = '<!--think_start-->','<!--think_end-->'
116
- re_think = re.compile(rf'{re.escape(think_start)}.*?{re.escape(think_end)}\n?', re.DOTALL)
117
-
118
105
  # %% ../nbs/07_chat.ipynb #be998131
119
- _fence_back = '`````'
120
106
  _fence_re = re.compile(f'^{_fence_back}(py|bash)\n(.*?)\n{_fence_back}$', re.DOTALL | re.MULTILINE)
121
- _result_re = re.compile(f'\n{_fence_back}result\n(.*?)\n{_fence_back}\n', re.DOTALL)
122
107
  _lang2tool = dict(py='python', bash='bash')
123
108
 
124
109
  class FenceToolStop:
@@ -136,83 +121,7 @@ def extract_fence_call(text):
136
121
  m = ms[-1]
137
122
  if not text[m.end():].strip(): return m.group(1), m.group(2)
138
123
 
139
- # %% ../nbs/07_chat.ipynb #215183bf
140
- @patch(as_prop=True)
141
- def text(self:Msg): return ''.join(p.text or '' for p in self.content if p.type == PartType.text)
142
-
143
- # %% ../nbs/07_chat.ipynb #1de7e4d2
144
- def _mk_result_fence(output): return f"\n{_fence_back}result\n{output}\n{_fence_back}\n"
145
-
146
- def _split_msg_on_fences(msg):
147
- "Split an assistant Msg on result fences, return list of Msgs"
148
- if msg.role != 'assistant': return [msg]
149
- if not _result_re.search(msg.text): return [msg]
150
- res, asst_parts, tool_parts = [], [], []
151
- for msg_part in msg.content:
152
- if msg_part.type == PartType.thinking: asst_parts.append(msg_part)
153
- elif msg_part.type == PartType.tool_use: tool_parts.append(msg_part)
154
- elif parts := _result_re.split(msg_part.text or ''):
155
- for i,p in enumerate(parts):
156
- if not p: continue
157
- if i % 2 == 0: res.append(Msg(role='assistant', content=asst_parts+[Part(type=PartType.text, text=p.strip())]))
158
- else: res.append(Msg(role='user', content=[Part(type=PartType.text, text=_mk_result_fence(p))]))
159
- if tool_parts: res.append(Msg(role='assistant', content=tool_parts))
160
- return res
161
-
162
- def _split_fence_msgs(msgs):
163
- "Split all assistant msgs on result fences for wire protocol"
164
- res = []
165
- for m in msgs: res.extend(_split_msg_on_fences(m))
166
- return res
167
-
168
- # %% ../nbs/07_chat.ipynb #45ada210
169
- def _extract_tool_parts(text:str):
170
- "Extract (tool_use_part, tool_result_part) from <details> json block"
171
- try: d = json.loads(text.strip())
172
- except: return None
173
- call = d['call']
174
- # Skip server tool calls in deserialization (round trip issues with Gemini/Anthropic)
175
- if d.get('server'): return None
176
- tu = Part(type=PartType.tool_use, text=None, data=dict(id=d['id'], name=call['function'], arguments=call['arguments']))
177
- tr = Part(type=PartType.tool_result, text=str(d['result']), data={'id': d['id'], 'name': call['function']})
178
- return tu, tr
179
-
180
- def split_tools(s):
181
- "Split formatted output into (text, summary, tooljson) chunks"
182
- return [(txt,summ,tj) for txt,_,summ,tj in chunked(re_tools.split(s.strip()), 4, pad=True)]
183
-
184
- # %% ../nbs/07_chat.ipynb #44060a78
185
- def fmt2hist(outp:str)->list[Msg]:
186
- "Transform a formatted output string into fastllm canonical Msgs"
187
- if token_dtls_tag in outp: outp = re_token.sub('', outp)
188
- if think_start in outp: outp = re_think.sub('', outp)
189
- if tool_dtls_tag not in outp:
190
- msg = Msg(role='assistant', content=[Part(type=PartType.text, text=outp.strip() or '.')])
191
- return _split_msg_on_fences(msg)
192
- hist, asst_parts, tool_parts = [], [], []
193
- def flush():
194
- if tool_parts:
195
- hist.append(Msg(role='assistant', content=asst_parts.copy()))
196
- hist.append(Msg(role='tool', content=tool_parts.copy()))
197
- asst_parts.clear()
198
- tool_parts.clear()
199
- for txt,_,tj in split_tools(outp):
200
- if txt and txt.strip():
201
- if tool_parts: flush()
202
- asst_parts.append(Part(type=PartType.text, text=txt.strip() or '.'))
203
- if tj and (tp := _extract_tool_parts(tj)):
204
- asst_parts.append(tp[0])
205
- tool_parts.append(tp[1])
206
- flush()
207
- if asst_parts: hist.append(Msg(role='assistant', content=asst_parts))
208
- result = []
209
- for msg in hist:
210
- if msg.role == 'assistant': result.extend(_split_msg_on_fences(msg))
211
- else: result.append(msg)
212
- if result[-1].role == 'tool': result.append(Msg(role='assistant', content=[Part(type=PartType.text, text='.')]))
213
- return result
214
-
215
- # %% ../nbs/07_chat.ipynb #8de5ce8d
124
+ # %% ../nbs/07_chat.ipynb #01bd81de
216
125
  def _apply_cache_idxs(msgs, cache_idxs=[-1], ttl=None):
217
126
  "Add cache control to `cache_idxs` after filtering tool-role msgs"
218
127
  ms = [j for j,m in enumerate(msgs) if m.role != 'tool']
@@ -221,7 +130,7 @@ def _apply_cache_idxs(msgs, cache_idxs=[-1], ttl=None):
221
130
  except IndexError: continue
222
131
  _add_cache_control(msgs[idx], ttl)
223
132
 
224
- # %% ../nbs/07_chat.ipynb #85882c9c
133
+ # %% ../nbs/07_chat.ipynb #fcbb9f5d
225
134
  def mk_msgs(
226
135
  msgs, # List of messages (each: str, bytes, list, Msg, or Completion)
227
136
  cache=False, # Enable Anthropic caching
@@ -231,7 +140,7 @@ def mk_msgs(
231
140
  "Create a list of fastllm canonical Msgs."
232
141
  if not msgs: return []
233
142
  if not isinstance(msgs, list): msgs = [msgs]
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()
143
+ msgs = L(msgs).map(lambda m: fmt2hist(m) if isinstance(m,str) and (tool_info in m or usage_info in m) else [m]).concat()
235
144
  res, role = [], 'user'
236
145
  for m in msgs:
237
146
  res.append(msg := remove_cache_ckpts(mk_msg(m, role=role)))
@@ -311,30 +220,8 @@ effort['x'] = 'max'
311
220
  def _mk_prefill(pf): return dict(text=pf)
312
221
 
313
222
  # %% ../nbs/07_chat.ipynb #dc17f844
314
- class StopResponse(str): pass
315
- class FullResponse(str): pass
316
-
317
223
  def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in tres_parts)
318
224
 
319
- # %% ../nbs/07_chat.ipynb #f58ce348
320
- def _trunc_str(s, mx=2000, skip=10, replace="TRUNCATED"):
321
- "Truncate `s` to `mx` chars max, adding `replace` if truncated; `mx=None` disables truncation"
322
- if mx is None or isinstance_str(s, ('FullResponse','Safe','PrettyString')): return s
323
- if not isinstance(s, str): s = str(s)
324
- s = type(s)(s.rstrip())
325
- if len(s)>2 and s[0]=='𝍁' and s[-1]=='𝍁':
326
- s = s[1:-1]
327
- if replace: return s
328
- if mx is None or len(s)<=mx: return s
329
- s = s[skip:mx-skip]
330
- ss = s.split(' ')
331
- if len(ss[-1])>150: ss[-1] = ss[-1][:5]
332
- s = ' '.join(ss)
333
- if skip: s = f"…{s}"
334
- s = f"{s}…"
335
- if replace: s = f"<{replace}>{s}</{replace}>"
336
- return s
337
-
338
225
  # %% ../nbs/07_chat.ipynb #ca9e447e
339
226
  _final_prompt = dict(role="user", content="You have used all your tool calls for this turn. Please summarize your findings. If you did not complete your goal, tell the user what further work is needed. You may use tools again on the next user message.")
340
227
 
@@ -379,8 +266,8 @@ class UsageStats:
379
266
 
380
267
  def fmt(self):
381
268
  if not self.total_tokens: return ''
382
- summ = f"${self.cost:.4f}" if self.cost else f"{self.total_tokens:,} tokens"
383
- return f"\n\n{token_dtls_tag}<summary>{summ}</summary>\n\n`{self!r}`\n\n</details>\n"
269
+ d = {k:v for k,v in self.__dict__.items() if v and not k.startswith('_')}
270
+ return "\n\n" + fenced(dumps(d, ensure_ascii=False), usage_info) + "\n"
384
271
 
385
272
  # %% ../nbs/07_chat.ipynb #cb3d7e77
386
273
  class AsyncChat:
@@ -406,12 +293,17 @@ class AsyncChat:
406
293
  default_cbs=True # Whether to include default callbacks
407
294
  ):
408
295
  "LiteLLM chat client."
296
+ if not vendor_name and not api_name and not (base_url and api_key):
297
+ v, model = split_vendor(model)
298
+ if v in vendor_mapping: vendor_name = v
299
+ elif v: api_name = v # a registered transport api (e.g. claude_code)
409
300
  self.model = model
410
301
  hist,tools = mk_msgs(hist,cache,cache_idxs,ttl),listify(tools)
411
302
  if ns is None and tools: ns = mk_ns(tools)
412
303
  elif ns is None: ns = globals()
413
304
  self.tool_schemas = [lite_mk_func(t) for t in tools] if tools else None
414
305
  self.use = UsageStats()
306
+ self.last_req_use = None # usage of the latest request only; `use` accumulates across a turn's tool-call steps
415
307
  store_attr(but='cbs')
416
308
  self.cbs = L()
417
309
  if default_cbs: self.add_cbs(defaults.chat_callbacks)
@@ -435,6 +327,7 @@ class AsyncChat:
435
327
  def _track(self, res):
436
328
  u = UsageStats.from_response(res)
437
329
  u.cost *= (1 + self.markup)
330
+ self.last_req_use = u
438
331
  self.use += u
439
332
 
440
333
  def add_cb(self, cb):
@@ -721,44 +614,13 @@ def _trunc_param(v, mx=40):
721
614
 
722
615
  # %% ../nbs/07_chat.ipynb #80c0abdb
723
616
  def _tc_summary(tr):
724
- "Format tool call as func(params) result string"
617
+ "Format tool call as a `func(params)→result` code span"
725
618
  params = ', '.join(f"{k}={_trunc_param(v)}" for k,v in tr.data['arguments'].items())
726
619
  res = f"→{_trunc_param(tr.text)}" if tr.text else ''
727
- return '<code>'+escape(f"{tr.data['name']}({params}){res}")+'</code>'
728
-
729
- # %% ../nbs/07_chat.ipynb #80f344cc
730
- def _trunc_content(content, mx):
731
- "Truncate tool result content, respecting '_full' flag"
732
- if isinstance(content, dict) and '_full' in content and len(content)==1: return content['_full']
733
- return _trunc_str(content, mx=mx)
734
-
735
- # %% ../nbs/07_chat.ipynb #3602a033
736
- def mk_tr_details(tr, mx=2000):
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()}
739
- res = {'id':tr.data['id'], 'server':tr.data.get('server', False),
740
- 'call':{'function': tr.data['name'], 'arguments': args},
741
- 'result':_trunc_content(tr.text, mx=mx),}
742
- summ = f"<summary>{_tc_summary(tr)}</summary>"
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"
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)
620
+ txt = f"{tr.data['name']}({params}){res}"
621
+ ticks = '`'*(max(map(len, re.findall('`+', txt)), default=0)+1)
622
+ pad = ' ' if '`' in txt else ''
623
+ return f"{ticks}{pad}{txt}{pad}{ticks}"
762
624
 
763
625
  # %% ../nbs/07_chat.ipynb #f0d984ec
764
626
  class StreamFormatter:
@@ -15,6 +15,7 @@ from fastcore.meta import *
15
15
  from fastspec.errors import api_error_from_event
16
16
 
17
17
  from .types import *
18
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, sys_text, part_txt, data_url, url_mime
18
19
  from .streaming import *
19
20
  from .streaming import mk_acollect_stream
20
21
 
@@ -14,6 +14,7 @@ from fastcore.meta import *
14
14
  from fastspec.errors import api_error_from_event
15
15
 
16
16
  from .types import *
17
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, sys_text, part_txt, data_url
17
18
  from .streaming import *
18
19
  from .streaming import mk_acollect_stream
19
20
  from .openai_responses import norm_usage
@@ -15,6 +15,7 @@ from fastcore.meta import *
15
15
  from fastspec.errors import api_error_from_event
16
16
 
17
17
  from .types import *
18
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, mk_tool_res_msg, sys_text, part_txt, data_url
18
19
  from .streaming import *
19
20
  from .streaming import mk_acollect_stream
20
21
 
@@ -12,6 +12,7 @@ from fastcore.utils import *
12
12
  from fastcore.meta import delegates
13
13
  from fastspec.errors import *
14
14
  from .types import *
15
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, _fence_back
15
16
 
16
17
  # %% ../nbs/01_streaming.ipynb #400d628a
17
18
  @dataclass
@@ -3,13 +3,12 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_types.ipynb.
4
4
 
5
5
  # %% auto #0
6
- __all__ = ['PartType', 'FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45', 'sonn', 'sonn46', 'sonn5',
7
- 'opus46', 'opus', 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark',
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']
6
+ __all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45', 'sonn', 'sonn46', 'sonn5', 'opus46', 'opus',
7
+ 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark', 'model_info_registry',
8
+ 'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', 'sol', 'terra', 'luna', 'gpt56s',
9
+ 'Usage', 'Completion', 'APIRegistry', 'mk_completion', 'fn_schema', 'payload_kwargs', 'get_api_key',
10
+ 'resize_b64', 'model_prices_meta', 'infer_api_name', 'get_model_meta', 'register_model_info',
11
+ 'get_model_info', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour']
13
12
 
14
13
  # %% ../nbs/00_types.ipynb #b4d047fd
15
14
  import httpx, base64, io
@@ -18,86 +17,8 @@ from datetime import datetime, timezone
18
17
  from fastcore.net import urljson
19
18
  from fastcore.utils import *
20
19
  from PIL import Image as PImg
20
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, data_url
21
21
 
22
- # %% ../nbs/00_types.ipynb #e568bade
23
- @dataclass
24
- class Part:
25
- "A normalized content part."
26
- type: str
27
- text: str = None
28
- data: dict = None
29
-
30
- # %% ../nbs/00_types.ipynb #aa3cfc23
31
- PartType = str_enum('PartType', 'text', 'thinking', 'refusal', 'tool_use', 'server_tool_result', 'tool_result',
32
- 'input_image', 'input_audio', 'input_video', 'input_file')
33
-
34
- # %% ../nbs/00_types.ipynb #2eeff103
35
- def _trunc_strs(o, n=200):
36
- "Truncate str or dict"
37
- if not o: return o
38
- if isinstance(o,str) and len(o)>n: return o[:100]+'...'
39
- if isinstance(o,dict): return {k: (v[:100]+'...' if isinstance(v,str) and len(v)>n else v) for k,v in o.items()}
40
- return o
41
-
42
- @patch
43
- def _repr_markdown_(self: Part):
44
- body = _trunc_strs(self.text) if self.text else ''
45
- data = _trunc_strs(self.data)
46
- return f"""**Part** (`{self.type}`)
47
-
48
- {body}
49
-
50
- <details markdown='1'>
51
-
52
- - data: `{data}`
53
-
54
- </details>"""
55
-
56
- # %% ../nbs/00_types.ipynb #afeb9eef
57
- @dataclass
58
- class Msg:
59
- "A normalized message."
60
- role: str
61
- content: List[Part]
62
-
63
- def _repr_markdown_(self):
64
- return f"""**Msg**
65
-
66
- - role: `{self.role}`
67
-
68
- <contents>
69
-
70
- {'\n\n'.join(p._repr_markdown_() for p in self.content)}
71
-
72
- </contents>"""
73
-
74
- # %% ../nbs/00_types.ipynb #f6d04920
75
- @dataclass
76
- class ToolCall:
77
- "Normalized tool call."
78
- id: str
79
- name: str
80
- arguments: dict = field(default_factory=dict)
81
- server: bool = False
82
- extra: dict = field(default_factory=dict)
83
-
84
- # %% ../nbs/00_types.ipynb #509cfc99
85
- @patch
86
- def _repr_markdown_(self: ToolCall):
87
- extra = _trunc_strs(self.extra)
88
- return f"""🔧 **{self.name}**(`{self.arguments}`)
89
-
90
- <details markdown='1'>
91
-
92
- - id: `{self.id}`
93
- - server: `{self.server}`
94
- - extra: `{extra}`
95
-
96
- </details>"""
97
-
98
- def display_list(l):
99
- from IPython.display import Markdown, display
100
- display(Markdown('\n\n'.join(o._repr_markdown_() for o in l)))
101
22
 
102
23
  # %% ../nbs/00_types.ipynb #802ad832
103
24
  @dataclass(frozen=True)
@@ -177,15 +98,6 @@ def mk_completion(resp, model, api_name, vendor_name):
177
98
  vendor_name=vendor_name,
178
99
  raw=resp)
179
100
 
180
- # %% ../nbs/00_types.ipynb #d5322db5
181
- def mk_tool_res_msg(tool_calls:list[ToolCall], results:list[str|list]):
182
- 'A util to prepare parallel tool call with str or media list results'
183
- parts = []
184
- for tc,res in zip(tool_calls, results):
185
- data = dict(id=tc.id, name=tc.name, arguments=tc.arguments, server=tc.server)
186
- parts.append(Part(type=PartType.tool_result, text=res, data=data))
187
- return Msg(role="tool", content=parts)
188
-
189
101
  # %% ../nbs/00_types.ipynb #8a8e468b
190
102
  def fn_schema(t):
191
103
  "Extract (name, description, parameters) from any tool format."
@@ -196,46 +108,6 @@ def fn_schema(t):
196
108
  return t.get('name',''), t.get('description',''), t.get('parameters', t.get('input_schema',{}))
197
109
  return None
198
110
 
199
- # %% ../nbs/00_types.ipynb #d1d48d91
200
- def sys_text(system):
201
- "Extract text from system (str or Part)."
202
- if system is None: return None
203
- return system if isinstance(system, str) else system.text
204
-
205
- def part_txt(p): return p.text if isinstance(p,Part) else p
206
-
207
- # %% ../nbs/00_types.ipynb #f3deb055
208
- @flexicache(time_policy(24*3600))
209
- def _fetch_url_partial(url, nbytes=512):
210
- "Fetch remote media bytes, optionally only first `nbytes`."
211
- try:
212
- with httpx.stream('GET', url, headers={'Range': f'bytes=0-{nbytes-1}'}, follow_redirects=True) as r:
213
- if r.status_code not in (200, 206): return
214
- return r.read()
215
- except (httpx.HTTPError, httpx.InvalidURL): return
216
-
217
- # %% ../nbs/00_types.ipynb #70a9a0c3
218
- _ext_mime = {
219
- '.jpg':'image/jpeg', '.jpeg':'image/jpeg', '.png':'image/png', '.gif':'image/gif', '.webp':'image/webp',
220
- '.pdf':'application/pdf',
221
- '.mp3':'audio/mpeg', '.wav':'audio/wav', '.ogg':'audio/ogg', '.flac':'audio/flac', '.m4a':'audio/mp4',
222
- '.mp4':'video/mp4', '.mov':'video/quicktime', '.webm':'video/webm',
223
- }
224
-
225
- def data_url(url):
226
- "Parse data:mime;base64,data URL into (mime, b64_data), or None."
227
- if not isinstance(url, str) or not url.startswith('data:') or ',' not in url: return None
228
- header, body = url.split(',', 1)
229
- if ';base64' not in header or not body: return None
230
- return header[5:].split(';',1)[0].strip() or 'application/octet-stream', body
231
-
232
- def url_mime(url, default='application/octet-stream'):
233
- "Guess mime from URL extension, and optional bytes fallback."
234
- if "youtube.com" in url or "youtu.be" in url: return "video/mp4"
235
- ext = '.' + url.rsplit('.', 1)[-1].split('?')[0].lower() if '.' in url.split('?')[0].split('/')[-1] else ''
236
- if (mime:=_ext_mime.get(ext)) is None: return detect_mime(_fetch_url_partial(url))
237
- return ifnone(mime, default)
238
-
239
111
  # %% ../nbs/00_types.ipynb #28c698fe
240
112
  def payload_kwargs(msgs, model, stream=False, system=None, max_tokens=None, temperature=None, tools=None, tool_choice=None, reasoning_effort=None, web_search_options=None, stop_callables=None): pass
241
113
 
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "Programming Language :: Python :: 3 :: Only",
17
17
  ]
18
- dependencies = ['fastspec>=0.0.11', 'toolslm', 'pillow']
18
+ dependencies = ['fastcore>=2.1.11', 'aidialog>=0.0.3', '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.30
3
+ Version: 0.0.32
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
@@ -9,6 +9,8 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
+ Requires-Dist: fastcore>=2.1.11
13
+ Requires-Dist: aidialog>=0.0.3
12
14
  Requires-Dist: fastspec>=0.0.11
13
15
  Requires-Dist: toolslm
14
16
  Requires-Dist: pillow
@@ -0,0 +1,5 @@
1
+ fastcore>=2.1.11
2
+ aidialog>=0.0.3
3
+ fastspec>=0.0.11
4
+ toolslm
5
+ pillow
@@ -1 +0,0 @@
1
- __version__ = "0.0.30"
@@ -1,3 +0,0 @@
1
- fastspec>=0.0.11
2
- toolslm
3
- pillow