python-fastllm 0.0.38__tar.gz → 0.0.39__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.38 → python_fastllm-0.0.39}/PKG-INFO +2 -2
  2. python_fastllm-0.0.39/fastllm/__init__.py +1 -0
  3. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/_modidx.py +14 -13
  4. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/acomplete.py +2 -1
  5. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/anthropic.py +37 -2
  6. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/chat.py +23 -114
  7. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/streaming.py +18 -19
  8. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/types.py +19 -41
  9. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/pyproject.toml +1 -1
  10. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/PKG-INFO +2 -2
  11. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/requires.txt +1 -1
  12. python_fastllm-0.0.38/fastllm/__init__.py +0 -1
  13. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/README.md +0 -0
  14. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/codex.py +0 -0
  15. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/gemini.py +0 -0
  16. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/openai_chat.py +0 -0
  17. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/openai_responses.py +0 -0
  18. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/anthropic.json +0 -0
  19. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/anthropic.yml +0 -0
  20. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/gemini.json +0 -0
  21. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/openai.with-code-samples.json +0 -0
  22. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/openai.with-code-samples.yml +0 -0
  23. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/fastllm/specs/spec_manifest.json +0 -0
  24. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/SOURCES.txt +0 -0
  25. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/dependency_links.txt +0 -0
  26. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/entry_points.txt +0 -0
  27. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/python_fastllm.egg-info/top_level.txt +0 -0
  28. {python_fastllm-0.0.38 → python_fastllm-0.0.39}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.38
3
+ Version: 0.0.39
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.18
13
- Requires-Dist: aidialog>=0.0.8
13
+ Requires-Dist: aidialog>=0.0.10
14
14
  Requires-Dist: fastspec>=0.0.11
15
15
  Requires-Dist: pillow
16
16
 
@@ -0,0 +1 @@
1
+ __version__ = "0.0.39"
@@ -19,10 +19,12 @@ d = { 'settings': { 'branch': 'main',
19
19
  'fastllm.acomplete.acomplete': ('acomplete.html#acomplete', 'fastllm/acomplete.py'),
20
20
  'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py'),
21
21
  'fastllm.acomplete.split_vendor': ('acomplete.html#split_vendor', 'fastllm/acomplete.py')},
