python-fastllm 0.0.31__py3-none-any.whl → 0.0.33__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastllm/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.31"
1
+ __version__ = "0.0.33"
fastllm/_modidx.py CHANGED
@@ -78,14 +78,9 @@ d = { 'settings': { 'branch': 'main',
78
78
  'fastllm.chat.FenceToolStop': ('chat.html#fencetoolstop', 'fastllm/chat.py'),
79
79
  'fastllm.chat.FenceToolStop.__call__': ('chat.html#fencetoolstop.__call__', 'fastllm/chat.py'),
80
80
  'fastllm.chat.FenceToolStop.__init__': ('chat.html#fencetoolstop.__init__', 'fastllm/chat.py'),
81
- 'fastllm.chat.FullResponse': ('chat.html#fullresponse', 'fastllm/chat.py'),
82
- 'fastllm.chat.MediaUrl': ('chat.html#mediaurl', 'fastllm/chat.py'),
83
- 'fastllm.chat.MediaUrl.__init__': ('chat.html#mediaurl.__init__', 'fastllm/chat.py'),
84
- 'fastllm.chat.Msg.text': ('chat.html#msg.text', 'fastllm/chat.py'),
85
81
  'fastllm.chat.StopReasonCallback': ('chat.html#stopreasoncallback', 'fastllm/chat.py'),
86
82
  'fastllm.chat.StopReasonCallback.after_acomplete': ( 'chat.html#stopreasoncallback.after_acomplete',
87
83
  'fastllm/chat.py'),
88
- 'fastllm.chat.StopResponse': ('chat.html#stopresponse', 'fastllm/chat.py'),
89
84
  'fastllm.chat.StopSequencesCallback': ('chat.html#stopsequencescallback', 'fastllm/chat.py'),
90
85
  'fastllm.chat.StopSequencesCallback.__init__': ( 'chat.html#stopsequencescallback.__init__',
91
86
  'fastllm/chat.py'),
@@ -113,7 +108,6 @@ d = { 'settings': { 'branch': 'main',
113
108
  'fastllm.chat._apply_cache_idxs': ('chat.html#_apply_cache_idxs', 'fastllm/chat.py'),
114
109
  'fastllm.chat._bytes2content': ('chat.html#_bytes2content', 'fastllm/chat.py'),
115
110
  'fastllm.chat._call_func': ('chat.html#_call_func', 'fastllm/chat.py'),
116
- 'fastllm.chat._extract_tool_parts': ('chat.html#_extract_tool_parts', 'fastllm/chat.py'),
117
111
  'fastllm.chat._handle_stop_reason': ('chat.html#_handle_stop_reason', 'fastllm/chat.py'),
118
112
  'fastllm.chat._has_cache': ('chat.html#_has_cache', 'fastllm/chat.py'),
119
113
  'fastllm.chat._has_search': ('chat.html#_has_search', 'fastllm/chat.py'),
@@ -123,15 +117,10 @@ d = { 'settings': { 'branch': 'main',
123
117
  'fastllm.chat._mime2part_type': ('chat.html#_mime2part_type', 'fastllm/chat.py'),
124
118
  'fastllm.chat._mk_content': ('chat.html#_mk_content', 'fastllm/chat.py'),
125
119
  'fastllm.chat._mk_prefill': ('chat.html#_mk_prefill', 'fastllm/chat.py'),
126
- 'fastllm.chat._mk_result_fence': ('chat.html#_mk_result_fence', 'fastllm/chat.py'),
127
120
  'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
128
- 'fastllm.chat._split_fence_msgs': ('chat.html#_split_fence_msgs', 'fastllm/chat.py'),
129
- 'fastllm.chat._split_msg_on_fences': ('chat.html#_split_msg_on_fences', 'fastllm/chat.py'),
130
121
  'fastllm.chat._tc_summary': ('chat.html#_tc_summary', 'fastllm/chat.py'),
131
122
  'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
132
- 'fastllm.chat._trunc_content': ('chat.html#_trunc_content', 'fastllm/chat.py'),
133
123
  'fastllm.chat._trunc_param': ('chat.html#_trunc_param', 'fastllm/chat.py'),
134
- 'fastllm.chat._trunc_str': ('chat.html#_trunc_str', 'fastllm/chat.py'),
135
124
  'fastllm.chat._url2content': ('chat.html#_url2content', 'fastllm/chat.py'),
136
125
  'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
137
126
  'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
@@ -140,17 +129,13 @@ d = { 'settings': { 'branch': 'main',
140
129
  'fastllm.chat.cite_footnote': ('chat.html#cite_footnote', 'fastllm/chat.py'),
141
130
  'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
142
131
  'fastllm.chat.extract_fence_call': ('chat.html#extract_fence_call', 'fastllm/chat.py'),
143
- 'fastllm.chat.fmt2hist': ('chat.html#fmt2hist', 'fastllm/chat.py'),
144
- 'fastllm.chat.hist2fmt': ('chat.html#hist2fmt', 'fastllm/chat.py'),
145
132
  'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
146
133
  'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
147
134
  'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
148
- 'fastllm.chat.mk_tr_details': ('chat.html#mk_tr_details', 'fastllm/chat.py'),
149
135
  'fastllm.chat.postproc': ('chat.html#postproc', 'fastllm/chat.py'),
150
136
  'fastllm.chat.remove_cache_ckpts': ('chat.html#remove_cache_ckpts', 'fastllm/chat.py'),
151
137
  'fastllm.chat.run_fence_tool': ('chat.html#run_fence_tool', 'fastllm/chat.py'),
152
138
  'fastllm.chat.search_count': ('chat.html#search_count', 'fastllm/chat.py'),
153
- 'fastllm.chat.split_tools': ('chat.html#split_tools', 'fastllm/chat.py'),
154
139
  'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
155
140
  'fastllm.chat.stop_sequences': ('chat.html#stop_sequences', 'fastllm/chat.py'),
156
141
  'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py')},
@@ -273,18 +258,8 @@ d = { 'settings': { 'branch': 'main',
273
258
  'fastllm.types.Completion': ('types.html#completion', 'fastllm/types.py'),
274
259
  'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
275
260
  'fastllm.types.Completion.cost': ('types.html#completion.cost', 'fastllm/types.py'),
276
- 'fastllm.types.Msg': ('types.html#msg', 'fastllm/types.py'),
277
- 'fastllm.types.Msg._repr_markdown_': ('types.html#msg._repr_markdown_', 'fastllm/types.py'),
278
- 'fastllm.types.Part': ('types.html#part', 'fastllm/types.py'),
279
- 'fastllm.types.Part._repr_markdown_': ('types.html#part._repr_markdown_', 'fastllm/types.py'),
280
- 'fastllm.types.ToolCall': ('types.html#toolcall', 'fastllm/types.py'),
281
- 'fastllm.types.ToolCall._repr_markdown_': ('types.html#toolcall._repr_markdown_', 'fastllm/types.py'),
282
261
  'fastllm.types.Usage': ('types.html#usage', 'fastllm/types.py'),
283
- 'fastllm.types._fetch_url_partial': ('types.html#_fetch_url_partial', 'fastllm/types.py'),
284
- 'fastllm.types._trunc_strs': ('types.html#_trunc_strs', 'fastllm/types.py'),
285
262
  'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
286
- 'fastllm.types.data_url': ('types.html#data_url', 'fastllm/types.py'),
287
- 'fastllm.types.display_list': ('types.html#display_list', 'fastllm/types.py'),
288
263
  'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
289
264
  'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
290
265
  'fastllm.types.get_model_info': ('types.html#get_model_info', 'fastllm/types.py'),
@@ -293,11 +268,7 @@ d = { 'settings': { 'branch': 'main',
293
268
  'fastllm.types.infer_api_name': ('types.html#infer_api_name', 'fastllm/types.py'),
294
269
  'fastllm.types.is_deepseek_peak_hour': ('types.html#is_deepseek_peak_hour', 'fastllm/types.py'),
295
270
  'fastllm.types.mk_completion': ('types.html#mk_completion', 'fastllm/types.py'),
296
- 'fastllm.types.mk_tool_res_msg': ('types.html#mk_tool_res_msg', 'fastllm/types.py'),
297
271
  'fastllm.types.model_prices_meta': ('types.html#model_prices_meta', 'fastllm/types.py'),
298
- 'fastllm.types.part_txt': ('types.html#part_txt', 'fastllm/types.py'),
299
272
  'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
300
273
  'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
301
- 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
302
- 'fastllm.types.sys_text': ('types.html#sys_text', 'fastllm/types.py'),
303
- 'fastllm.types.url_mime': ('types.html#url_mime', 'fastllm/types.py')}}}
274
+ 'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py')}}}
fastllm/acomplete.py CHANGED
@@ -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 *
fastllm/anthropic.py CHANGED
@@ -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
 
fastllm/chat.py CHANGED
@@ -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:
@@ -727,44 +614,13 @@ def _trunc_param(v, mx=40):
727
614
 
728
615
  # %% ../nbs/07_chat.ipynb #80c0abdb
729
616
  def _tc_summary(tr):
730
- "Format tool call as func(params) result string"
617
+ "Format tool call as a `func(params)→result` code span"
731
618
  params = ', '.join(f"{k}={_trunc_param(v)}" for k,v in tr.data['arguments'].items())
732
619
  res = f"→{_trunc_param(tr.text)}" if tr.text else ''
733
- return '<code>'+escape(f"{tr.data['name']}({params}){res}")+'</code>'
734
-
735
- # %% ../nbs/07_chat.ipynb #80f344cc
736
- def _trunc_content(content, mx):
737
- "Truncate tool result content, respecting '_full' flag"
738
- if isinstance(content, dict) and '_full' in content and len(content)==1: return content['_full']
739
- return _trunc_str(content, mx=mx)
740
-
741
- # %% ../nbs/07_chat.ipynb #3602a033
742
- def mk_tr_details(tr, mx=2000):
743
- "Create <details> block for tool call as JSON; `mx=None` disables truncation"
744
- 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()}
745
- res = {'id':tr.data['id'], 'server':tr.data.get('server', False),
746
- 'call':{'function': tr.data['name'], 'arguments': args},
747
- 'result':_trunc_content(tr.text, mx=mx),}
748
- summ = f"<summary>{_tc_summary(tr)}</summary>"
749
- 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"
750
-
751
- # %% ../nbs/07_chat.ipynb #ae9dfdb5
752
- def hist2fmt(msgs:list[Msg], mx=2000)->str:
753
- "Render assistant/tool `msgs` as one formatted output string, the inverse of `fmt2hist`"
754
- tus, out = {}, []
755
- for m in msgs:
756
- if m.role == 'assistant':
757
- for p in m.content:
758
- if p.type == PartType.text and p.text: out.append(p.text.strip())
759
- elif p.type == PartType.tool_use: tus[p.data['id']] = p
760
- elif m.role == 'tool':
761
- for p in m.content:
762
- if p.type != PartType.tool_result: continue
763
- tu = tus.get(p.data['id'])
764
- d = dict(p.data, arguments=tu.data.get('arguments', {}) if tu else {})
765
- out.append(mk_tr_details(Part(type=PartType.tool_result, text=p.text, data=d), mx=mx).strip())
766
- else: raise ValueError(f"hist2fmt renders assistant and tool messages only, got {m.role!r}")
767
- 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}"
768
624
 
769
625
  # %% ../nbs/07_chat.ipynb #f0d984ec
770
626
  class StreamFormatter:
fastllm/gemini.py CHANGED
@@ -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
 
fastllm/openai_chat.py CHANGED
@@ -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
 
fastllm/streaming.py CHANGED
@@ -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
fastllm/types.py CHANGED
@@ -3,13 +3,13 @@
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', 'sonn46', 'sonn', 'sonn5', 'opus46', 'opus48',
7
+ 'opus', 'opus5', 'fable', 'fable5', 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55',
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']
13
13
 
14
14
  # %% ../nbs/00_types.ipynb #b4d047fd
15
15
  import httpx, base64, io
@@ -18,86 +18,8 @@ from datetime import datetime, timezone
18
18
  from fastcore.net import urljson
19
19
  from fastcore.utils import *
20
20
  from PIL import Image as PImg
21
+ from aidialog.msg_parts import Part, PartType, Msg, ToolCall, data_url
21
22
 
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
23
 
102
24
  # %% ../nbs/00_types.ipynb #802ad832
103
25
  @dataclass(frozen=True)
@@ -177,15 +99,6 @@ def mk_completion(resp, model, api_name, vendor_name):
177
99
  vendor_name=vendor_name,
178
100
  raw=resp)
