python-fastllm 0.0.27__tar.gz → 0.0.29__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.
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/PKG-INFO +1 -1
- python_fastllm-0.0.29/fastllm/__init__.py +1 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/_modidx.py +3 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/acomplete.py +3 -2
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/chat.py +54 -33
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/openai_responses.py +6 -2
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/types.py +19 -6
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/pyproject.toml +3 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/PKG-INFO +1 -1
- python_fastllm-0.0.27/fastllm/__init__.py +0 -1
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/README.md +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/anthropic.py +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/gemini.py +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/openai_chat.py +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/anthropic.json +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/anthropic.yml +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/gemini.json +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/openai.with-code-samples.json +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/openai.with-code-samples.yml +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/specs/spec_manifest.json +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/fastllm/streaming.py +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/SOURCES.txt +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/requires.txt +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.27 → python_fastllm-0.0.29}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.29"
|
|
@@ -140,6 +140,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
140
140
|
'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
|
|
141
141
|
'fastllm.chat.extract_fence_call': ('chat.html#extract_fence_call', 'fastllm/chat.py'),
|
|
142
142
|
'fastllm.chat.fmt2hist': ('chat.html#fmt2hist', 'fastllm/chat.py'),
|
|
143
|
+
'fastllm.chat.hist2fmt': ('chat.html#hist2fmt', 'fastllm/chat.py'),
|
|
143
144
|
'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
|
|
144
145
|
'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
|
|
145
146
|
'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
|
|
@@ -235,6 +236,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
235
236
|
'fastllm/openai_responses.py'),
|
|
236
237
|
'fastllm.openai_responses.denorm_user': ( 'oai_responses.html#denorm_user',
|
|
237
238
|
'fastllm/openai_responses.py'),
|
|
239
|
+
'fastllm.openai_responses.denorm_video': ( 'oai_responses.html#denorm_video',
|
|
240
|
+
'fastllm/openai_responses.py'),
|
|
238
241
|
'fastllm.openai_responses.denorm_web_search': ( 'oai_responses.html#denorm_web_search',
|
|
239
242
|
'fastllm/openai_responses.py'),
|
|
240
243
|
'fastllm.openai_responses.get_hdrs': ( 'oai_responses.html#get_hdrs',
|
|
@@ -39,12 +39,13 @@ vendor_mapping = {
|
|
|
39
39
|
"codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN', _codex_json),
|
|
40
40
|
"moonshot": ('openai_chat', "https://api.moonshot.ai/v1", "MOONSHOT_API_KEY"),
|
|
41
41
|
"deepseek": ('openai_chat', "https://api.deepseek.com/v1", "DEEPSEEK_API_KEY"),
|
|
42
|
-
"mimo": ('
|
|
42
|
+
"mimo": ('anthropic', "https://api.xiaomimimo.com/anthropic", "MIMO_API_KEY"),
|
|
43
43
|
"openrouter": ('openai_chat', "https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
|
44
44
|
"together": ('openai_chat', "https://api.together.xyz/v1", "TOGETHER_API_KEY"),
|
|
45
45
|
"fireworks_ai": ('openai_chat', "https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"),
|
|
46
46
|
"qwen": ('openai_chat', "https://dashscope.aliyuncs.com/compatible-mode/v1", "QWEN_API_KEY"),
|
|
47
|
-
"minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY")
|
|
47
|
+
"minimax": ('anthropic', "https://api.minimax.io/anthropic", "MINIMAX_API_KEY"),
|
|
48
|
+
"meta_ai": ('openai', "https://api.meta.ai/v1", "META_API_KEY")
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
# %% ../nbs/06_acomplete.ipynb #77d27ea7
|
|
@@ -9,17 +9,16 @@ __all__ = ['tool_dtls_tag', 're_tools', 'token_dtls_tag', 're_token', 'think_sta
|
|
|
9
9
|
'structured', 'StopResponse', 'FullResponse', 'search_count', 'UsageStats', 'AsyncChat',
|
|
10
10
|
'astream_with_complete', 'ChatCallback', 'DeepseekMsgsCallback', 'DeepseekPrefillCallback', 'add_warning',
|
|
11
11
|
'StopReasonCallback', 'run_fence_tool', 'FenceToolCallback', 'ToolReminderCallback', 'stop_sequences',
|
|
12
|
-
'StopSequencesCallback', 'mk_tr_details', '
|
|
12
|
+
'StopSequencesCallback', 'mk_tr_details', 'hist2fmt', 'StreamFormatter', 'AsyncStreamFormatter',
|
|
13
|
+
'adisplay_stream']
|
|
13
14
|
|
|
14
15
|
# %% ../nbs/07_chat.ipynb #d5a3bc1f
|
|
15
|
-
import
|
|
16
|
+
import base64, json
|
|
16
17
|
from typing import Optional,Callable
|
|
17
18
|
from html import escape
|
|
18
19
|
from toolslm.funccall import mk_ns, call_func, call_func_async, get_schema
|
|
19
20
|
from fastcore.utils import *
|
|
20
21
|
from fastcore.meta import delegates
|
|
21
|
-
from fastcore import imghdr
|
|
22
|
-
from fastcore.xml import Safe
|
|
23
22
|
from dataclasses import dataclass
|
|
24
23
|
|
|
25
24
|
from .types import *
|
|
@@ -51,8 +50,10 @@ def _url2content(o):
|
|
|
51
50
|
return Part(type=_mime2part_type(mime), text=o.url, data=dict(mime=mime))
|
|
52
51
|
|
|
53
52
|
# %% ../nbs/07_chat.ipynb #48c78e48
|
|
54
|
-
def _add_cache_control(
|
|
55
|
-
|
|
53
|
+
def _add_cache_control(
|
|
54
|
+
msg, # LiteLLM formatted msg
|
|
55
|
+
ttl=None # Cache TTL: '5m' (default) or '1h'
|
|
56
|
+
):
|
|
56
57
|
"cache `msg` with default time-to-live (ttl) of 5minutes ('5m'), but can be set to '1h'."
|
|
57
58
|
cc = {"type": "ephemeral"} | ({"ttl": ttl} if ttl else {})
|
|
58
59
|
cache_idx = None
|
|
@@ -107,10 +108,10 @@ def mk_msg(
|
|
|
107
108
|
# %% ../nbs/07_chat.ipynb #db466e1c
|
|
108
109
|
tool_dtls_tag = "<details class='tool-usage-details' markdown='1'>"
|
|
109
110
|
re_tools = re.compile(fr"^({tool_dtls_tag}\n*(?:<summary>(?P<summary>.*?)</summary>\n*)?\n*```json\n+(.*?)\n+```\n+</details>)",
|
|
110
|
-
|
|
111
|
+
flags=re.DOTALL|re.MULTILINE)
|
|
111
112
|
token_dtls_tag = "<details class='token-usage-details' markdown='1'>"
|
|
112
113
|
re_token = re.compile(fr"^{re.escape(token_dtls_tag)}\n*<summary>.*?</summary>\n*\n*`.*?`\n*\n*</details>\n?",
|
|
113
|
-
|
|
114
|
+
flags=re.DOTALL|re.MULTILINE)
|
|
114
115
|
think_start,think_end = '<!--think_start-->','<!--think_end-->'
|
|
115
116
|
re_think = re.compile(rf'{re.escape(think_start)}.*?{re.escape(think_end)}\n?', re.DOTALL)
|
|
116
117
|
|
|
@@ -172,7 +173,7 @@ def _extract_tool_parts(text:str):
|
|
|
172
173
|
call = d['call']
|
|
173
174
|
# Skip server tool calls in deserialization (round trip issues with Gemini/Anthropic)
|
|
174
175
|
if d.get('server'): return None
|
|
175
|
-
tu = Part(type=PartType.tool_use, text=None, data=
|
|
176
|
+
tu = Part(type=PartType.tool_use, text=None, data=dict(id=d['id'], name=call['function'], arguments=call['arguments']))
|
|
176
177
|
tr = Part(type=PartType.tool_result, text=str(d['result']), data={'id': d['id'], 'name': call['function']})
|
|
177
178
|
return tu, tr
|
|
178
179
|
|
|
@@ -193,7 +194,8 @@ def fmt2hist(outp:str)->list[Msg]:
|
|
|
193
194
|
if tool_parts:
|
|
194
195
|
hist.append(Msg(role='assistant', content=asst_parts.copy()))
|
|
195
196
|
hist.append(Msg(role='tool', content=tool_parts.copy()))
|
|
196
|
-
asst_parts.clear()
|
|
197
|
+
asst_parts.clear()
|
|
198
|
+
tool_parts.clear()
|
|
197
199
|
for txt,_,tj in split_tools(outp):
|
|
198
200
|
if txt and txt.strip():
|
|
199
201
|
if tool_parts: flush()
|
|
@@ -229,9 +231,7 @@ def mk_msgs(
|
|
|
229
231
|
"Create a list of fastllm canonical Msgs."
|
|
230
232
|
if not msgs: return []
|
|
231
233
|
if not isinstance(msgs, list): msgs = [msgs]
|
|
232
|
-
msgs = L(msgs).map(lambda m:
|
|
233
|
-
fmt2hist(m) if isinstance(m,str) and (tool_dtls_tag in m or token_dtls_tag in m) else [m]
|
|
234
|
-
).concat()
|
|
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()
|
|
235
235
|
res, role = [], 'user'
|
|
236
236
|
for m in msgs:
|
|
237
237
|
res.append(msg := remove_cache_ckpts(mk_msg(m, role=role)))
|
|
@@ -290,10 +290,11 @@ def _lite_call_func(tc, tool_schemas, ns):
|
|
|
290
290
|
@delegates(acomplete)
|
|
291
291
|
async def structured(
|
|
292
292
|
m:str, # LiteLLM model string
|
|
293
|
-
msgs:list, # List of messages
|
|
293
|
+
msgs:list, # List of messages
|
|
294
294
|
tool:Callable, # Tool to be used for creating the structured output (class, dataclass or Pydantic, function, etc)
|
|
295
295
|
sp:str|Part='', # System message
|
|
296
|
-
**kwargs
|
|
296
|
+
**kwargs
|
|
297
|
+
):
|
|
297
298
|
"Return the value of the tool call (generally used for structured outputs)"
|
|
298
299
|
t = lite_mk_func(tool)
|
|
299
300
|
r = await acomplete(msgs, m, system=sp, tools=[t], tool_choice=nested_idx(t, 'function', 'name'), **kwargs)
|
|
@@ -317,14 +318,14 @@ def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in
|
|
|
317
318
|
|
|
318
319
|
# %% ../nbs/07_chat.ipynb #f58ce348
|
|
319
320
|
def _trunc_str(s, mx=2000, skip=10, replace="TRUNCATED"):
|
|
320
|
-
"Truncate `s` to `mx` chars max, adding `replace` if truncated"
|
|
321
|
+
"Truncate `s` to `mx` chars max, adding `replace` if truncated; `mx=None` disables truncation"
|
|
321
322
|
if not isinstance(s, str): s = str(s)
|
|
322
323
|
s = type(s)(s.rstrip())
|
|
323
324
|
if len(s)>2 and s[0]=='𝍁' and s[-1]=='𝍁':
|
|
324
325
|
s = s[1:-1]
|
|
325
326
|
if replace: return s
|
|
326
327
|
if isinstance_str(s, ('FullResponse','Safe','PrettyString')): return s
|
|
327
|
-
if len(s)<=mx: return s
|
|
328
|
+
if mx is None or len(s)<=mx: return s
|
|
328
329
|
s = s[skip:mx-skip]
|
|
329
330
|
ss = s.split(' ')
|
|
330
331
|
if len(ss[-1])>150: ss[-1] = ss[-1][:5]
|
|
@@ -348,7 +349,8 @@ def search_count(r):
|
|
|
348
349
|
|
|
349
350
|
# %% ../nbs/07_chat.ipynb #61395e0d
|
|
350
351
|
class UsageStats:
|
|
351
|
-
def __init__(self, model='', prompt_tokens=0, completion_tokens=0, total_tokens=0, cached_tokens=0,
|
|
352
|
+
def __init__(self, model='', prompt_tokens=0, completion_tokens=0, total_tokens=0, cached_tokens=0,
|
|
353
|
+
cache_creation_tokens=0, reasoning_tokens=0, web_search_requests=0, cost=0.0): store_attr()
|
|
352
354
|
|
|
353
355
|
@classmethod
|
|
354
356
|
def from_response(cls, r):
|
|
@@ -384,13 +386,13 @@ class UsageStats:
|
|
|
384
386
|
class AsyncChat:
|
|
385
387
|
def __init__(
|
|
386
388
|
self,
|
|
387
|
-
model:str, # LiteLLM compatible model name
|
|
389
|
+
model:str, # LiteLLM compatible model name
|
|
388
390
|
sp='', # System prompt
|
|
389
391
|
temp=None, # Temperature
|
|
390
392
|
search=False, # Search (l,m,h), if model supports it
|
|
391
393
|
tools:list=None, # Add tools
|
|
392
394
|
hist:list=None, # Chat history
|
|
393
|
-
ns:Optional[dict]=None, # Custom namespace for tool calling
|
|
395
|
+
ns:Optional[dict]=None, # Custom namespace for tool calling
|
|
394
396
|
cache=False, # Anthropic prompt caching
|
|
395
397
|
cache_idxs:list=[-1], # Anthropic cache breakpoint idxs, use `0` for sys prompt if provided
|
|
396
398
|
ttl=None, # Anthropic prompt caching ttl
|
|
@@ -421,8 +423,7 @@ class AsyncChat:
|
|
|
421
423
|
if sp:
|
|
422
424
|
if 0 in self.cache_idxs: sp = _add_cache_control(Msg('',[Part(PartType.text, sp)]))
|
|
423
425
|
cache_idxs = L(self.cache_idxs).filter().map(lambda o: o-1 if o>0 else o)
|
|
424
|
-
else:
|
|
425
|
-
cache_idxs = self.cache_idxs
|
|
426
|
+
else: cache_idxs = self.cache_idxs
|
|
426
427
|
if msg: self.hist = self.hist+[msg]
|
|
427
428
|
self.hist = mk_msgs(self.hist, self.cache and 'claude' in self.model, cache_idxs, self.ttl)
|
|
428
429
|
msgs = self.hist
|
|
@@ -506,7 +507,7 @@ async def astream_with_complete(self, agen, postproc=noop):
|
|
|
506
507
|
@patch
|
|
507
508
|
@delegates(acomplete)
|
|
508
509
|
async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, search=None, stream=False, max_steps=2, step=1,
|
|
509
|
-
|
|
510
|
+
final_prompt=None, tool_choice=None, max_tokens=None, n_workers=8, pause=0.001, tc_timeout=7200, **kwargs):
|
|
510
511
|
if step>max_steps+1: return
|
|
511
512
|
self.prefill, max_tokens = self._prep_call(prefill, search, max_tokens, kwargs, stream=stream, think=think)
|
|
512
513
|
self.turn_sysp, self.turn_msgs = self._prep_msg(msg, prefill)
|
|
@@ -514,9 +515,8 @@ async def _call(self:AsyncChat, msg=None, prefill=None, temp=None, think=None, s
|
|
|
514
515
|
|
|
515
516
|
self.turn_kwargs, self.stream = kwargs, stream
|
|
516
517
|
async for o in self._call_cbs('before_acomplete'): yield o
|
|
517
|
-
res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream,
|
|
518
|
-
|
|
519
|
-
temperature=None if think else ifnone(temp,self.temp), **self.turn_kwargs)
|
|
518
|
+
res = await acomplete(self.turn_msgs, self.model, system=self.turn_sysp, stream=stream, tools=self.tool_schemas,
|
|
519
|
+
tool_choice=tool_choice, max_tokens=int(max_tokens), temperature=None if think else ifnone(temp,self.temp), **self.turn_kwargs)
|
|
520
520
|
if stream:
|
|
521
521
|
if self.prefill: yield _mk_prefill(self.prefill)
|
|
522
522
|
res = astream_with_complete(res, postproc=postproc)
|
|
@@ -565,7 +565,7 @@ async def __call__(
|
|
|
565
565
|
search=None, # Override search set on chat initialization (l,m,h)
|
|
566
566
|
stream=False, # Stream results
|
|
567
567
|
max_steps=2, # Maximum number of tool calls
|
|
568
|
-
final_prompt=_final_prompt, # Final prompt when tool calls have ran out
|
|
568
|
+
final_prompt=_final_prompt, # Final prompt when tool calls have ran out
|
|
569
569
|
return_all=False, # Returns all intermediate ModelResponses if not streaming and has tool calls
|
|
570
570
|
**kwargs
|
|
571
571
|
):
|
|
@@ -593,8 +593,7 @@ class DeepseekMsgsCallback(ChatCallback):
|
|
|
593
593
|
async def after_msgs(self):
|
|
594
594
|
if 'deepseek' not in self.model: return
|
|
595
595
|
for m in self.turn_msgs:
|
|
596
|
-
if m.role=='assistant' and not any(p.type==PartType.thinking for p in m.content):
|
|
597
|
-
m.content.append(Part(PartType.thinking, ''))
|
|
596
|
+
if m.role=='assistant' and not any(p.type==PartType.thinking for p in m.content): m.content.append(Part(PartType.thinking, ''))
|
|
598
597
|
if False: yield
|
|
599
598
|
|
|
600
599
|
# %% ../nbs/07_chat.ipynb #14baac3e
|
|
@@ -666,7 +665,9 @@ class FenceToolCallback(ChatCallback):
|
|
|
666
665
|
lang, code = fence
|
|
667
666
|
out = await run_fence_tool(lang, code, self.ns)
|
|
668
667
|
for p in reversed(m.content):
|
|
669
|
-
if p.type == PartType.text:
|
|
668
|
+
if p.type == PartType.text:
|
|
669
|
+
p.text += out
|
|
670
|
+
break
|
|
670
671
|
self.chat.toolloop = True
|
|
671
672
|
if self.stream: yield {'text': out}
|
|
672
673
|
|
|
@@ -733,14 +734,32 @@ def _trunc_content(content, mx):
|
|
|
733
734
|
|
|
734
735
|
# %% ../nbs/07_chat.ipynb #3602a033
|
|
735
736
|
def mk_tr_details(tr, mx=2000):
|
|
736
|
-
"Create <details> block for tool call as JSON"
|
|
737
|
-
args = {k:_trunc_str(v, mx=mx*5) for k,v in tr.data['arguments'].items()}
|
|
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()}
|
|
738
739
|
res = {'id':tr.data['id'], 'server':tr.data.get('server', False),
|
|
739
740
|
'call':{'function': tr.data['name'], 'arguments': args},
|
|
740
741
|
'result':_trunc_content(tr.text, mx=mx),}
|
|
741
742
|
summ = f"<summary>{_tc_summary(tr)}</summary>"
|
|
742
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"
|
|
743
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)
|
|
762
|
+
|
|
744
763
|
# %% ../nbs/07_chat.ipynb #f0d984ec
|
|
745
764
|
class StreamFormatter:
|
|
746
765
|
def __init__(self, mx=2000, debug=False, showthink=False):
|
|
@@ -752,7 +771,9 @@ class StreamFormatter:
|
|
|
752
771
|
res = ''
|
|
753
772
|
if self.debug: print(o)
|
|
754
773
|
is_think = isinstance(o, dict) and o.get('thinking')
|
|
755
|
-
if not is_think and self._in_think:
|
|
774
|
+
if not is_think and self._in_think:
|
|
775
|
+
res += f'\n\n</details>\n{think_end}\n\n'
|
|
776
|
+
self._in_think = False
|
|
756
777
|
if isinstance(o, dict):
|
|
757
778
|
if thk:=o.get('thinking'):
|
|
758
779
|
if self.showthink:
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
__all__ = ['api_ns', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'norm_finish', 'norm_parts', 'norm_sse_event',
|
|
5
5
|
'delta_index_fn', 'acollect_stream', 'denorm_tool_use', 'denorm_tool', 'denorm_assistant', 'denorm_msgs',
|
|
6
6
|
'denorm_tool_schs', 'denorm_tool_choice', 'denorm_reasoning', 'denorm_web_search', 'denorm_system',
|
|
7
|
-
'denorm_user', 'denorm_image', 'denorm_file', 'denorm_tool_result', 'mk_payload', 'get_hdrs',
|
|
7
|
+
'denorm_user', 'denorm_image', 'denorm_video', 'denorm_file', 'denorm_tool_result', 'mk_payload', 'get_hdrs',
|
|
8
|
+
'cost']
|
|
8
9
|
|
|
9
10
|
# %% ../nbs/02_oai_responses.ipynb #591f55b5
|
|
10
11
|
import json
|
|
@@ -197,13 +198,16 @@ def denorm_user(m:Msg):
|
|
|
197
198
|
if p.type == PartType.text: parts.append({"type": "input_text", "text": p.text or ""})
|
|
198
199
|
elif p.type == PartType.input_image: parts.append(denorm_image(p))
|
|
199
200
|
elif p.type == PartType.input_audio: raise ValueError("OpenAI Responses API does not support audio input; Coming Soon.")
|
|
200
|
-
elif p.type == PartType.input_video:
|
|
201
|
+
elif p.type == PartType.input_video: parts.append(denorm_video(p))
|
|
201
202
|
elif p.type == PartType.input_file: parts.append(denorm_file(p))
|
|
202
203
|
return dict(type='message', role='user', content=parts)
|
|
203
204
|
|
|
204
205
|
# %% ../nbs/02_oai_responses.ipynb #4ced282f
|
|
205
206
|
def denorm_image(p): return {"type": "input_image", "image_url": p.text}
|
|
206
207
|
|
|
208
|
+
# %% ../nbs/02_oai_responses.ipynb #e0ca62d6
|
|
209
|
+
def denorm_video(p): return {"type": "input_video", "video_url": p.text}
|
|
210
|
+
|
|
207
211
|
# %% ../nbs/02_oai_responses.ipynb #6e79a133
|
|
208
212
|
def denorm_file(p):
|
|
209
213
|
if (b64:=data_url(p.text)): return {"type": "input_file", "file_data": p.text, "filename": f"upload.{b64[0].split('/')[-1]}"}
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
# %% auto #0
|
|
6
6
|
__all__ = ['PartType', 'FinishReason', 'api_registry', 'model_prices_url', 'haik45', 'sonn45', 'sonn', 'sonn46', 'sonn5',
|
|
7
7
|
'opus46', 'opus', 'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark',
|
|
8
|
-
'model_info_registry', 'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', '
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour']
|
|
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']
|
|
13
13
|
|
|
14
14
|
# %% ../nbs/00_types.ipynb #b4d047fd
|
|
15
15
|
import httpx, base64, io
|
|
@@ -391,6 +391,13 @@ register_model_info('claude-fable-5', vendor_name='anthropic', base="claude-opus
|
|
|
391
391
|
input_cost_per_token=10e-6, cache_creation_input_token_cost=12.5e-6, output_cost_per_token=50e-6,
|
|
392
392
|
cache_read_input_token_cost=1e-6, search_context_cost_per_query=0.005)
|
|
393
393
|
|
|
394
|
+
# %% ../nbs/00_types.ipynb #4e1b40b3
|
|
395
|
+
register_model_info('muse-spark-1.1', vendor_name='meta_ai', **modern_llm,
|
|
396
|
+
max_input_tokens=1_048_576, max_output_tokens=128000, max_tokens=128000,
|
|
397
|
+
supports_vision=True, supports_image_input=True, supports_video_input=True, supports_pdf_input=True,
|
|
398
|
+
supports_web_search=True, search_context_cost_per_query=0.0025,
|
|
399
|
+
input_cost_per_token=1.25e-6, output_cost_per_token=4.25e-6, cache_read_input_token_cost=0.15e-6)
|
|
400
|
+
|
|
394
401
|
# %% ../nbs/00_types.ipynb #2c23d11e
|
|
395
402
|
codex_pricing = dict(
|
|
396
403
|
input_cost_per_token = 0.10/1_000_000, output_cost_per_token = 0.50/1_000_000,
|
|
@@ -410,6 +417,12 @@ for model in (haik45, sonn45, sonn46, sonn5, opus46, opus):
|
|
|
410
417
|
# %% ../nbs/00_types.ipynb #60630540
|
|
411
418
|
register_model_info(sonn5, base=sonn5, max_input_tokens=760_000)
|
|
412
419
|
|
|
420
|
+
# %% ../nbs/00_types.ipynb #bb0c4c2a
|
|
421
|
+
sol,terra,luna = gpt56s = 'gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna'.split()
|
|
422
|
+
for model in ['gpt-5.6']+gpt56s:
|
|
423
|
+
register_model_info(model, 'openai', base=model, base_vendor_name='openai', max_input_tokens=272_000)
|
|
424
|
+
register_model_info(model, 'codex', base=model, base_vendor_name='openai', max_input_tokens=272_000, **codex_pricing)
|
|
425
|
+
|
|
413
426
|
# %% ../nbs/00_types.ipynb #24cc47ec
|
|
414
427
|
def get_model_pricing(mn, vendor_name, million=True):
|
|
415
428
|
return {k:round(v * (1e6 if million else 1), 6)
|
|
@@ -422,7 +435,7 @@ def approx_pricing(nm, vendor_name, out=10, cache=80, inp=10, markup=0):
|
|
|
422
435
|
p = get_model_pricing(nm, vendor_name)
|
|
423
436
|
ic = p.get('cache_creation_input_token_cost', p['input_cost_per_token'])
|
|
424
437
|
res = (p['output_cost_per_token']*out + p['cache_read_input_token_cost']*cache + ic*inp) / (out+cache+inp)
|
|
425
|
-
if
|
|
438
|
+
if vendor_name=='anthropic': res *= 1.5
|
|
426
439
|
return res*(1+markup)
|
|
427
440
|
|
|
428
441
|
# %% ../nbs/00_types.ipynb #d2a310fb
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.27"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fastllm-0.0.27 → python_fastllm-0.0.29}/python_fastllm.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|