python-fastllm 0.0.7__tar.gz → 0.0.8__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.7 → python_fastllm-0.0.8}/PKG-INFO +1 -1
- python_fastllm-0.0.8/fastllm/__init__.py +1 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/_modidx.py +3 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/acomplete.py +17 -1
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/anthropic.py +17 -13
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/chat.py +13 -8
- python_fastllm-0.0.8/fastllm/codex.py +7 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/streaming.py +2 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/types.py +7 -3
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/PKG-INFO +1 -1
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/SOURCES.txt +1 -0
- python_fastllm-0.0.7/fastllm/__init__.py +0 -1
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/README.md +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/gemini.py +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/openai_chat.py +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/openai_responses.py +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/anthropic.json +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/anthropic.yml +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/gemini.json +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/openai.with-code-samples.json +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/openai.with-code-samples.yml +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/fastllm/specs/spec_manifest.json +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/pyproject.toml +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/requires.txt +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.7 → python_fastllm-0.0.8}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.8"
|
|
@@ -10,6 +10,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
10
10
|
'fastllm.acomplete._classify_error': ('acomplete.html#_classify_error', 'fastllm/acomplete.py'),
|
|
11
11
|
'fastllm.acomplete._classify_error_stream': ( 'acomplete.html#_classify_error_stream',
|
|
12
12
|
'fastllm/acomplete.py'),
|
|
13
|
+
'fastllm.acomplete._debug_print': ('acomplete.html#_debug_print', 'fastllm/acomplete.py'),
|
|
13
14
|
'fastllm.acomplete._is_ctx_exceeded': ('acomplete.html#_is_ctx_exceeded', 'fastllm/acomplete.py'),
|
|
14
15
|
'fastllm.acomplete.acomplete': ('acomplete.html#acomplete', 'fastllm/acomplete.py'),
|
|
15
16
|
'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py')},
|
|
@@ -31,6 +32,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
31
32
|
'fastllm.anthropic.denorm_tool_use': ('anthropic.html#denorm_tool_use', 'fastllm/anthropic.py'),
|
|
32
33
|
'fastllm.anthropic.denorm_user': ('anthropic.html#denorm_user', 'fastllm/anthropic.py'),
|
|
33
34
|
'fastllm.anthropic.denorm_web_search': ('anthropic.html#denorm_web_search', 'fastllm/anthropic.py'),
|
|
35
|
+
'fastllm.anthropic.finalize_usage': ('anthropic.html#finalize_usage', 'fastllm/anthropic.py'),
|
|
34
36
|
'fastllm.anthropic.get_hdrs': ('anthropic.html#get_hdrs', 'fastllm/anthropic.py'),
|
|
35
37
|
'fastllm.anthropic.mk_payload': ('anthropic.html#mk_payload', 'fastllm/anthropic.py'),
|
|
36
38
|
'fastllm.anthropic.norm_finish': ('anthropic.html#norm_finish', 'fastllm/anthropic.py'),
|
|
@@ -145,6 +147,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
145
147
|
'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
|
|
146
148
|
'fastllm.chat.stop_sequences': ('chat.html#stop_sequences', 'fastllm/chat.py'),
|
|
147
149
|
'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py')},
|
|
150
|
+
'fastllm.codex': {},
|
|
148
151
|
'fastllm.gemini': { 'fastllm.gemini._gem_filter_sch': ('gemini.html#_gem_filter_sch', 'fastllm/gemini.py'),
|
|
149
152
|
'fastllm.gemini._gem_part_type': ('gemini.html#_gem_part_type', 'fastllm/gemini.py'),
|
|
150
153
|
'fastllm.gemini.acollect_stream': ('gemini.html#acollect_stream', 'fastllm/gemini.py'),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_acomplete.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
|
-
__all__ = ['specs_path', 'ant_spec', 'oai_spec', 'gem_spec', 'vendor_mapping', 'api2spec', 'mk_client',
|
|
6
|
+
__all__ = ['specs_path', 'ant_spec', 'oai_spec', 'gem_spec', 'vendor_mapping', 'api2spec', 'defaults', 'mk_client',
|
|
7
7
|
'ContextWindowExceededError', 'acomplete']
|
|
8
8
|
|
|
9
9
|
# %% ../nbs/06_acomplete.ipynb #f2f57253
|
|
@@ -98,6 +98,21 @@ async def _classify_error_stream(gen):
|
|
|
98
98
|
async for x in gen: yield x
|
|
99
99
|
except APIError as e: raise _classify_error(e) from e
|
|
100
100
|
|
|
101
|
+
# %% ../nbs/06_acomplete.ipynb #f626a4e1
|
|
102
|
+
defaults = SimpleNamespace(debug_mode=False)
|
|
103
|
+
|
|
104
|
+
def _debug_print(model, api_name, vendor_name, payload, func):
|
|
105
|
+
"Pretty-print acomplete inputs when defaults.debug_mode is set"
|
|
106
|
+
from pprint import pformat
|
|
107
|
+
p = dict(payload)
|
|
108
|
+
if defaults.debug_mode == 'brief' and 'tools' in p:
|
|
109
|
+
p['tools'] = '; '.join(o.get('name', o.get('type', o)) for o in p['tools'])
|
|
110
|
+
print('━'*60)
|
|
111
|
+
print(f"\033[1;36mfastllm debug\033[0m model={model} vendor={vendor_name} api={api_name} base_url={func.base_url} path={func.path}")
|
|
112
|
+
print('─'*60)
|
|
113
|
+
print(f"\033[1;33mpayload:\033[0m\n{pformat(p, width=120, sort_dicts=False)}")
|
|
114
|
+
print('━'*60)
|
|
115
|
+
|
|
101
116
|
# %% ../nbs/06_acomplete.ipynb #2379ec94
|
|
102
117
|
@delegates(payload_kwargs)
|
|
103
118
|
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None, base_url=None, xtra_body=None, xtra_hdrs=None,
|
|
@@ -114,6 +129,7 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
114
129
|
if vendor_name == 'deepseek' and 'v4' in model: payload['messages'][-1]['prefix'] = True
|
|
115
130
|
if vendor_name == 'moonshot' and 'kimi' in model: payload['messages'][-1]['partial'] = True
|
|
116
131
|
func = attrgetter(api.op_path[stream])(cli)
|
|
132
|
+
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, payload, func)
|
|
117
133
|
try: resp = await func(**payload)
|
|
118
134
|
except APIError as e: raise _classify_error(e) from e
|
|
119
135
|
if stream: return _classify_error_stream(api.acollect_stream(resp, model=model, vendor_name=vendor_name, stop_callables=stop_callables))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_anthropic.ipynb.
|
|
2
2
|
|
|
3
3
|
# %% auto #0
|
|
4
|
-
__all__ = ['ant_tc_types', '
|
|
4
|
+
__all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'finalize_usage', 'norm_finish', 'norm_parts',
|
|
5
5
|
'norm_sse_event', 'delta_index_fn', 'acollect_stream', 'denorm_tool_use', 'denorm_assistant', 'denorm_tool',
|
|
6
6
|
'denorm_msgs', 'denorm_tool_schs', 'denorm_tool_choice', 'denorm_reasoning', 'denorm_web_search',
|
|
7
7
|
'denorm_system', 'denorm_user', 'denorm_image', 'denorm_file', 'denorm_tool_result', 'mk_payload',
|
|
@@ -42,7 +42,18 @@ def norm_usage(resp):
|
|
|
42
42
|
pt = int(usg.get("input_tokens", 0) or 0) + cached + cache_creation
|
|
43
43
|
ct = int(usg.get("output_tokens", 0) or 0)
|
|
44
44
|
return Usage(prompt_tokens=pt, completion_tokens=ct, total_tokens=pt + ct,
|
|
45
|
-
cached_tokens=cached, cache_creation_tokens=cache_creation, raw=usg)
|
|
45
|
+
cached_tokens=cached, cache_creation_tokens=cache_creation, reasoning_tokens=0, raw=usg)
|
|
46
|
+
|
|
47
|
+
def finalize_usage(usg, parts):
|
|
48
|
+
"Adjust usage using finalized Anthropic content parts."
|
|
49
|
+
if not usg: return usg
|
|
50
|
+
rc = '\n'.join(p.text or '' for p in parts if p.type == PartType.thinking)
|
|
51
|
+
ct = int(usg.raw.get('output_tokens', usg.completion_tokens) or 0)
|
|
52
|
+
rt = min(int(len(rc.split())*1.5), ct) if rc else 0
|
|
53
|
+
res = Usage(prompt_tokens=usg.prompt_tokens, completion_tokens=ct-rt, total_tokens=usg.prompt_tokens+ct,
|
|
54
|
+
cached_tokens=usg.cached_tokens, cache_creation_tokens=usg.cache_creation_tokens, reasoning_tokens=rt, raw=usg.raw)
|
|
55
|
+
print(res)
|
|
56
|
+
return res
|
|
46
57
|
|
|
47
58
|
# %% ../nbs/04_anthropic.ipynb #7a8b1f8f
|
|
48
59
|
def norm_finish(resp, tcs=None):
|
|
@@ -197,7 +208,7 @@ def denorm_reasoning(v):
|
|
|
197
208
|
def denorm_web_search(v):
|
|
198
209
|
"Map canonical web_search_options to Anthropic hosted web_search tool."
|
|
199
210
|
_max_uses = {"low": 1, "medium": 5, "high": 10}
|
|
200
|
-
t = {"type": "
|
|
211
|
+
t = {"type": "web_search_20250305", "name": "web_search"}
|
|
201
212
|
if (typ := (v or {}).get("type")): t["type"] = typ
|
|
202
213
|
if (s := (v or {}).get("search_context_size")):
|
|
203
214
|
t["max_uses"] = _max_uses.get(s, 5)
|
|
@@ -286,13 +297,6 @@ def cost(usage, m):
|
|
|
286
297
|
return cost
|
|
287
298
|
|
|
288
299
|
# %% ../nbs/04_anthropic.ipynb #f7c0b989
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
norm_usage=norm_usage,
|
|
293
|
-
acollect_stream=acollect_stream,
|
|
294
|
-
mk_payload=mk_payload,
|
|
295
|
-
cost=cost,
|
|
296
|
-
get_hdrs=get_hdrs,
|
|
297
|
-
op_path=('messages.messages_post','messages.messages_post'))
|
|
298
|
-
api_registry.register('anthropic', **api_ns)
|
|
300
|
+
api_registry.register('anthropic', norm_tool_calls=norm_tool_calls, norm_parts=norm_parts, norm_finish=norm_finish, norm_usage=norm_usage,
|
|
301
|
+
finalize_usage=finalize_usage, acollect_stream=acollect_stream, mk_payload=mk_payload, cost=cost, get_hdrs=get_hdrs,
|
|
302
|
+
op_path=('messages.messages_post','messages.messages_post'))
|
|
@@ -93,7 +93,7 @@ tool_dtls_tag = "<details class='tool-usage-details' markdown='1'>"
|
|
|
93
93
|
re_tools = re.compile(fr"^({tool_dtls_tag}\n*(?:<summary>(?P<summary>.*?)</summary>\n*)?\n*```json\n+(.*?)\n+```\n+</details>)",
|
|
94
94
|
flags=re.DOTALL|re.MULTILINE)
|
|
95
95
|
token_dtls_tag = "<details class='token-usage-details' markdown='1'>"
|
|
96
|
-
re_token = re.compile(fr"^{re.escape(token_dtls_tag)}
|
|
96
|
+
re_token = re.compile(fr"^{re.escape(token_dtls_tag)}\n*<summary>.*?</summary>\n*\n*`.*?`\n*\n*</details>\n?",
|
|
97
97
|
flags=re.DOTALL|re.MULTILINE)
|
|
98
98
|
|
|
99
99
|
# %% ../nbs/07_chat.ipynb #be998131
|
|
@@ -209,7 +209,9 @@ def mk_msgs(
|
|
|
209
209
|
"Create a list of fastllm canonical Msgs."
|
|
210
210
|
if not msgs: return []
|
|
211
211
|
if not isinstance(msgs, list): msgs = [msgs]
|
|
212
|
-
msgs = L(msgs).map(lambda m:
|
|
212
|
+
msgs = L(msgs).map(lambda m:
|
|
213
|
+
fmt2hist(m) if isinstance(m,str) and (tool_dtls_tag in m or token_dtls_tag in m) else [m]
|
|
214
|
+
).concat()
|
|
213
215
|
res, role = [], 'user'
|
|
214
216
|
for m in msgs:
|
|
215
217
|
res.append(msg := remove_cache_ckpts(mk_msg(m, role=role)))
|
|
@@ -297,9 +299,11 @@ def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in
|
|
|
297
299
|
def _trunc_str(s, mx=2000, skip=10, replace="TRUNCATED"):
|
|
298
300
|
"Truncate `s` to `mx` chars max, adding `replace` if truncated"
|
|
299
301
|
if not isinstance(s, str): s = str(s)
|
|
300
|
-
|
|
302
|
+
s = s.rstrip()
|
|
303
|
+
if len(s)>2 and s[0]=='𝍁' and s[-1]=='𝍁':
|
|
304
|
+
s = s[1:-1]
|
|
305
|
+
if replace: return s
|
|
301
306
|
if isinstance_str(s, ('FullResponse','Safe')): return s
|
|
302
|
-
s = str(s).strip()
|
|
303
307
|
if len(s)<=mx: return s
|
|
304
308
|
s = s[skip:mx-skip]
|
|
305
309
|
ss = s.split(' ')
|
|
@@ -431,7 +435,8 @@ def _think_kw(model, think, vendor_name):
|
|
|
431
435
|
if not think: return {}
|
|
432
436
|
if 'opus-4-7' in model:
|
|
433
437
|
e = 'xhigh' if think=='h' else effort.get(think)
|
|
434
|
-
|
|
438
|
+
eff = dict(thinking={"type":"adaptive", "display":"summarized"}, output_config={"effort":e})
|
|
439
|
+
return dict(reasoning_effort=eff)
|
|
435
440
|
try: xhigh = get_model_info(model, vendor_name).get('supports_xhigh_reasoning_effort')
|
|
436
441
|
except: xhigh = False
|
|
437
442
|
eff = effort.get(think) if think!='x' else 'xhigh' if xhigh else 'high'
|
|
@@ -691,7 +696,7 @@ defaults.chat_callbacks = [DeepseekPrefillCallback, FenceToolCallback, ToolRemin
|
|
|
691
696
|
def _trunc_param(v, mx=40):
|
|
692
697
|
"Truncate and escape param value for display"
|
|
693
698
|
tp = _trunc_str(str(v).replace('`', r'\`'), mx=mx, replace=None, skip=0)
|
|
694
|
-
try: return
|
|
699
|
+
try: return dumps(tp, ensure_ascii=False)
|
|
695
700
|
except Exception: return repr(tp).replace('\\\\', '\\')
|
|
696
701
|
|
|
697
702
|
# %% ../nbs/07_chat.ipynb #80c0abdb
|
|
@@ -721,7 +726,7 @@ def mk_tr_details(tr, mx=2000):
|
|
|
721
726
|
'call':{'function': tr.data['name'], 'arguments': args},
|
|
722
727
|
'result':_trunc_content(tr.text, mx=mx),}
|
|
723
728
|
summ = f"<summary>{_tc_summary(tr)}</summary>"
|
|
724
|
-
return f"\n\n{tool_dtls_tag}\n{summ}\n\n```json\n{dumps(res, indent=2)}\n```\n\n</details>\n\n"
|
|
729
|
+
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"
|
|
725
730
|
|
|
726
731
|
# %% ../nbs/07_chat.ipynb #3049001c
|
|
727
732
|
def mk_srv_tc_details(tc, mx=2000):
|
|
@@ -729,7 +734,7 @@ def mk_srv_tc_details(tc, mx=2000):
|
|
|
729
734
|
args = {k:_trunc_str(v, mx=mx*5) for k,v in tc.arguments.items()}
|
|
730
735
|
res = {'id':tc.id, 'server':True, 'call':{'function': tc.name, 'arguments': args}, 'result':"Server tool call executed."}
|
|
731
736
|
summ = f"<summary>{_srv_tc_summary(tc)}</summary>"
|
|
732
|
-
return f"\n\n{tool_dtls_tag}\n{summ}\n\n```json\n{dumps(res, indent=2)}\n```\n\n</details>\n\n"
|
|
737
|
+
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"
|
|
733
738
|
|
|
734
739
|
# %% ../nbs/07_chat.ipynb #f0d984ec
|
|
735
740
|
# status_re = re.compile(r'^- ⏳ <code>(.*)</code> ⏳$|^🧠+$', re.MULTILINE) # TODO: Need to yield tool calls as they are done collated in fastllm `_acollect_stream`
|
|
@@ -138,6 +138,7 @@ async def mk_acollect_stream(it, index_fn, model=None, api_name=None, vendor_nam
|
|
|
138
138
|
deltas.append(d)
|
|
139
139
|
part_accum.finalize()
|
|
140
140
|
tcs = part_accum.tool_calls
|
|
141
|
+
if api_name: usg = api_registry.apis[api_name].finalize_usage(usg, part_accum.parts)
|
|
141
142
|
if stop: fin = FinishReason.stop
|
|
142
143
|
fin = FinishReason.tool_calls if fin==FinishReason.stop and any(~L(tcs).attrgot('server')) else fin # recheck tool calls post collation
|
|
143
144
|
# tool calls and non-anthropic citations are yielded at the end
|
|
@@ -145,3 +146,4 @@ async def mk_acollect_stream(it, index_fn, model=None, api_name=None, vendor_nam
|
|
|
145
146
|
message=Msg(role="assistant", content=part_accum.parts),
|
|
146
147
|
finish_reason=fin, usage=usg, tool_calls=tcs, api_name=api_name, vendor_name=vendor_name,
|
|
147
148
|
raw={'deltas':deltas})
|
|
149
|
+
|
|
@@ -147,25 +147,29 @@ FinishReason = str_enum('finish_reason', 'stop', 'tool_calls', 'length', 'conten
|
|
|
147
147
|
# %% ../nbs/00_types.ipynb #fc681c52
|
|
148
148
|
class APIRegistry:
|
|
149
149
|
def __init__(self): self.apis = {}
|
|
150
|
-
def register(self, name, **kwargs): self.apis[name] = SimpleNamespace(**kwargs)
|
|
150
|
+
def register(self, name, finalize_usage=noop, **kwargs): self.apis[name] = SimpleNamespace(finalize_usage=finalize_usage, **kwargs)
|
|
151
151
|
|
|
152
152
|
api_registry = APIRegistry()
|
|
153
153
|
|
|
154
|
+
|
|
154
155
|
# %% ../nbs/00_types.ipynb #d58a5f96
|
|
155
156
|
def mk_completion(resp, model, api_name, vendor_name):
|
|
156
157
|
"Normalize an api response into Completion."
|
|
157
158
|
api = api_registry.apis[api_name]
|
|
158
159
|
tcs = api.norm_tool_calls(resp)
|
|
160
|
+
parts = api.norm_parts(resp)
|
|
161
|
+
usg = api.finalize_usage(api.norm_usage(resp), parts)
|
|
159
162
|
return Completion(
|
|
160
163
|
model=resp.get("model") or model,
|
|
161
|
-
message=Msg(role="assistant", content=
|
|
164
|
+
message=Msg(role="assistant", content=parts),
|
|
162
165
|
finish_reason=api.norm_finish(resp, tcs),
|
|
163
|
-
usage=
|
|
166
|
+
usage=usg,
|
|
164
167
|
tool_calls=tcs,
|
|
165
168
|
api_name=api_name,
|
|
166
169
|
vendor_name=vendor_name,
|
|
167
170
|
raw=resp)
|
|
168
171
|
|
|
172
|
+
|
|
169
173
|
# %% ../nbs/00_types.ipynb #d5322db5
|
|
170
174
|
def mk_tool_res_msg(tool_calls:list[ToolCall], results:list[str|list]):
|
|
171
175
|
'A util to prepare parallel tool call with str or media list results'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.7"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|