python-fastllm 0.0.11__tar.gz → 0.0.14__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.11 → python_fastllm-0.0.14}/PKG-INFO +2 -1
- python_fastllm-0.0.14/fastllm/__init__.py +1 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/_modidx.py +4 -1
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/acomplete.py +2 -1
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/anthropic.py +10 -5
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/chat.py +3 -3
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/openai_responses.py +5 -2
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/streaming.py +1 -5
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/types.py +17 -4
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/pyproject.toml +1 -1
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/PKG-INFO +2 -1
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/requires.txt +1 -0
- python_fastllm-0.0.11/fastllm/__init__.py +0 -1
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/README.md +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/gemini.py +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/openai_chat.py +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/anthropic.json +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/anthropic.yml +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/gemini.json +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/openai.with-code-samples.json +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/openai.with-code-samples.yml +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/fastllm/specs/spec_manifest.json +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/SOURCES.txt +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.11 → python_fastllm-0.0.14}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fastllm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
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
|
|
@@ -11,6 +11,7 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: fastspec
|
|
13
13
|
Requires-Dist: toolslm
|
|
14
|
+
Requires-Dist: pillow
|
|
14
15
|
|
|
15
16
|
# fastllm
|
|
16
17
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.14"
|
|
@@ -19,6 +19,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
19
19
|
'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py')},
|
|
20
20
|
'fastllm.anthropic': { 'fastllm.anthropic._ant_cc': ('anthropic.html#_ant_cc', 'fastllm/anthropic.py'),
|
|
21
21
|
'fastllm.anthropic._ant_part_type': ('anthropic.html#_ant_part_type', 'fastllm/anthropic.py'),
|
|
22
|
+
'fastllm.anthropic._sanid': ('anthropic.html#_sanid', 'fastllm/anthropic.py'),
|
|
22
23
|
'fastllm.anthropic.acollect_stream': ('anthropic.html#acollect_stream', 'fastllm/anthropic.py'),
|
|
23
24
|
'fastllm.anthropic.cost': ('anthropic.html#cost', 'fastllm/anthropic.py'),
|
|
24
25
|
'fastllm.anthropic.delta_index_fn': ('anthropic.html#delta_index_fn', 'fastllm/anthropic.py'),
|
|
@@ -204,7 +205,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
204
205
|
'fastllm.openai_chat.norm_parts': ('oai_chat.html#norm_parts', 'fastllm/openai_chat.py'),
|
|
205
206
|
'fastllm.openai_chat.norm_sse_event': ('oai_chat.html#norm_sse_event', 'fastllm/openai_chat.py'),
|
|
206
207
|
'fastllm.openai_chat.norm_tool_calls': ('oai_chat.html#norm_tool_calls', 'fastllm/openai_chat.py')},
|
|
207
|
-
'fastllm.openai_responses': { 'fastllm.openai_responses.
|
|
208
|
+
'fastllm.openai_responses': { 'fastllm.openai_responses._sanid': ('oai_responses.html#_sanid', 'fastllm/openai_responses.py'),
|
|
209
|
+
'fastllm.openai_responses.acollect_stream': ( 'oai_responses.html#acollect_stream',
|
|
208
210
|
'fastllm/openai_responses.py'),
|
|
209
211
|
'fastllm.openai_responses.cost': ('oai_responses.html#cost', 'fastllm/openai_responses.py'),
|
|
210
212
|
'fastllm.openai_responses.delta_index_fn': ( 'oai_responses.html#delta_index_fn',
|
|
@@ -291,5 +293,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
291
293
|
'fastllm.types.part_txt': ('types.html#part_txt', 'fastllm/types.py'),
|
|
292
294
|
'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
|
|
293
295
|
'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
|
|
296
|
+
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
|
|
294
297
|
'fastllm.types.sys_text': ('types.html#sys_text', 'fastllm/types.py'),
|
|
295
298
|
'fastllm.types.url_mime': ('types.html#url_mime', 'fastllm/types.py')}}}
|
|
@@ -29,7 +29,8 @@ oai_spec = SpecParser.from_openapi(dict2obj(json.loads((specs_path/'openai.with
|
|
|
29
29
|
gem_spec = SpecParser.from_discovery(dict2obj(json.loads((specs_path/'gemini.json').read_text())))
|
|
30
30
|
|
|
31
31
|
# %% ../nbs/06_acomplete.ipynb #32ee2546
|
|
32
|
-
|
|
32
|
+
_codex_path = os.getenv('CODEX_AUTH_PATH', '~/.codex/auth.json')
|
|
33
|
+
_codex_json = _codex_path, ('tokens','access_token')
|
|
33
34
|
vendor_mapping = {
|
|
34
35
|
"openai": ('openai', 'https://api.openai.com/v1', 'OPENAI_API_KEY'),
|
|
35
36
|
"anthropic": ('anthropic', 'https://api.anthropic.com', 'ANTHROPIC_API_KEY'),
|
|
@@ -125,11 +125,14 @@ def _ant_cc(block, p):
|
|
|
125
125
|
if (cc := (p.data or {}).get('cache_control')): block['cache_control'] = cc
|
|
126
126
|
return block
|
|
127
127
|
|
|
128
|
+
# %% ../nbs/04_anthropic.ipynb #62e9a042
|
|
129
|
+
def _sanid(id_str): return re.sub(r'[^a-zA-Z0-9_-]', '_', id_str or '')
|
|
130
|
+
|
|
128
131
|
# %% ../nbs/04_anthropic.ipynb #6ec772cb
|
|
129
132
|
def denorm_tool_use(p:Part):
|
|
130
133
|
"Convert canonical tool_use Part to Anthropic tool_use content block."
|
|
131
134
|
d = p.data or {}
|
|
132
|
-
block = dict(type='tool_use', id=d.get('id',''), name=d.get('name',''), input=d.get('arguments') or {})
|
|
135
|
+
block = dict(type='tool_use', id=_sanid(d.get('id','')), name=d.get('name',''), input=d.get('arguments') or {})
|
|
133
136
|
if 'caller' in d: block['caller'] = d['caller']
|
|
134
137
|
return _ant_cc(block, p)
|
|
135
138
|
|
|
@@ -238,8 +241,10 @@ def denorm_user(m:Msg):
|
|
|
238
241
|
return dict(role='user', content=parts)
|
|
239
242
|
|
|
240
243
|
# %% ../nbs/04_anthropic.ipynb #edd87272
|
|
241
|
-
def denorm_image(p):
|
|
242
|
-
if (b64:=data_url(p.text)):
|
|
244
|
+
def denorm_image(p, max_sz=None):
|
|
245
|
+
if (b64:=data_url(p.text)):
|
|
246
|
+
data = resize_b64(b64[1], max_sz) if max_sz else b64[1]
|
|
247
|
+
return {"type": "image", "source": {"type": "base64", "media_type": b64[0], "data": data}}
|
|
243
248
|
return {"type": "image", "source": {"type": "url", "url": p.text}}
|
|
244
249
|
|
|
245
250
|
# %% ../nbs/04_anthropic.ipynb #fc6bbdfc
|
|
@@ -251,12 +256,12 @@ def denorm_file(p):
|
|
|
251
256
|
def denorm_tool_result(p:Part):
|
|
252
257
|
"Convert canonical tool_result Part to Anthropic tool_result content block."
|
|
253
258
|
d = p.data or {}
|
|
254
|
-
tid = d.get('id') or d.get('call_id','')
|
|
259
|
+
tid = _sanid(d.get('id') or d.get('call_id',''))
|
|
255
260
|
if isinstance(p.text, list):
|
|
256
261
|
blocks = []
|
|
257
262
|
for pp in p.text:
|
|
258
263
|
if pp.type == PartType.text: blocks.append({"type": "text", "text": pp.text or ""})
|
|
259
|
-
elif pp.type == PartType.input_image: blocks.append(denorm_image(pp))
|
|
264
|
+
elif pp.type == PartType.input_image: blocks.append(denorm_image(pp, max_sz=2000))
|
|
260
265
|
elif pp.type == PartType.input_file: blocks.append(denorm_file(pp))
|
|
261
266
|
else: raise ValueError(f"Anthropic tool_result does not support {pp.type}")
|
|
262
267
|
return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=blocks), p)
|
|
@@ -183,7 +183,7 @@ def fmt2hist(outp:str)->list[Msg]:
|
|
|
183
183
|
"Transform a formatted output string into fastllm canonical Msgs"
|
|
184
184
|
if token_dtls_tag in outp: outp = re_token.sub('', outp)
|
|
185
185
|
if tool_dtls_tag not in outp:
|
|
186
|
-
msg = Msg(role='assistant', content=[Part(type=PartType.text, text=outp.strip())])
|
|
186
|
+
msg = Msg(role='assistant', content=[Part(type=PartType.text, text=outp.strip() or '.')])
|
|
187
187
|
return _split_msg_on_fences(msg)
|
|
188
188
|
hist, asst_parts, tool_parts = [], [], []
|
|
189
189
|
def flush():
|
|
@@ -194,7 +194,7 @@ def fmt2hist(outp:str)->list[Msg]:
|
|
|
194
194
|
for txt,_,tj in split_tools(outp):
|
|
195
195
|
if txt and txt.strip():
|
|
196
196
|
if tool_parts: flush()
|
|
197
|
-
asst_parts.append(Part(type=PartType.text, text=txt.strip()))
|
|
197
|
+
asst_parts.append(Part(type=PartType.text, text=txt.strip() or '.'))
|
|
198
198
|
if tj and (tp := _extract_tool_parts(tj)):
|
|
199
199
|
asst_parts.append(tp[0])
|
|
200
200
|
tool_parts.append(tp[1])
|
|
@@ -316,7 +316,7 @@ def _has_stop(tres_parts): return any(isinstance(p.text, StopResponse) for p in
|
|
|
316
316
|
def _trunc_str(s, mx=2000, skip=10, replace="TRUNCATED"):
|
|
317
317
|
"Truncate `s` to `mx` chars max, adding `replace` if truncated"
|
|
318
318
|
if not isinstance(s, str): s = str(s)
|
|
319
|
-
s = s.rstrip()
|
|
319
|
+
s = s.rstrip() if type(s) is str else type(s)(s.rstrip())
|
|
320
320
|
if len(s)>2 and s[0]=='𝍁' and s[-1]=='𝍁':
|
|
321
321
|
s = s[1:-1]
|
|
322
322
|
if replace: return s
|
|
@@ -109,10 +109,13 @@ async def acollect_stream(resp, **kwargs):
|
|
|
109
109
|
res = mk_acollect_stream(norm_and_yield(resp, norm_sse_event), index_fn=delta_index_fn, api_name='openai', **kwargs)
|
|
110
110
|
async for o in res: yield o
|
|
111
111
|
|
|
112
|
+
# %% ../nbs/02_oai_responses.ipynb #9608e813
|
|
113
|
+
def _sanid(id_str): return id_str[:64] # codex max 64 char limit
|
|
114
|
+
|
|
112
115
|
# %% ../nbs/02_oai_responses.ipynb #b746c82b
|
|
113
116
|
def denorm_tool_use(p:Part):
|
|
114
117
|
"Convert canonical tool_use Part back to OpenAI Responses function_call item."
|
|
115
|
-
return dict(type='function_call', call_id=p.data.get('id'), name=p.data.get('name'), arguments=json.dumps(p.data.get('arguments', '{}')))
|
|
118
|
+
return dict(type='function_call', call_id=_sanid(p.data.get('id')), name=p.data.get('name'), arguments=json.dumps(p.data.get('arguments', '{}')))
|
|
116
119
|
|
|
117
120
|
# %% ../nbs/02_oai_responses.ipynb #8f42adf7
|
|
118
121
|
def denorm_tool(m:Msg):
|
|
@@ -208,7 +211,7 @@ def denorm_file(p):
|
|
|
208
211
|
# %% ../nbs/02_oai_responses.ipynb #145b1c79
|
|
209
212
|
def denorm_tool_result(m:Part):
|
|
210
213
|
"Convert canonical tool result back to OpenAI Responses function_call_output item."
|
|
211
|
-
cid = m.data.get('id', '') or m.data.get('call_id')
|
|
214
|
+
cid = _sanid(m.data.get('id', '') or m.data.get('call_id'))
|
|
212
215
|
if isinstance(m.text, list):
|
|
213
216
|
out = []
|
|
214
217
|
for p in m.text:
|
|
@@ -137,14 +137,10 @@ async def mk_acollect_stream(it, index_fn, model=None, api_name=None, vendor_nam
|
|
|
137
137
|
data = {**acc.extra, 'id':acc.id, 'name':acc.name, 'arguments':args, 'server':acc.server}
|
|
138
138
|
yield Part(type=PartType.tool_use, data=data)
|
|
139
139
|
# Server tool results for anthropic are yielded in d.server_tool_result by checking injected dummy `_delta`
|
|
140
|
-
if acc.server
|
|
140
|
+
if acc.server: yield Part(type=PartType.tool_result, text="Server tool call executed.", data=data)
|
|
141
141
|
if d.server_tool_result:
|
|
142
142
|
idx = _fidx(d, 'server_tool_result')
|
|
143
143
|
part_accum.parts[idx] = Part(type=typ, data=d.server_tool_result)
|
|
144
|
-
srv_tc = next((p for p in reversed(list(part_accum.parts.values())) if isinstance(p, ToolCall) and p.server), None)
|
|
145
|
-
if srv_tc:
|
|
146
|
-
data = {**srv_tc.extra, 'id':srv_tc.id, 'name':srv_tc.name, 'arguments':srv_tc.arguments, 'server':True}
|
|
147
|
-
yield Part(type=PartType.tool_result, text="Server tool call executed.", data=data)
|
|
148
144
|
r = _proc(d, 'refusal')
|
|
149
145
|
if r[0]: yield r[0]
|
|
150
146
|
if d.finish_reason: fin = d.finish_reason
|
|
@@ -7,15 +7,16 @@ __all__ = ['PartType', 'FinishReason', 'api_registry', 'model_prices_url', 'haik
|
|
|
7
7
|
'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark', 'model_info_registry',
|
|
8
8
|
'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', 'Part', 'Msg', 'ToolCall',
|
|
9
9
|
'display_list', 'Usage', 'Completion', 'APIRegistry', 'mk_completion', 'mk_tool_res_msg', 'fn_schema',
|
|
10
|
-
'sys_text', 'part_txt', 'data_url', 'url_mime', 'payload_kwargs', 'get_api_key', '
|
|
11
|
-
'infer_api_name', 'get_model_meta', 'register_model_info', 'get_model_info',
|
|
12
|
-
'approx_pricing']
|
|
10
|
+
'sys_text', 'part_txt', 'data_url', 'url_mime', 'payload_kwargs', 'get_api_key', 'resize_b64',
|
|
11
|
+
'model_prices_meta', 'infer_api_name', 'get_model_meta', 'register_model_info', 'get_model_info',
|
|
12
|
+
'get_model_pricing', 'approx_pricing']
|
|
13
13
|
|
|
14
14
|
# %% ../nbs/00_types.ipynb #b4d047fd
|
|
15
|
-
import httpx
|
|
15
|
+
import httpx, base64, io
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
17
|
from fastcore.net import urljson
|
|
18
18
|
from fastcore.utils import *
|
|
19
|
+
from PIL import Image as PImg
|
|
19
20
|
|
|
20
21
|
# %% ../nbs/00_types.ipynb #e568bade
|
|
21
22
|
@dataclass
|
|
@@ -242,6 +243,18 @@ def get_api_key(api_key, default):
|
|
|
242
243
|
if not key: raise ValueError(f"Missing API key: set environment variable '{default}' or pass `api_key` parameter")
|
|
243
244
|
return key
|
|
244
245
|
|
|
246
|
+
# %% ../nbs/00_types.ipynb #25e9cd60
|
|
247
|
+
def resize_b64(b64, max_sz):
|
|
248
|
+
"Resize a base64 image data to a max long edge, preserving aspect ratio."
|
|
249
|
+
img = PImg.open(io.BytesIO(base64.b64decode(b64)))
|
|
250
|
+
if max(img.size) < max_sz: return b64
|
|
251
|
+
img.thumbnail((max_sz,max_sz), PImg.Resampling.LANCZOS)
|
|
252
|
+
fmt = (img.format or 'PNG').upper()
|
|
253
|
+
if fmt=='JPG': fmt = 'JPEG'
|
|
254
|
+
buf = io.BytesIO()
|
|
255
|
+
img.save(buf, format=fmt)
|
|
256
|
+
return base64.b64encode(buf.getvalue()).decode()
|
|
257
|
+
|
|
245
258
|
# %% ../nbs/00_types.ipynb #852adecd
|
|
246
259
|
model_prices_url = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json'
|
|
247
260
|
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Programming Language :: Python :: 3 :: Only",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ['fastspec', 'toolslm']
|
|
18
|
+
dependencies = ['fastspec', 'toolslm', 'pillow']
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
21
|
Repository = "https://github.com/AnswerDotAI/fastllm"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fastllm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
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
|
|
@@ -11,6 +11,7 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: fastspec
|
|
13
13
|
Requires-Dist: toolslm
|
|
14
|
+
Requires-Dist: pillow
|
|
14
15
|
|
|
15
16
|
# fastllm
|
|
16
17
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.11"
|
|
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.11 → python_fastllm-0.0.14}/python_fastllm.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|