python-fastllm 0.0.30__tar.gz → 0.0.32__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.30 → python_fastllm-0.0.32}/PKG-INFO +3 -1
- python_fastllm-0.0.32/fastllm/__init__.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/_modidx.py +3 -31
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/acomplete.py +18 -2
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/anthropic.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/chat.py +31 -169
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/gemini.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/openai_chat.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/openai_responses.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/streaming.py +1 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/types.py +7 -135
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/pyproject.toml +1 -1
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/PKG-INFO +3 -1
- python_fastllm-0.0.32/python_fastllm.egg-info/requires.txt +5 -0
- python_fastllm-0.0.30/fastllm/__init__.py +0 -1
- python_fastllm-0.0.30/python_fastllm.egg-info/requires.txt +0 -3
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/README.md +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/codex.py +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/anthropic.json +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/anthropic.yml +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/gemini.json +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/openai.with-code-samples.json +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/openai.with-code-samples.yml +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/fastllm/specs/spec_manifest.json +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/SOURCES.txt +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/dependency_links.txt +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/entry_points.txt +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/top_level.txt +0 -0
- {python_fastllm-0.0.30 → python_fastllm-0.0.32}/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.32
|
|
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 @@
|
|
|
1
|
+
__version__ = "0.0.32"
|
|
@@ -16,7 +16,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
16
16
|
'fastllm.acomplete._retry_call': ('acomplete.html#_retry_call', 'fastllm/acomplete.py'),
|
|
17
17
|
'fastllm.acomplete._retry_stream': ('acomplete.html#_retry_stream', 'fastllm/acomplete.py'),
|
|
18
18
|
'fastllm.acomplete.acomplete': ('acomplete.html#acomplete', 'fastllm/acomplete.py'),
|
|
19
|
-
'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py')
|
|
19
|
+
'fastllm.acomplete.mk_client': ('acomplete.html#mk_client', 'fastllm/acomplete.py'),
|
|
20
|
+
'fastllm.acomplete.split_vendor': ('acomplete.html#split_vendor', 'fastllm/acomplete.py')},
|
|
20
21
|
'fastllm.anthropic': { 'fastllm.anthropic._ant_cc': ('anthropic.html#_ant_cc', 'fastllm/anthropic.py'),
|
|
21
22
|
'fastllm.anthropic._ant_part_type': ('anthropic.html#_ant_part_type', 'fastllm/anthropic.py'),
|
|
22
23
|
'fastllm.anthropic._sanid': ('anthropic.html#_sanid', 'fastllm/anthropic.py'),
|
|
@@ -77,14 +78,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
77
78
|
'fastllm.chat.FenceToolStop': ('chat.html#fencetoolstop', 'fastllm/chat.py'),
|
|
78
79
|
'fastllm.chat.FenceToolStop.__call__': ('chat.html#fencetoolstop.__call__', 'fastllm/chat.py'),
|
|
79
80
|
'fastllm.chat.FenceToolStop.__init__': ('chat.html#fencetoolstop.__init__', 'fastllm/chat.py'),
|
|
80
|
-
'fastllm.chat.FullResponse': ('chat.html#fullresponse', 'fastllm/chat.py'),
|
|
81
|
-
'fastllm.chat.MediaUrl': ('chat.html#mediaurl', 'fastllm/chat.py'),
|
|
82
|
-
'fastllm.chat.MediaUrl.__init__': ('chat.html#mediaurl.__init__', 'fastllm/chat.py'),
|
|
83
|
-
'fastllm.chat.Msg.text': ('chat.html#msg.text', 'fastllm/chat.py'),
|
|
84
81
|
'fastllm.chat.StopReasonCallback': ('chat.html#stopreasoncallback', 'fastllm/chat.py'),
|
|
85
82
|
'fastllm.chat.StopReasonCallback.after_acomplete': ( 'chat.html#stopreasoncallback.after_acomplete',
|
|
86
83
|
'fastllm/chat.py'),
|
|
87
|
-
'fastllm.chat.StopResponse': ('chat.html#stopresponse', 'fastllm/chat.py'),
|
|
88
84
|
'fastllm.chat.StopSequencesCallback': ('chat.html#stopsequencescallback', 'fastllm/chat.py'),
|
|
89
85
|
'fastllm.chat.StopSequencesCallback.__init__': ( 'chat.html#stopsequencescallback.__init__',
|
|
90
86
|
'fastllm/chat.py'),
|
|
@@ -112,7 +108,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
112
108
|
'fastllm.chat._apply_cache_idxs': ('chat.html#_apply_cache_idxs', 'fastllm/chat.py'),
|
|
113
109
|
'fastllm.chat._bytes2content': ('chat.html#_bytes2content', 'fastllm/chat.py'),
|
|
114
110
|
'fastllm.chat._call_func': ('chat.html#_call_func', 'fastllm/chat.py'),
|
|
115
|
-
'fastllm.chat._extract_tool_parts': ('chat.html#_extract_tool_parts', 'fastllm/chat.py'),
|
|
116
111
|
'fastllm.chat._handle_stop_reason': ('chat.html#_handle_stop_reason', 'fastllm/chat.py'),
|
|
117
112
|
'fastllm.chat._has_cache': ('chat.html#_has_cache', 'fastllm/chat.py'),
|
|
118
113
|
'fastllm.chat._has_search': ('chat.html#_has_search', 'fastllm/chat.py'),
|
|
@@ -122,15 +117,10 @@ d = { 'settings': { 'branch': 'main',
|
|
|
122
117
|
'fastllm.chat._mime2part_type': ('chat.html#_mime2part_type', 'fastllm/chat.py'),
|
|
123
118
|
'fastllm.chat._mk_content': ('chat.html#_mk_content', 'fastllm/chat.py'),
|
|
124
119
|
'fastllm.chat._mk_prefill': ('chat.html#_mk_prefill', 'fastllm/chat.py'),
|
|
125
|
-
'fastllm.chat._mk_result_fence': ('chat.html#_mk_result_fence', 'fastllm/chat.py'),
|
|
126
120
|
'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
|
|
127
|
-
'fastllm.chat._split_fence_msgs': ('chat.html#_split_fence_msgs', 'fastllm/chat.py'),
|
|
128
|
-
'fastllm.chat._split_msg_on_fences': ('chat.html#_split_msg_on_fences', 'fastllm/chat.py'),
|
|
129
121
|
'fastllm.chat._tc_summary': ('chat.html#_tc_summary', 'fastllm/chat.py'),
|
|
130
122
|
'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
|
|
131
|
-
'fastllm.chat._trunc_content': ('chat.html#_trunc_content', 'fastllm/chat.py'),
|
|
132
123
|
'fastllm.chat._trunc_param': ('chat.html#_trunc_param', 'fastllm/chat.py'),
|
|
133
|
-
'fastllm.chat._trunc_str': ('chat.html#_trunc_str', 'fastllm/chat.py'),
|
|
134
124
|
'fastllm.chat._url2content': ('chat.html#_url2content', 'fastllm/chat.py'),
|
|
135
125
|
'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
|
|
136
126
|
'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
|
|
@@ -139,17 +129,13 @@ d = { 'settings': { 'branch': 'main',
|
|
|
139
129
|
'fastllm.chat.cite_footnote': ('chat.html#cite_footnote', 'fastllm/chat.py'),
|
|
140
130
|
'fastllm.chat.contents': ('chat.html#contents', 'fastllm/chat.py'),
|
|
141
131
|
'fastllm.chat.extract_fence_call': ('chat.html#extract_fence_call', 'fastllm/chat.py'),
|
|
142
|
-
'fastllm.chat.fmt2hist': ('chat.html#fmt2hist', 'fastllm/chat.py'),
|
|
143
|
-
'fastllm.chat.hist2fmt': ('chat.html#hist2fmt', 'fastllm/chat.py'),
|
|
144
132
|
'fastllm.chat.lite_mk_func': ('chat.html#lite_mk_func', 'fastllm/chat.py'),
|
|
145
133
|
'fastllm.chat.mk_msg': ('chat.html#mk_msg', 'fastllm/chat.py'),
|
|
146
134
|
'fastllm.chat.mk_msgs': ('chat.html#mk_msgs', 'fastllm/chat.py'),
|
|
147
|
-
'fastllm.chat.mk_tr_details': ('chat.html#mk_tr_details', 'fastllm/chat.py'),
|
|
148
135
|
'fastllm.chat.postproc': ('chat.html#postproc', 'fastllm/chat.py'),
|
|
149
136
|
'fastllm.chat.remove_cache_ckpts': ('chat.html#remove_cache_ckpts', 'fastllm/chat.py'),
|
|
150
137
|
'fastllm.chat.run_fence_tool': ('chat.html#run_fence_tool', 'fastllm/chat.py'),
|
|
151
138
|
'fastllm.chat.search_count': ('chat.html#search_count', 'fastllm/chat.py'),
|
|
152
|
-
'fastllm.chat.split_tools': ('chat.html#split_tools', 'fastllm/chat.py'),
|
|
153
139
|
'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
|
|
154
140
|
'fastllm.chat.stop_sequences': ('chat.html#stop_sequences', 'fastllm/chat.py'),
|
|
155
141
|
'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py')},
|
|
@@ -272,18 +258,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
272
258
|
'fastllm.types.Completion': ('types.html#completion', 'fastllm/types.py'),
|
|
273
259
|
'fastllm.types.Completion._repr_markdown_': ('types.html#completion._repr_markdown_', 'fastllm/types.py'),
|
|
274
260
|
'fastllm.types.Completion.cost': ('types.html#completion.cost', 'fastllm/types.py'),
|
|
275
|
-
'fastllm.types.Msg': ('types.html#msg', 'fastllm/types.py'),
|
|
276
|
-
'fastllm.types.Msg._repr_markdown_': ('types.html#msg._repr_markdown_', 'fastllm/types.py'),
|
|
277
|
-
'fastllm.types.Part': ('types.html#part', 'fastllm/types.py'),
|
|
278
|
-
'fastllm.types.Part._repr_markdown_': ('types.html#part._repr_markdown_', 'fastllm/types.py'),
|
|
279
|
-
'fastllm.types.ToolCall': ('types.html#toolcall', 'fastllm/types.py'),
|
|
280
|
-
'fastllm.types.ToolCall._repr_markdown_': ('types.html#toolcall._repr_markdown_', 'fastllm/types.py'),
|
|
281
261
|
'fastllm.types.Usage': ('types.html#usage', 'fastllm/types.py'),
|
|
282
|
-
'fastllm.types._fetch_url_partial': ('types.html#_fetch_url_partial', 'fastllm/types.py'),
|
|
283
|
-
'fastllm.types._trunc_strs': ('types.html#_trunc_strs', 'fastllm/types.py'),
|
|
284
262
|
'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
|
|
285
|
-
'fastllm.types.data_url': ('types.html#data_url', 'fastllm/types.py'),
|
|
286
|
-
'fastllm.types.display_list': ('types.html#display_list', 'fastllm/types.py'),
|
|
287
263
|
'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
|
|
288
264
|
'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
|
|
289
265
|
'fastllm.types.get_model_info': ('types.html#get_model_info', 'fastllm/types.py'),
|
|
@@ -292,11 +268,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
292
268
|
'fastllm.types.infer_api_name': ('types.html#infer_api_name', 'fastllm/types.py'),
|
|
293
269
|
'fastllm.types.is_deepseek_peak_hour': ('types.html#is_deepseek_peak_hour', 'fastllm/types.py'),
|
|
294
270
|
'fastllm.types.mk_completion': ('types.html#mk_completion', 'fastllm/types.py'),
|
|
295
|
-
'fastllm.types.mk_tool_res_msg': ('types.html#mk_tool_res_msg', 'fastllm/types.py'),
|
|
296
271
|
'fastllm.types.model_prices_meta': ('types.html#model_prices_meta', 'fastllm/types.py'),
|
|
297
|
-
'fastllm.types.part_txt': ('types.html#part_txt', 'fastllm/types.py'),
|
|
298
272
|
'fastllm.types.payload_kwargs': ('types.html#payload_kwargs', 'fastllm/types.py'),
|
|
299
273
|
'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
|
|
300
|
-
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py')
|
|
301
|
-
'fastllm.types.sys_text': ('types.html#sys_text', 'fastllm/types.py'),
|
|
302
|
-
'fastllm.types.url_mime': ('types.html#url_mime', 'fastllm/types.py')}}}
|
|
274
|
+
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py')}}}
|
|
@@ -3,8 +3,8 @@
|
|
|
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', 'defaults', '
|
|
7
|
-
'ContextWindowExceededError', 'acomplete']
|
|
6
|
+
__all__ = ['specs_path', 'ant_spec', 'oai_spec', 'gem_spec', 'vendor_mapping', 'api2spec', 'defaults', 'split_vendor',
|
|
7
|
+
'mk_client', 'ContextWindowExceededError', 'acomplete']
|
|
8
8
|
|
|
9
9
|
# %% ../nbs/06_acomplete.ipynb #f2f57253
|
|
10
10
|
import asyncio,json,httpx
|
|
@@ -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 *
|
|
@@ -51,11 +52,22 @@ vendor_mapping = {
|
|
|
51
52
|
# %% ../nbs/06_acomplete.ipynb #77d27ea7
|
|
52
53
|
api2spec = {'openai':oai_spec, 'openai_chat':oai_spec, 'anthropic':ant_spec, 'gemini':gem_spec}
|
|
53
54
|
|
|
55
|
+
# %% ../nbs/06_acomplete.ipynb #e3ed40fb
|
|
56
|
+
def split_vendor(model):
|
|
57
|
+
"Split a `'vendor/model'` path into `(vendor_name, model)`: the prefix must name a known vendor or a registered api, else `(None, model)`."
|
|
58
|
+
if model and '/' in model:
|
|
59
|
+
v, m = model.split('/', 1)
|
|
60
|
+
if v in vendor_mapping or v in api_registry.apis: return v, m
|
|
61
|
+
return None, model
|
|
62
|
+
|
|
54
63
|
# %% ../nbs/06_acomplete.ipynb #79075d95
|
|
55
64
|
@flexicache()
|
|
56
65
|
def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, base_url=None, xtra_hdrs=None,
|
|
57
66
|
timeout=httpx.Timeout(connect=30, read=300, write=30, pool=10)):
|
|
58
67
|
err_msg = f"please pass a valid one vendor: {', '.join(list(vendor_mapping))} or pass `api_name`,`base_url` and `api_key`"
|
|
68
|
+
if not vendor_name and not api_name and not (base_url and api_key):
|
|
69
|
+
v, m = split_vendor(model)
|
|
70
|
+
if v in vendor_mapping: vendor_name, model = v, m
|
|
59
71
|
if vendor_name:
|
|
60
72
|
override_base_url = base_url
|
|
61
73
|
try:
|
|
@@ -145,6 +157,10 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
145
157
|
base_url=None, xtra_body=None, xtra_hdrs=None, stream=False,
|
|
146
158
|
stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
|
|
147
159
|
"Unified completion across different APIs."
|
|
160
|
+
if not vendor_name and not api_name and not (base_url and api_key):
|
|
161
|
+
v, m = split_vendor(model)
|
|
162
|
+
if v in vendor_mapping: vendor_name, model = v, m
|
|
163
|
+
elif v: api_name, model = v, m # a registered transport api (e.g. claude_code): not an HTTP vendor
|
|
148
164
|
if api_name == 'claude_code':
|
|
149
165
|
if not stream: raise NotImplementedError("claude_code backend supports stream=True only for now")
|
|
150
166
|
api = api_registry.apis[api_name]
|
|
@@ -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__ = ['
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
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 #
|
|
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 #
|
|
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 #
|
|
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 #
|
|
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 #
|
|
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 (
|
|
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
|
-
|
|
383
|
-
return
|
|
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:
|
|
@@ -406,12 +293,17 @@ class AsyncChat:
|
|
|
406
293
|
default_cbs=True # Whether to include default callbacks
|
|
407
294
|
):
|
|
408
295
|
"LiteLLM chat client."
|
|
296
|
+
if not vendor_name and not api_name and not (base_url and api_key):
|
|
297
|
+
v, model = split_vendor(model)
|
|
298
|
+
if v in vendor_mapping: vendor_name = v
|
|
299
|
+
elif v: api_name = v # a registered transport api (e.g. claude_code)
|
|
409
300
|
self.model = model
|
|
410
301
|
hist,tools = mk_msgs(hist,cache,cache_idxs,ttl),listify(tools)
|
|
411
302
|
if ns is None and tools: ns = mk_ns(tools)
|
|
412
303
|
elif ns is None: ns = globals()
|
|
413
304
|
self.tool_schemas = [lite_mk_func(t) for t in tools] if tools else None
|
|
414
305
|
self.use = UsageStats()
|
|
306
|
+
self.last_req_use = None # usage of the latest request only; `use` accumulates across a turn's tool-call steps
|
|
415
307
|
store_attr(but='cbs')
|
|
416
308
|
self.cbs = L()
|
|
417
309
|
if default_cbs: self.add_cbs(defaults.chat_callbacks)
|
|
@@ -435,6 +327,7 @@ class AsyncChat:
|
|
|
435
327
|
def _track(self, res):
|
|
436
328
|
u = UsageStats.from_response(res)
|
|
437
329
|
u.cost *= (1 + self.markup)
|
|
330
|
+
self.last_req_use = u
|
|
438
331
|
self.use += u
|
|
439
332
|
|
|
440
333
|
def add_cb(self, cb):
|
|
@@ -721,44 +614,13 @@ def _trunc_param(v, mx=40):
|
|
|
721
614
|
|
|
722
615
|
# %% ../nbs/07_chat.ipynb #80c0abdb
|
|
723
616
|
def _tc_summary(tr):
|
|
724
|
-
"Format tool call as func(params)
|
|
617
|
+
"Format tool call as a `func(params)→result` code span"
|
|
725
618
|
params = ', '.join(f"{k}={_trunc_param(v)}" for k,v in tr.data['arguments'].items())
|
|
726
619
|
res = f"→{_trunc_param(tr.text)}" if tr.text else ''
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
"Truncate tool result content, respecting '_full' flag"
|
|
732
|
-
if isinstance(content, dict) and '_full' in content and len(content)==1: return content['_full']
|
|
733
|
-
return _trunc_str(content, mx=mx)
|
|
734
|
-
|
|
735
|
-
# %% ../nbs/07_chat.ipynb #3602a033
|
|
736
|
-
def mk_tr_details(tr, mx=2000):
|
|
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()}
|
|
739
|
-
res = {'id':tr.data['id'], 'server':tr.data.get('server', False),
|
|
740
|
-
'call':{'function': tr.data['name'], 'arguments': args},
|
|
741
|
-
'result':_trunc_content(tr.text, mx=mx),}
|
|
742
|
-
summ = f"<summary>{_tc_summary(tr)}</summary>"
|
|
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"
|
|
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)
|
|
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}"
|
|
762
624
|
|
|
763
625
|
# %% ../nbs/07_chat.ipynb #f0d984ec
|
|
764
626
|
class StreamFormatter:
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_types.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
|
-
__all__ = ['
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
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', 'sonn', 'sonn46', 'sonn5', 'opus46', 'opus',
|
|
7
|
+
'gpt54', 'gpt54m', 'gpt55', 'codex54', 'codex54m', 'codex55', 'codex53spark', 'model_info_registry',
|
|
8
|
+
'modern_llm', 'deepseek_v4_common', 'mimo_v25_common', 'codex_pricing', 'sol', 'terra', 'luna', 'gpt56s',
|
|
9
|
+
'Usage', 'Completion', 'APIRegistry', 'mk_completion', 'fn_schema', 'payload_kwargs', 'get_api_key',
|
|
10
|
+
'resize_b64', 'model_prices_meta', 'infer_api_name', 'get_model_meta', 'register_model_info',
|
|
11
|
+
'get_model_info', 'get_model_pricing', 'approx_pricing', 'is_deepseek_peak_hour']
|
|
13
12
|
|
|
14
13
|
# %% ../nbs/00_types.ipynb #b4d047fd
|
|
15
14
|
import httpx, base64, io
|
|
@@ -18,86 +17,8 @@ from datetime import datetime, timezone
|
|
|
18
17
|
from fastcore.net import urljson
|
|
19
18
|
from fastcore.utils import *
|
|
20
19
|
from PIL import Image as PImg
|
|
20
|
+
from aidialog.msg_parts import Part, PartType, Msg, ToolCall, data_url
|
|
21
21
|
|
|
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
22
|
|
|
102
23
|
# %% ../nbs/00_types.ipynb #802ad832
|
|
103
24
|
@dataclass(frozen=True)
|
|
@@ -177,15 +98,6 @@ def mk_completion(resp, model, api_name, vendor_name):
|
|
|
177
98
|
vendor_name=vendor_name,
|
|
178
99
|
raw=resp)
|
|
179
100
|
|
|
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
101
|
# %% ../nbs/00_types.ipynb #8a8e468b
|
|
190
102
|
def fn_schema(t):
|
|
191
103
|
"Extract (name, description, parameters) from any tool format."
|
|
@@ -196,46 +108,6 @@ def fn_schema(t):
|
|
|
196
108
|
return t.get('name',''), t.get('description',''), t.get('parameters', t.get('input_schema',{}))
|
|
197
109
|
return None
|
|
198
110
|
|
|
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
111
|
# %% ../nbs/00_types.ipynb #28c698fe
|
|
240
112
|
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
113
|
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Programming Language :: Python :: 3 :: Only",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ['fastspec>=0.0.11', 'toolslm', 'pillow']
|
|
18
|
+
dependencies = ['fastcore>=2.1.11', 'aidialog>=0.0.3', 'fastspec>=0.0.11', '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.32
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.30"
|
|
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.30 → python_fastllm-0.0.32}/python_fastllm.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|