179
101
 
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
102
  # %% ../nbs/00_types.ipynb #8a8e468b
190
103
  def fn_schema(t):
191
104
  "Extract (name, description, parameters) from any tool format."
@@ -196,46 +109,6 @@ def fn_schema(t):
196
109
  return t.get('name',''), t.get('description',''), t.get('parameters', t.get('input_schema',{}))
197
110
  return None
198
111
 
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
112
  # %% ../nbs/00_types.ipynb #28c698fe
240
113
  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
114
 
@@ -286,10 +159,12 @@ def get_model_meta(model, vendor_name=None, tfm=noop):
286
159
  # %% ../nbs/00_types.ipynb #60607e23
287
160
  haik45 = "claude-haiku-4-5"
288
161
  sonn45 = "claude-sonnet-4-5"
289
- sonn = sonn46 = "claude-sonnet-4-6"
290
- sonn5 = "claude-sonnet-5"
162
+ sonn46 = "claude-sonnet-4-6"
163
+ sonn = sonn5 = "claude-sonnet-5"
291
164
  opus46 = "claude-opus-4-6"
292
- opus = "claude-opus-4-8"
165
+ opus48 = "claude-opus-4-8"
166
+ opus = opus5 = "claude-opus-5"
167
+ fable = fable5 = 'claude-fable-5'
293
168
  gpt54 = "gpt-5.4"