22
- 'fastllm.anthropic': { 'fastllm.anthropic._ant_cc': ('anthropic.html#_ant_cc', 'fastllm/anthropic.py'),
22
+ 'fastllm.anthropic': { 'fastllm.anthropic._add_cache_control': ('anthropic.html#_add_cache_control', 'fastllm/anthropic.py'),
23
+ 'fastllm.anthropic._ant_cc': ('anthropic.html#_ant_cc', 'fastllm/anthropic.py'),
23
24
  'fastllm.anthropic._ant_part_type': ('anthropic.html#_ant_part_type', 'fastllm/anthropic.py'),
24
25
  'fastllm.anthropic._sanid': ('anthropic.html#_sanid', 'fastllm/anthropic.py'),
25
26
  'fastllm.anthropic.acollect_stream': ('anthropic.html#acollect_stream', 'fastllm/anthropic.py'),
27
+ 'fastllm.anthropic.apply_cache_idxs': ('anthropic.html#apply_cache_idxs', 'fastllm/anthropic.py'),
26
28
  'fastllm.anthropic.cost': ('anthropic.html#cost', 'fastllm/anthropic.py'),
27
29
  'fastllm.anthropic.delta_index_fn': ('anthropic.html#delta_index_fn', 'fastllm/anthropic.py'),
28
30
  'fastllm.anthropic.denorm_assistant': ('anthropic.html#denorm_assistant', 'fastllm/anthropic.py'),
@@ -66,9 +68,6 @@ d = { 'settings': { 'branch': 'main',
66
68
  'fastllm.chat.DeepseekMsgsCallback': ('chat.html#deepseekmsgscallback', 'fastllm/chat.py'),
67
69
  'fastllm.chat.DeepseekMsgsCallback.after_msgs': ( 'chat.html#deepseekmsgscallback.after_msgs',
68
70
  'fastllm/chat.py'),
69
- 'fastllm.chat.DeepseekPrefillCallback': ('chat.html#deepseekprefillcallback', 'fastllm/chat.py'),
70
- 'fastllm.chat.DeepseekPrefillCallback.before_acomplete': ( 'chat.html#deepseekprefillcallback.before_acomplete',
71
- 'fastllm/chat.py'),
72
71
  'fastllm.chat.FenceToolCallback': ('chat.html#fencetoolcallback', 'fastllm/chat.py'),
73
72
  'fastllm.chat.FenceToolCallback.after_msgs': ('chat.html#fencetoolcallback.after_msgs', 'fastllm/chat.py'),
74
73
  'fastllm.chat.FenceToolCallback.before_acomplete': ( 'chat.html#fencetoolcallback.before_acomplete',
@@ -99,17 +98,13 @@ d = { 'settings': { 'branch': 'main',
99
98
  'fastllm.chat.UsageStats.fmt': ('chat.html#usagestats.fmt', 'fastllm/chat.py'),
100
99
  'fastllm.chat.UsageStats.from_response': ('chat.html#usagestats.from_response', 'fastllm/chat.py'),
101
100
  'fastllm.chat._active_fence_langs': ('chat.html#_active_fence_langs', 'fastllm/chat.py'),
102
- 'fastllm.chat._add_cache_control': ('chat.html#_add_cache_control', 'fastllm/chat.py'),
103
101
  'fastllm.chat._alite_call_func': ('chat.html#_alite_call_func', 'fastllm/chat.py'),
104
- 'fastllm.chat._apply_cache_idxs': ('chat.html#_apply_cache_idxs', 'fastllm/chat.py'),
105
102
  'fastllm.chat._call_func': ('chat.html#_call_func', 'fastllm/chat.py'),
106
103
  'fastllm.chat._handle_stop_reason': ('chat.html#_handle_stop_reason', 'fastllm/chat.py'),
107
- 'fastllm.chat._has_cache': ('chat.html#_has_cache', 'fastllm/chat.py'),
108
104
  'fastllm.chat._has_search': ('chat.html#_has_search', 'fastllm/chat.py'),
109
105
  'fastllm.chat._has_stop': ('chat.html#_has_stop', 'fastllm/chat.py'),
110
106
  'fastllm.chat._inject_tool_reminder': ('chat.html#_inject_tool_reminder', 'fastllm/chat.py'),
111
107
  'fastllm.chat._lite_call_func': ('chat.html#_lite_call_func', 'fastllm/chat.py'),
112
- 'fastllm.chat._mk_prefill': ('chat.html#_mk_prefill', 'fastllm/chat.py'),
113
108
  'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
114
109
  'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
115
110
  'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
@@ -119,10 +114,7 @@ d = { 'settings': { 'branch': 'main',
119
114
  'fastllm.chat.cite_footnote': ('chat.html#cite_footnote', 'fastllm/chat.py'),
120
115
  'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
121
116
  'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
122
- 'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
123
- 'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
124
117
  'fastllm.chat.postproc': ('chat.html#postproc', 'fastllm/chat.py'),
125
- 'fastllm.chat.remove_cache_ckpts': ('chat.html#remove_cache_ckpts', 'fastllm/chat.py'),
126
118
  'fastllm.chat.run_fence_tool': ('chat.html#run_fence_tool', 'fastllm/chat.py'),
127
119
  'fastllm.chat.search_count': ('chat.html#search_count', 'fastllm/chat.py'),
128
120
  'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
@@ -232,12 +224,18 @@ d = { 'settings': { 'branch': 'main',
232
224
  'fastllm.openai_responses.norm_usage': ( 'oai_responses.html#norm_usage',
233
225
  'fastllm/openai_responses.py')},
234
226
  'fastllm.streaming': { 'fastllm.streaming.Delta': ('streaming.html#delta', 'fastllm/streaming.py'),
227
+ 'fastllm.streaming.Delta.__eq__': ('streaming.html#delta.__eq__', 'fastllm/streaming.py'),
228
+ 'fastllm.streaming.Delta.__hash__': ('streaming.html#delta.__hash__', 'fastllm/streaming.py'),
229
+ 'fastllm.streaming.Delta.__init__': ('streaming.html#delta.__init__', 'fastllm/streaming.py'),
235
230
  'fastllm.streaming.FenceToolStop': ('streaming.html#fencetoolstop', 'fastllm/streaming.py'),
236
231
  'fastllm.streaming.FenceToolStop.__call__': ( 'streaming.html#fencetoolstop.__call__',
237
232
  'fastllm/streaming.py'),
238
233
  'fastllm.streaming.FenceToolStop.__init__': ( 'streaming.html#fencetoolstop.__init__',
239
234
  'fastllm/streaming.py'),
240
235
  'fastllm.streaming.PartAccum': ('streaming.html#partaccum', 'fastllm/streaming.py'),
236
+ 'fastllm.streaming.PartAccum.__eq__': ('streaming.html#partaccum.__eq__', 'fastllm/streaming.py'),
237
+ 'fastllm.streaming.PartAccum.__hash__': ('streaming.html#partaccum.__hash__', 'fastllm/streaming.py'),
238
+ 'fastllm.streaming.PartAccum.__init__': ('streaming.html#partaccum.__init__', 'fastllm/streaming.py'),
241
239
  'fastllm.streaming.PartAccum.append': ('streaming.html#partaccum.append', 'fastllm/streaming.py'),
242
240
  'fastllm.streaming.PartAccum.get_merged': ( 'streaming.html#partaccum.get_merged',
243
241
  'fastllm/streaming.py'),
@@ -247,6 +245,9 @@ d = { 'settings': { 'branch': 'main',
247
245
  'fastllm.streaming.PrintStream.__init__': ( 'streaming.html#printstream.__init__',
248
246
  'fastllm/streaming.py'),
249
247
  'fastllm.streaming.Status': ('streaming.html#status', 'fastllm/streaming.py'),
248
+ 'fastllm.streaming.Status.__eq__': ('streaming.html#status.__eq__', 'fastllm/streaming.py'),
249
+ 'fastllm.streaming.Status.__hash__': ('streaming.html#status.__hash__', 'fastllm/streaming.py'),
250
+ 'fastllm.streaming.Status.__init__': ('streaming.html#status.__init__', 'fastllm/streaming.py'),
250
251
  'fastllm.streaming.Status.formatted': ('streaming.html#status.formatted', 'fastllm/streaming.py'),
251
252
  'fastllm.streaming._mk_delta_part': ('streaming.html#_mk_delta_part', 'fastllm/streaming.py'),
252
253
  'fastllm.streaming._trim_delta': ('streaming.html#_trim_delta', 'fastllm/streaming.py'),
@@ -259,12 +260,12 @@ d = { 'settings': { 'branch': 'main',
259
260
  'fastllm.types.APIRegistry.__missing__': ('types.html#apiregistry.__missing__', 'fastllm/types.py'),
260
261
  'fastllm.types.APIRegistry._load': ('types.html#apiregistry._load', 'fastllm/types.py'),
261
262
  'fastllm.types.APIRegistry.register': ('types.html#apiregistry.register', 'fastllm/types.py'),
262
- 'fastllm.types.Completion': ('types.html#completion', 'fastllm/types.py'),
263
263
  'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
264
264
  'fastllm.types.Completion.cost': ('types.html#completion.cost', 'fastllm/types.py'),
265
265
  'fastllm.types.Completion.formatted': ('types.html#completion.formatted', 'fastllm/types.py'),
266
- 'fastllm.types.Completion.tool_calls': ('types.html#completion.tool_calls', 'fastllm/types.py'),
267
266
  'fastllm.types.Usage': ('types.html#usage', 'fastllm/types.py'),
267
+ 'fastllm.types.Usage.__eq__': ('types.html#usage.__eq__', 'fastllm/types.py'),
268
+ 'fastllm.types.Usage.__init__': ('types.html#usage.__init__', 'fastllm/types.py'),
268
269
  'fastllm.types._rm_ctx_tiers': ('types.html#_rm_ctx_tiers', 'fastllm/types.py'),
269
270
  'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
270
271
  'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
@@ -16,7 +16,8 @@ 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, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo, InputFile, mk_tool_res_msg
19
+ from aidialog.msg_parts import (Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo,
20
+ InputFile, mk_tool_res_msg, Completion)
20
21
  from .streaming import *
21
22
  from .openai_responses import *
22
23
  from .openai_chat import *
@@ -5,7 +5,7 @@ __all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'f
5
5
  'norm_tr_parts', 'norm_sse_event', 'delta_index_fn', 'acollect_stream', 'denorm_tool_use',
6
6
  'denorm_assistant', 'denorm_tool', 'denorm_msgs', 'denorm_tool_schs', 'denorm_tool_choice',
7
7
  'denorm_reasoning', 'denorm_web_search', 'denorm_system', 'denorm_user', 'denorm_image', 'denorm_file',
8
- 'denorm_tool_result', 'mk_payload', 'get_hdrs', 'cost']
8
+ 'denorm_tool_result', 'apply_cache_idxs', 'mk_payload', 'get_hdrs', 'cost']
9
9
 
10
10
  # %% ../nbs/04_anthropic.ipynb #02afd3d7
11
11
  import json
@@ -271,12 +271,47 @@ def denorm_tool_result(p:ToolResult):
271
271
  return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=blocks), p)
272
272
  return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=tool_text(p.text)), p)
273
273
 
274
+ # %% ../nbs/04_anthropic.ipynb #e4eb8d3b
275
+ def _add_cache_control(
276
+ msg, # Canonical Msg to mark
277
+ ttl=None # Cache TTL: '5m' (default) or '1h'
278
+ ):
279
+ "Set a cache breakpoint on the last `Text` or `ToolUse` part of `msg`"
280
+ cc = {"type": "ephemeral"} | ({"ttl": ttl} if ttl else {})
281
+ idxs = [i for i,p in enumerate(msg.content) if isinstance(p, (Text, ToolUse))]
282
+ if idxs: msg.content[idxs[-1]].cache_control = cc
283
+ return msg
284
+
285
+ # %% ../nbs/04_anthropic.ipynb #6bc52ef5
286
+ def apply_cache_idxs(
287
+ msgs, # Canonical Msgs to mark
288
+ cache_idxs=[-1], # Breakpoint idxs into `msgs` (tool-role msgs excluded); 0 is `sp` when given
289
+ sp=None, # System prompt: str, Msg, or Part
290
+ ttl=None # Cache TTL: '5m' (default) or '1h'
291
+ ):
292
+ "Strip all cache breakpoints from `msgs`, then mark `cache_idxs`; returns `sp`, marked if 0 is included"
293
+ for m in msgs:
294
+ for p in m.content: p.cache_control = None
295
+ if sp:
296
+ if 0 in cache_idxs:
297
+ if isinstance(sp, str): sp = Text(sp)
298
+ if isinstance(sp, Part): sp = Msg('', [sp])
299
+ _add_cache_control(sp, ttl)
300
+ cache_idxs = [o-1 if o>0 else o for o in cache_idxs if o]
301
+ ms = [j for j,m in enumerate(msgs) if m.role != 'tool']
302
+ for i in cache_idxs:
303
+ try: _add_cache_control(msgs[ms[i]], ttl)
304
+ except IndexError: continue
305
+ return sp
306
+
274
307
  # %% ../nbs/04_anthropic.ipynb #587d4fe5
275
308
  @delegates(payload_kwargs)
276
309
  def mk_payload(msgs, model, **kwargs):
310
+ sp = kwargs.get('system')
311
+ if (ci:=kwargs.get('cache_idxs')) is not None: sp = apply_cache_idxs(msgs, ci, sp, kwargs.get('ttl'))
277
312
  payload = dict(model=model, messages=denorm_msgs(msgs), max_tokens=kwargs.get('max_tokens') or 1024)
278
313
  if kwargs.get('stream'): payload['stream'] = True
279
- if sp:=kwargs.get('system'): payload['system'] = denorm_system(sp)
314
+ if sp: payload['system'] = denorm_system(sp)
280
315
  if tools:=kwargs.get('tools'): payload['tools'] = denorm_tool_schs(tools)
281
316
  if tchc:=kwargs.get('tool_choice'): payload['tool_choice'] = denorm_tool_choice(tchc)
282
317
  if thk:=kwargs.get('reasoning_effort'): payload.update(denorm_reasoning(thk))
@@ -3,49 +3,24 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/07_chat.ipynb.
4
4
 
5
5
  # %% auto #0
6
- __all__ = ['effort', 'remove_cache_ckpts', 'contents', 'stop_reason', 'mk_msg', 'mk_msgs', 'cite_footnote', 'postproc', 'Refresh',
7
- 'think_sep', 'lite_mk_func', 'structured', 'search_count', 'UsageStats', 'AsyncChat',
8
- 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback', 'add_warning',
9
- 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback', 'StopSequencesCallback',
10
- 'StreamAccum', 'adisplay_stream']
6
+ __all__ = ['effort', 'contents', 'stop_reason', 'cite_footnote', 'postproc', 'Refresh', 'think_sep', 'lite_mk_func', 'structured',
7
+ 'search_count', 'UsageStats', 'AsyncChat', 'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback',
8
+ 'add_warning', 'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback',
9
+ 'StopSequencesCallback', 'StreamAccum', 'adisplay_stream']
11
10
 
12
11
  # %% ../nbs/07_chat.ipynb #d5a3bc1f
13
12
  from typing import Optional,Callable
14
13
  from fastcore.funccall import mk_ns, call_func, call_func_async, get_schema
15
14
  from fastcore.utils import *
16
15
  from fastcore.meta import delegates
17
- from dataclasses import dataclass
18
16
 
19
17
  from .types import *
20
- from aidialog.msg_parts import (Msg, Part, PartType, Text, Thinking, ToolUse, ToolResult, InputImage, tool_text, mk_tool_res_msg, mk_tr_details,
21
- tool_info, usage_info, think_start, think_end, StopResponse, ToolResponse, display_list, fmt2hist, hist2fmt,
22
- mk_content, mk_result_fence, split_fence_msgs, extract_fence_call, trunc_str)
18
+ from aidialog.msg_parts import (Msg, Part, PartType, Text, Thinking, ToolUse, tool_text, mk_tool_res_msg, tool_info, usage_info,
19
+ StopResponse, ToolResponse, display_list, fmt2hist, hist2fmt, mk_result_fence, split_fence_msgs, extract_fence_call,
20
+ trunc_str, Completion, mk_msg, mk_msgs)
23
21
  from .acomplete import *
24
22
  from .streaming import FenceToolStop, stop_sequences
25
23
 
26
- # %% ../nbs/07_chat.ipynb #43a55cda
27
- def _add_cache_control(
28
- msg, # LiteLLM formatted msg
29
- ttl=None # Cache TTL: '5m' (default) or '1h'
30
- ):
31
- "cache `msg` with default time-to-live (ttl) of 5minutes ('5m'), but can be set to '1h'."
32
- cc = {"type": "ephemeral"} | ({"ttl": ttl} if ttl else {})
33
- cache_idx = None
34
- for idx, part in enumerate(msg.content):
35
- if isinstance(part, (Text, ToolUse)): cache_idx = idx
36
- msg.content[idx].cache_control = cc
37
- return msg
38
-
39
- def _has_cache(msg):
40
- "Check if msg has cache_control set"
41
- return any(part.cache_control for part in msg.content)
42
-
43
- def remove_cache_ckpts(msg):
44
- "remove cache checkpoints and return msg."
45
- for part in msg.content: part.cache_control = None
46
- return msg
47
-
48
-
49
24
  # %% ../nbs/07_chat.ipynb #48c78e48
50
25
  def contents(c):
51
26
  "Get Msg object from Completion."
@@ -56,51 +31,6 @@ def stop_reason(c):
56
31
  if not c.finish_reason: return 'unk'
57
32
  return c.finish_reason
58
33
 
59
- # %% ../nbs/07_chat.ipynb #4a1c44ea
60
- def mk_msg(
61
- content, # Content: str, bytes (image), list of mixed content, or dict w 'role' and 'content' fields
62
- role="user", # Message role if content isn't already a dict/Message
63
- cache=False, # Enable Anthropic caching
64
- ttl=None # Cache TTL: '5m' (default) or '1h'
65
- ):
66
- "Create a LiteLLM compatible message."
67
- if content is None: return None
68
- if isinstance(content, Msg): return content
69
- if isinstance(content, Completion): return content.message
70
- if isinstance(content, list) and len(content) == 1 and isinstance(content[0], str): parts = [Text(content[0])]
71
- elif isinstance(content, list): parts = [mk_content(o) for o in content]
72
- elif isinstance(content, dict): return Msg(role=content['role'], content=[Text(content['content'])])
73
- else: parts = [Text(content)]
74
- msg = Msg(role=role, content=parts)
75
- return _add_cache_control(msg, ttl=ttl) if cache else msg
76
-
77
- # %% ../nbs/07_chat.ipynb #01bd81de
78
- def _apply_cache_idxs(msgs, cache_idxs=[-1], ttl=None):
79
- "Add cache control to `cache_idxs` after filtering tool-role msgs"
80
- ms = [j for j,m in enumerate(msgs) if m.role != 'tool']
81
- for i in cache_idxs:
82
- try: idx = ms[i]
83
- except IndexError: continue
84
- _add_cache_control(msgs[idx], ttl)
85
-
86
- # %% ../nbs/07_chat.ipynb #fcbb9f5d
87
- def mk_msgs(
88
- msgs, # List of messages (each: str, bytes, list, Msg, or Completion)
89
- cache=False, # Enable Anthropic caching
90
- cache_idxs=[-1], # Cache breakpoint idxs
91
- ttl=None, # Cache TTL: '5m' (default) or '1h'
92
- ):
93
- "Create a list of fastllm canonical Msgs."
94
- if not msgs: return []
95
- if not isinstance(msgs, list): msgs = [msgs]
96
- 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()
97
- res, role = [], 'user'
98
- for m in msgs:
99
- res.append(msg := remove_cache_ckpts(mk_msg(m, role=role)))
100
- role = 'assistant' if msg.role in ('user','tool') else 'user'
101
- if cache: _apply_cache_idxs(res, cache_idxs, ttl)
102
- return res
103
-
104
34
  # %% ../nbs/07_chat.ipynb #447aed6e
105
35
  def cite_footnote(citations):
106
36
  'Build citation footnotes for a single Delta'
@@ -172,9 +102,6 @@ def _has_search(info): return bool(info.get('search_context_cost_per_query') or
172
102
  effort = AttrDict({o[0]:o for o in ('low','medium','high')})
173
103
  effort['x'] = 'max'
174
104
 
175
- # %% ../nbs/07_chat.ipynb #e1facb77
176
- def _mk_prefill(pf): return Text(pf)
177
-
178
105
  # %% ../nbs/07_chat.ipynb #dc17f844
179
106
  def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in tres_parts)
180
107
 
@@ -255,7 +182,7 @@ class AsyncChat:
255
182
  if v in vendor_mapping: vendor_name = v
256
183
  elif v: api_name = v # a registered transport api (e.g. claude_code)
257
184
  self.model = model
258
- hist,tools = mk_msgs(hist,cache,cache_idxs,ttl),listify(tools)
185
+ hist,tools = mk_msgs(hist),listify(tools)
259
186
  if ns is None and tools: ns = mk_ns(tools)
260
187
  elif ns is None: ns = globals()
261
188
  self.tool_schemas = [lite_mk_func(t) for t in tools] if tools else None
@@ -267,18 +194,11 @@ class AsyncChat:
267
194
  if default_cbs: self.add_cbs(defaults.chat_callbacks)
268
195
  self.add_cbs(cbs)
269
196
 
270
- def _prep_msg(self, msg=None, prefill=None):
197
+ def _prep_msg(self, msg=None):
271
198
  "Prepare the system prompt and messages list for the API call"
272
- sp = self.sp
273
- if sp:
274
- if 0 in self.cache_idxs: sp = _add_cache_control(Msg('',[Text(sp)]))
275
- cache_idxs = L(self.cache_idxs).filter().map(lambda o: o-1 if o>0 else o)
276
- else: cache_idxs = self.cache_idxs
277
199
  if msg: self.hist = self.hist+[msg]
278
- self.hist = mk_msgs(self.hist, self.cache and 'claude' in self.model, cache_idxs, self.ttl)
279
- msgs = self.hist
280
- if prefill: msgs = self.hist + [Msg(role='assistant', content=[Text(prefill)])]
281
- return sp, msgs
200
+ self.hist = mk_msgs(self.hist)
201
+ return self.sp, self.hist
282
202
 
283
203
  @property
284
204
  def tcdict(self): return dict(tool_schemas=self.tool_schemas, ns=self.ns)
@@ -317,11 +237,10 @@ def _think_kw(model, think, vendor_name):
317
237
 
318
238
  # %% ../nbs/07_chat.ipynb #06e898fd
319
239
  @patch
320
- def _prep_call(self:AsyncChat, prefill, search, max_tokens, kwargs, stream=False, think=None):
321
- "Prepare model info, prefill, search, and provider kwargs for a completion call"
240
+ def _prep_call(self:AsyncChat, search, max_tokens, kwargs, stream=False, think=None):
241
+ "Prepare model info, search, and provider kwargs for a completion call"
322
242
  model_info = get_model_info(self.model, self.vendor_name)
323
243
  if max_tokens is None: max_tokens = ifnone(model_info.get('max_output_tokens'), 32_000)
324
- if not model_info.get("supports_assistant_prefill"): prefill = None
325
244
  if _has_search(model_info) and (s:=ifnone(search,self.search)):
326
245
  if 'web_search_options' not in kwargs: kwargs['web_search_options'] = {}
327
246
  kwargs['web_search_options']['search_context_size'] = effort[s]
@@ -333,7 +252,7 @@ def _prep_call(self:AsyncChat, prefill, search, max_tokens, kwargs, stream=False
333
252
  if self.base_url: kwargs['base_url'] = self.base_url
334
253
  if self.extra_headers: kwargs['xtra_headers'] = self.extra_headers
335
254
  kwargs.update(_think_kw(self.model, think, self.vendor_name))
336
- return prefill, max_tokens
255
+ return max_tokens
337
256
 
338
257
  # %% ../nbs/07_chat.ipynb #07951b77
339
258
  @patch
@@ -357,26 +276,25 @@ async def astream_with_complete(self, agen, postproc=noop):
357
276
  # %% ../nbs/07_chat.ipynb #a049cf52
358
277
  @patch
359
278
  @delegates(acomplete)
360
- async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, search=None, stream=False, max_steps=2, step=1,
279
+ async def _call(self:AsyncChat, msg=None, temp=None, think=None, search=None, stream=False, max_steps=2, step=1,
361
280
  final_prompt=None, tool_choice=None, max_tokens=None, n_workers=8, pause=0.001, tc_timeout=7200, **kwargs):
362
281
  if step>max_steps+1: return
363
- self.prefill, max_tokens = self._prep_call(prefill, search, max_tokens, kwargs, stream=stream, think=think)
364
- self.turn_sysp, self.turn_msgs = self._prep_msg(msg, prefill)
282
+ max_tokens = self._prep_call(search, max_tokens, kwargs, stream=stream, think=think)
283
+ self.turn_sysp, self.turn_msgs = self._prep_msg(msg)
365
284
  async for o in self._call_cbs('after_msgs'): yield o
366
285
 
367
286
  self.turn_kwargs, self.stream = kwargs, stream
368
287
  async for o in self._call_cbs('before_acomplete'): yield o
369
288
  res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream, tools=self.tool_schemas,
370
- tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if think else ifnone(temp,self.temp), **self.turn_kwargs)
289
+ tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if think else ifnone(temp,self.temp),
290
+ cache_idxs=self.cache_idxs if self.cache else [], ttl=self.ttl, **self.turn_kwargs)
371
291
  if stream:
372
- if self.prefill: yield _mk_prefill(self.prefill)
373
292
  res = astream_with_complete(res, postproc=postproc)
374
293
  async for chunk in res:
375
294
  if isinstance(chunk, Thinking) and self.showthink: chunk.showthink = True
376
295
  yield chunk
377
296
  res = res.value
378
297
  self.turn_res, self.turn_msg = res, contents(res)
379
- if self.prefill: self.turn_msg.content[0].text = self.prefill + self.turn_msg.content[0].text
380
298
  self.hist.append(self.turn_msg)
381
299
  async for o in self._call_cbs('after_acomplete'): yield o
382
300
  self._track(self.turn_res)
@@ -397,14 +315,14 @@ async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, s
397
315
  if self.toolloop and step <= max_steps:
398
316
  try:
399
317
  async for result in self._call(
400
- self.prompt, None, temp, think, search, stream, max_steps, step+1,
318
+ self.prompt, temp, think, search, stream, max_steps, step+1,
401
319
  final_prompt, tool_choice=tool_choice, **kwargs): yield result
402
320
  except ContextWindowExceededError:
403
321
  if tmsg is not None:
404
322
  for p in tmsg.content:
405
323
  if len(p.text)>1000: p.text = _cwe_msg + trunc_str(p.text, mx=1000)
406
324
  async for result in self._call(
407
- self.prompt, None, temp, think, search, stream, max_steps, step+1,
325
+ self.prompt, temp, think, search, stream, max_steps, step+1,
408
326
  final_prompt, tool_choice='none', **kwargs): yield result
409
327
 
410
328
  # %% ../nbs/07_chat.ipynb #1361515a
@@ -413,7 +331,6 @@ async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, s
413
331
  async def __call__(
414
332
  self:AsyncChat,
415
333
  msg=None, # Message str, or list of multiple message parts
416
- prefill=None, # Prefill AI response if model supports it
417
334
  temp=None, # Override temp set on chat initialization
418
335
  think=None, # Thinking (l,m,h)
419
336
  search=None, # Override search set on chat initialization (l,m,h)
@@ -425,7 +342,7 @@ async def __call__(
425
342
  ):
426
343
  self.use = UsageStats()
427
344
  self._turn_start = len(self.hist)
428
- result_gen = self._call(msg, prefill, temp, think, search, stream, max_steps, 1, final_prompt, **kwargs)
345
+ result_gen = self._call(msg, temp, think, search, stream, max_steps, 1, final_prompt, **kwargs)
429
346
  if stream or return_all: return result_gen
430
347
  async for res in result_gen: pass
431
348
  return res # normal chat behavior only return last msg
@@ -458,14 +375,6 @@ class DeepseekMsgsCallback(ChatCallback):
458
375
  if m.role=='assistant' and not any(isinstance(p, Thinking) for p in m.content): m.content.append(Thinking(''))
459
376
  if False: yield
460
377
 
461
- # %% ../nbs/07_chat.ipynb #14baac3e
462
- class DeepseekPrefillCallback(ChatCallback):
463
- order = 10
464
- async def before_acomplete(self):
465
- if self.prefill and self.vendor_name == 'deepseek' and self.model.startswith("deepseek-"):
466
- self.chat.turn_kwargs['base_url'] = 'https://api.deepseek.com/beta'
467
- if False: yield
468
-
469
378
  # %% ../nbs/07_chat.ipynb #ce47dc4a
470
379
  def add_warning(r, msg):
471
380
  wrn = Text(f"<warning>{msg}</warning>")
@@ -565,7 +474,7 @@ class StopSequencesCallback(ChatCallback):
565
474
  if False: yield
566
475
 
567
476
  # %% ../nbs/07_chat.ipynb #318ee856
568
- defaults.chat_callbacks = [DeepseekPrefillCallback, FenceToolCallback, ToolReminderCallback, StopReasonCallback]
477
+ defaults.chat_callbacks = [FenceToolCallback, ToolReminderCallback, StopReasonCallback]
569
478
 
570
479
  # %% ../nbs/07_chat.ipynb #ead4b56d
571
480
  class StreamAccum:
@@ -8,26 +8,21 @@ __all__ = ['Delta', 'norm_and_yield', 'PrintStream', 'PartAccum', 'FenceToolStop
8
8
 
9
9
  # %% ../nbs/01_streaming.ipynb #0df5c926
10
10
  import json,copy
11
- from dataclasses import dataclass, field, fields
12
11
  from fastcore.utils import *
13
12
  from fastcore.meta import delegates
14
13
  from fastspec.errors import *
15
14
  from .types import *
16
- from aidialog.msg_parts import Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, ServerToolResult, mk_part, fence_call_re
15
+ from aidialog.msg_parts import Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, ServerToolResult, mk_part, fence_call_re, Completion
17
16
 
18
17
  # %% ../nbs/01_streaming.ipynb #400d628a
19
- @dataclass
20
- class Delta:
18
+ class Delta(BasicRepr):
21
19
  "Normalized streaming delta event."
22
- text: str = ""
23
- thinking: str = ""
24
- refusal: str = ""
25
- tool_calls: list[ToolUse] = field(default_factory=list)
26
- citations: list = field(default_factory=list)
27
- server_tool_result: dict = None
28
- finish_reason: str = None
29
- usage: Usage = None
30
- raw: dict = field(default_factory=dict)
20
+ def __init__(self, text='', thinking='', refusal='', tool_calls=None, citations=None,
21
+ server_tool_result=None, finish_reason=None, usage=None, raw=None):
22
+ store_attr()
23
+ self.tool_calls,self.citations,self.raw = ifnone(tool_calls,[]),ifnone(citations,[]),ifnone(raw,{})
24
+ def __eq__(self, o): return type(o) is type(self) and self.__dict__ == o.__dict__
25
+ def __hash__(self): return hash((self.text, self.thinking, self.finish_reason))
31
26
 
32
27
  # %% ../nbs/01_streaming.ipynb #f59f837b
33
28
  async def norm_and_yield(resp, norm_func):
@@ -47,10 +42,13 @@ class PrintStream:
47
42
  if f: print(f, end='', flush=True)
48
43
 
49
44
  # %% ../nbs/01_streaming.ipynb #a7f1738a
50
- @dataclass
51
- class PartAccum:
45
+ class PartAccum(BasicRepr):
52
46
  "Accumulate streamed part deltas by index"
53
- parts: dict = field(default_factory=dict)
47
+ def __init__(self, parts=None):
48
+ store_attr()
49
+ self.parts = ifnone(parts, {})
50
+ def __eq__(self, o): return type(o) is type(self) and self.__dict__ == o.__dict__
51
+ def __hash__(self): return hash(len(self.parts))
54
52
 
55
53
  def append(self, typ, index, txt='', citations=None, **tc_kwargs):
56
54
  'Create and accumulate same type sequential parts'
@@ -117,11 +115,12 @@ def stop_sequences(seqs):
117
115
  return _stop
118
116
 
119
117
  # %% ../nbs/01_streaming.ipynb #90f91955
120
- @dataclass
121
- class Status:
118
+ class Status(BasicRepr):
122
119
  "Stream marker for activity that isn't model content (e.g. draining deltas after a stop sequence)"
123
- status: str
124
120
  type = 'status'
121
+ def __init__(self, status): store_attr()
122
+ def __eq__(self, o): return type(o) is type(self) and self.__dict__ == o.__dict__
123
+ def __hash__(self): return hash(self.status)
125
124
  @property
126
125
  def formatted(self): return '🧠'
127
126
 
@@ -6,51 +6,29 @@
6
6
  __all__ = ['FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45', 'sonn46', 'sonn', 'sonn5', 'opus46', 'opus48',
7
7
  'opus', 'opus5', 'fable', 'fable5', 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55',
8
8
  'codex53spark', 'model_info_registry', 'modern_llm', 'deepseek_v4_common', 'mimo_v25_common',
9
- 'codex_pricing', 'sol', 'terra', 'luna', 'gpt56s', 'Usage', 'Completion', 'APIRegistry', 'mk_completion',
10
- 'fn_schema', 'payload_kwargs', 'get_api_key', 'resize_b64', 'model_prices_meta', 'infer_api_name',
11
- 'get_model_meta', 'register_model_info', 'get_model_info', 'get_model_pricing', 'approx_pricing',
12
- 'is_deepseek_peak_hour', 'price_tier', 'tier_rate']
9
+ 'codex_pricing', 'sol', 'terra', 'luna', 'gpt56s', 'Usage', 'APIRegistry', 'mk_completion', 'fn_schema',
10
+ 'payload_kwargs', 'get_api_key', 'resize_b64', 'model_prices_meta', 'infer_api_name', 'get_model_meta',
11
+ 'register_model_info', 'get_model_info', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour',
12
+ 'price_tier', 'tier_rate']
13
13
 
14
14
  # %% ../nbs/00_types.ipynb #b4d047fd
15
15
  import httpx, base64, io
16
16
  from importlib.metadata import entry_points
17
- from dataclasses import dataclass, field
18
17
  from datetime import datetime, timezone
19
- from fastcore.net import urljson
20
18
  from fastcore.utils import *
21
19
  from PIL import Image as PImg
22
- from aidialog.msg_parts import Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo, InputFile, data_url
20
+ from aidialog.msg_parts import Msg, Thinking, ToolUse, Completion
23
21
 
24
22
 
25
23
  # %% ../nbs/00_types.ipynb #802ad832
26
- @dataclass(frozen=True)
27
- class Usage:
24
+ class Usage(BasicRepr):
28
25
  "Normalized usage."
29
- prompt_tokens: int = 0
30
- completion_tokens: int = 0
31
- total_tokens: int = 0
32
- cached_tokens: int = 0
33
- cache_creation_tokens: int = 0
34
- reasoning_tokens: int = 0
35
- raw: dict = field(default_factory=dict)
36
-
37
-
38
- # %% ../nbs/00_types.ipynb #297a1672
39
- @dataclass(frozen=True)
40
- class Completion:
41
- "Normalized completion response."
42
- model: str
43
- message: Msg
44
- finish_reason: str = None
45
- usage: Usage = None
46
- api_name: str = None
47
- vendor_name: str = None
48
- raw: dict = field(default_factory=dict)
49
-
50
- @property
51
- def tool_calls(self):
52
- "The `ToolUse` parts of `message`: a call lives in the content, not beside it"
53
- return [p for p in self.message.content if isinstance(p, ToolUse)]
26
+ def __init__(self, prompt_tokens=0, completion_tokens=0, total_tokens=0, cached_tokens=0,
27
+ cache_creation_tokens=0, reasoning_tokens=0, raw=None):
28
+ store_attr()
29
+ self.raw = ifnone(raw, {})
30
+ def __eq__(self, o): return type(o) is type(self) and self.__dict__ == o.__dict__
31
+
54
32
 
55
33
  # %% ../nbs/00_types.ipynb #4901e693
56
34
  @patch(as_prop=True)
@@ -62,18 +40,18 @@ def _repr_markdown_(self: Completion):
62
40
  content = ''
63
41
  for p in self.message.content:
64
42
  if isinstance(p, Thinking):
65
- if p.text: content += f"<details><summary>Thinking</summary>\n\n{p.text}\n\n</details>\n\n"
43
+ if p.text: content += f"\n::: details\n## Thinking\n\n{p.text}\n\n:::\n\n"
66
44
  elif isinstance(p, ToolUse): content += f"\n\n🔧 {p.name}({p.arguments})\n"
67
45
  elif txt := p.text: content += txt
68
46
  details = [f"model: `{self.model}`", f"finish_reason: `{self.finish_reason}`", f"usage: `{self.usage}`"]
69
47
  det_str = '\n- '.join(details)
70
48
  return f"""{content}
71
49
 
72
- <details markdown='1'>
50
+ ::: details
73
51
 
74
52
  - {det_str}
75
53
 
76
- </details>"""
54
+ :::"""
77
55
 
78
56
  # %% ../nbs/00_types.ipynb #ce59e431
79
57
  FinishReason = str_enum('finish_reason', 'stop', 'tool_calls', 'length', 'content_filter')
@@ -123,7 +101,8 @@ def fn_schema(t):
123
101
  return None
124
102
 
125
103
  # %% ../nbs/00_types.ipynb #28c698fe
126
- 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
104
+ def payload_kwargs(msgs, model, stream=False, system=None, max_tokens=None, temperature=None, tools=None, tool_choice=None,
105
+ reasoning_effort=None, web_search_options=None, cache_idxs=None, ttl=None, stop_callables=None): pass
127
106
 
128
107
  # %% ../nbs/00_types.ipynb #c2a2cb49
129
108
  def get_api_key(api_key, default):
@@ -234,7 +213,7 @@ register_model_info('gpt-5.4-mini', vendor_name='openai', base='gpt-5.4-mini', s
234
213
 
235
214
  for model in ('kimi-k2.5', 'kimi-k2.6' , 'kimi-k3'):
236
215
  register_model_info(model, vendor_name='moonshot', base=f'moonshot/{model}', base_vendor_name=None,
237
- supports_reasoning=True, supports_vision=True, supports_assistant_prefill=True)
216
+ supports_reasoning=True, supports_vision=True)
238
217
 
239
218
  register_model_info('gemini-3.1-flash-lite', vendor_name='gemini', base='gemini-3.1-flash-lite-preview')
240
219
  register_model_info('models/gemini-3.1-flash-lite', vendor_name='gemini', base='gemini-3.1-flash-lite-preview')
@@ -252,8 +231,7 @@ register_model_info('kimi-k3', vendor_name='moonshot', base='kimi-k2.6',
252
231
  input_cost_per_token=3e-6, cache_read_input_token_cost=0.30e-6, output_cost_per_token=15.0e-6)
253
232
 
254
233
  # %% ../nbs/00_types.ipynb #948d55d0
255
- deepseek_v4_common = dict(**modern_llm, supports_assistant_prefill=True,
256
- max_input_tokens=1048576, max_output_tokens=393216, max_tokens=393216)
234
+ deepseek_v4_common = dict(**modern_llm, max_input_tokens=1048576, max_output_tokens=393216, max_tokens=393216)
257
235
 
258
236
  register_model_info('deepseek-v4-flash', vendor_name='deepseek', base='deepseek/deepseek-v3.2', **deepseek_v4_common,
259
237
  input_cost_per_token=1.4e-07, input_cost_per_token_cache_hit=2.8e-09,
@@ -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.18', 'aidialog>=0.0.8', 'fastspec>=0.0.11', 'pillow']
18
+ dependencies = ['fastcore>=2.1.18', 'aidialog>=0.0.10', 'fastspec>=0.0.11', '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.38
3
+ Version: 0.0.39
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.18
13
- Requires-Dist: aidialog>=0.0.8
13
+ Requires-Dist: aidialog>=0.0.10
14
14
  Requires-Dist: fastspec>=0.0.11
15
15
  Requires-Dist: pillow
16
16
 
@@ -1,4 +1,4 @@
1
1
  fastcore>=2.1.18
2
- aidialog>=0.0.8
2
+ aidialog>=0.0.10
3
3
  fastspec>=0.0.11
4
4
  pillow
@@ -1 +0,0 @@
1
- __version__ = "0.0.38"