294
169
  gpt54m = "gpt-5.4-mini"
295
170
  gpt55 = "gpt-5.5"
@@ -314,9 +189,6 @@ def get_model_info(mn, vendor_name=None):
314
189
  if 'search_context_cost_per_query' in info: info['supports_web_search'] = True
315
190
  return dict2obj(info)
316
191
 
317
- # %% ../nbs/00_types.ipynb #331c5d0a
318
- register_model_info("claude-opus-4-8", vendor_name='anthropic', base="claude-opus-4-6")
319
-
320
192
  # %% ../nbs/00_types.ipynb #b36178d4
321
193
  modern_llm = dict(supports_function_calling=True, supports_tool_choice=True, supports_prompt_caching=True,
322
194
  supports_parallel_function_calling=True, supports_native_streaming=True, supports_native_structured_output=True,
@@ -387,10 +259,6 @@ register_model_info('mimo-v2.5-pro-ultraspeed', vendor_name='mimo', **mimo_v25_c
387
259
  # %% ../nbs/00_types.ipynb #defb1c5c
388
260
  register_model_info('MiniMax-M3', vendor_name='minimax', **modern_llm, max_input_tokens=512_000, max_output_tokens=512_000, max_tokens=512_000, input_cost_per_token=0.3e-6, output_cost_per_token=1.2e-6, cache_read_input_token_cost=0.06e-6, supports_vision=True, supports_video_input=True)
389
261
 
390
- register_model_info('claude-fable-5', vendor_name='anthropic', base="claude-opus-4-8",
391
- input_cost_per_token=10e-6, cache_creation_input_token_cost=12.5e-6, output_cost_per_token=50e-6,
392
- cache_read_input_token_cost=1e-6, search_context_cost_per_query=0.005)
393
-
394
262
  # %% ../nbs/00_types.ipynb #4e1b40b3
395
263
  register_model_info('muse-spark-1.1', vendor_name='meta_ai', **modern_llm,
396
264
  max_input_tokens=1_048_576, max_output_tokens=128000, max_tokens=128000,
@@ -411,12 +279,9 @@ register_model_info(codex53spark, 'codex', **codex_pricing,
411
279
  max_tokens=128000, max_input_tokens=128000, max_output_tokens=128000)
412
280
 
413
281
  # %% ../nbs/00_types.ipynb #5d3e4720
414
- for model in (haik45, sonn45, sonn46, sonn5, opus46, opus):
282
+ for model in (haik45, sonn45, sonn46, sonn5, opus46, opus48, opus5, fable5):
415
283
  register_model_info(model, 'claude_code', base=model, base_vendor_name='anthropic', **codex_pricing)
416
284
 
417
- # %% ../nbs/00_types.ipynb #60630540
418
- register_model_info(sonn5, base=sonn5, max_input_tokens=760_000)
419
-
420
285
  # %% ../nbs/00_types.ipynb #bb0c4c2a
421
286
  sol,terra,luna = gpt56s = 'gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna'.split()
422
287
  for model in ['gpt-5.6']+gpt56s:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fastllm
3
- Version: 0.0.31
3
+ Version: 0.0.33
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,22 @@
1
+ fastllm/__init__.py,sha256=AGWXI5DVsNfiiV5VgHNNK08ZPMOHqnJB3xLlEoyC3hg,23
2
+ fastllm/_modidx.py,sha256=4WFucmoHE-R_z1fXpcAIFu1dPG79f0qhVxadj9IECFs,32065
3
+ fastllm/acomplete.py,sha256=dkocdUEwHIfTW70XaBX2yQwl5qpvVbWkGRUOYVNphFA,10171
4
+ fastllm/anthropic.py,sha256=vlfXK9KotK0pUUwilSRMYDYS8qmE8n1ReQVu6fR0J-k,15524
5
+ fastllm/chat.py,sha256=Gma7TIsBqtaoDfkGQJabrT9lK97S2QNyQe6NdkCRePY,30957
6
+ fastllm/codex.py,sha256=HZchfrGUgdf8ayhtOFbIRmh9YmIqfQBwqviAEeir4Uo,161
7
+ fastllm/gemini.py,sha256=DXEKu7tlYJ0MCSHF2RKEjrT0-JDRVgZXmjt4JLYDR1A,15145
8
+ fastllm/openai_chat.py,sha256=mvmQQHgyZm7Cs1K2YQWzZQyXzA6gwximmwbYpAqk5-E,11056
9
+ fastllm/openai_responses.py,sha256=nWrGzPAmGauPHE52MeFSyBUu-HMB5dSmaCJR7jigbP0,13415
10
+ fastllm/streaming.py,sha256=WYDGfyVtCj-5VFTpbPa6jSvVFYRMSSSaks0AdPC5zqI,7671
11
+ fastllm/types.py,sha256=rtkutVi2-XsVXHnvdcdTLnnyRLagjm_uT7CuAz4ZjMs,16190
12
+ fastllm/specs/anthropic.json,sha256=VCgTjM2_HoDpCkeu3q_TCOEZLMHriJZLAG3LnDBAgGM,541035
13
+ fastllm/specs/anthropic.yml,sha256=3S3NAKdXB1Nwp-Sn9Gmh4tBnwhGGhMO3DXkGqPXPUYs,724122
14
+ fastllm/specs/gemini.json,sha256=zJGOdvZ2BvCiTENZt0-BDEvNBMl8h6EBmEskle_WBto,309331
15
+ fastllm/specs/openai.with-code-samples.json,sha256=Kto19AW1u8MfxVDJ4cFVBIdZQOIyy8NWylswo57eABU,1995929
16
+ fastllm/specs/openai.with-code-samples.yml,sha256=DlcWGdaeP4k7smVjt6UbyehJ-2XGU3rn3nCIBMDRfYU,2553630
17
+ fastllm/specs/spec_manifest.json,sha256=9tVFwojXFnNqsAxQzCRTP1lgSIM0fXixnrXdv4Cmb0c,653
18
+ python_fastllm-0.0.33.dist-info/METADATA,sha256=PljSw249JcQWy2eqhFRgs54NSCkkbWTm1Y33zxURbXc,19640
19
+ python_fastllm-0.0.33.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
20
+ python_fastllm-0.0.33.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
+ python_fastllm-0.0.33.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
+ python_fastllm-0.0.33.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- fastllm/__init__.py,sha256=YRrMDApG1V6fNZdI4BA631DmsqtamSXgI1yu0tnt4h0,23
2
- fastllm/_modidx.py,sha256=WKjTRVleZUXLBysnrvwkWRkSMk09mnbUO1NSoxhM5Ng,35217
3
- fastllm/acomplete.py,sha256=iVowR8SjiDtJsCnIWQuxkzGszG9S9aakPXo7EkxRu34,10103
4
- fastllm/anthropic.py,sha256=OHyazPPsXro3ooS0dBy-tk2g8fwXaAS_Cq-7hnc4E6M,15453
5
- fastllm/chat.py,sha256=j2COIPPRJUVy__y4BZgG73WmfWu68kmRlXMbRsBXG7w,37910
6
- fastllm/codex.py,sha256=HZchfrGUgdf8ayhtOFbIRmh9YmIqfQBwqviAEeir4Uo,161
7
- fastllm/gemini.py,sha256=Zw6zIsaQ0yht2qNyuntcaNFDPpGPO3sfi6v_izzXd_4,15044
8
- fastllm/openai_chat.py,sha256=b3JHumjX-DAxLB02IeKjcUeUkpM7k4SLQ_qdvcfDcwk,10965
9
- fastllm/openai_responses.py,sha256=5oUtrYtSd4WChkGhwmVC7LilPAguTx0EnpH4lEqYP40,13307
10
- fastllm/streaming.py,sha256=mDyaK2JJ5mIjtS0SkAx-iFFL0cN4XkXcdWHaY8w5H3M,7597
11
- fastllm/types.py,sha256=K0kg_ZCZXteSgrdhOF_K9S9dgcSKjAUhKUGhxRYrtmY,20790
12
- fastllm/specs/anthropic.json,sha256=VCgTjM2_HoDpCkeu3q_TCOEZLMHriJZLAG3LnDBAgGM,541035
13
- fastllm/specs/anthropic.yml,sha256=3S3NAKdXB1Nwp-Sn9Gmh4tBnwhGGhMO3DXkGqPXPUYs,724122
14
- fastllm/specs/gemini.json,sha256=zJGOdvZ2BvCiTENZt0-BDEvNBMl8h6EBmEskle_WBto,309331
15
- fastllm/specs/openai.with-code-samples.json,sha256=Kto19AW1u8MfxVDJ4cFVBIdZQOIyy8NWylswo57eABU,1995929
16
- fastllm/specs/openai.with-code-samples.yml,sha256=DlcWGdaeP4k7smVjt6UbyehJ-2XGU3rn3nCIBMDRfYU,2553630
17
- fastllm/specs/spec_manifest.json,sha256=9tVFwojXFnNqsAxQzCRTP1lgSIM0fXixnrXdv4Cmb0c,653
18
- python_fastllm-0.0.31.dist-info/METADATA,sha256=PAIJ578D91vgJFwwnDGYDCsPDmMMM6HEZdpERIRYwcg,19577
19
- python_fastllm-0.0.31.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
20
- python_fastllm-0.0.31.dist-info/entry_points.txt,sha256=_cVRbpVkvyxdZfR7UCXa0lIXZtTSsZQWoBPhDwMqrHs,80
21
- python_fastllm-0.0.31.dist-info/top_level.txt,sha256=F8qodL7nEGUHGmzzqfhNKCTIr1i0D6cvudOnm3z7o0Y,8
22
- python_fastllm-0.0.31.dist-info/RECORD,,