python-fastllm 0.0.63__py3-none-any.whl → 0.0.65__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastllm/__init__.py +1 -1
- fastllm/_modidx.py +6 -4
- fastllm/acomplete.py +21 -38
- fastllm/anthropic.py +41 -44
- fastllm/chat.py +50 -55
- fastllm/codex.py +13 -0
- fastllm/gemini.py +29 -29
- fastllm/model_prices.json +40924 -24115
- fastllm/openai_chat.py +31 -35
- fastllm/openai_responses.py +36 -34
- fastllm/responses.py +28 -28
- fastllm/types.py +53 -34
- {python_fastllm-0.0.63.dist-info → python_fastllm-0.0.65.dist-info}/METADATA +12 -12
- python_fastllm-0.0.65.dist-info/RECORD +18 -0
- python_fastllm-0.0.63.dist-info/RECORD +0 -18
- {python_fastllm-0.0.63.dist-info → python_fastllm-0.0.65.dist-info}/WHEEL +0 -0
- {python_fastllm-0.0.63.dist-info → python_fastllm-0.0.65.dist-info}/entry_points.txt +0 -0
- {python_fastllm-0.0.63.dist-info → python_fastllm-0.0.65.dist-info}/top_level.txt +0 -0
fastllm/__init__.py
CHANGED
fastllm/_modidx.py
CHANGED
|
@@ -10,7 +10,6 @@ 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._codex_mtime_policy': ('acomplete.html#_codex_mtime_policy', 'fastllm/acomplete.py'),
|
|
14
13
|
'fastllm.acomplete._debug_print': ('acomplete.html#_debug_print', 'fastllm/acomplete.py'),
|
|
15
14
|
'fastllm.acomplete._is_ctx_exceeded': ('acomplete.html#_is_ctx_exceeded', 'fastllm/acomplete.py'),
|
|
16
15
|
'fastllm.acomplete._raise_if_done': ('acomplete.html#_raise_if_done', 'fastllm/acomplete.py'),
|
|
@@ -112,7 +111,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
112
111
|
'fastllm.chat._inject_tool_reminder': ('chat.html#_inject_tool_reminder', 'fastllm/chat.py'),
|
|
113
112
|
'fastllm.chat._lite_call_func': ('chat.html#_lite_call_func', 'fastllm/chat.py'),
|
|
114
113
|
'fastllm.chat._mk_tool_result': ('chat.html#_mk_tool_result', 'fastllm/chat.py'),
|
|
115
|
-
'fastllm.chat._think_kw': ('chat.html#_think_kw', 'fastllm/chat.py'),
|
|
116
114
|
'fastllm.chat._usrtools': ('chat.html#_usrtools', 'fastllm/chat.py'),
|
|
117
115
|
'fastllm.chat.add_warning': ('chat.html#add_warning', 'fastllm/chat.py'),
|
|
118
116
|
'fastllm.chat.adisplay_stream': ('chat.html#adisplay_stream', 'fastllm/chat.py'),
|
|
@@ -126,7 +124,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
126
124
|
'fastllm.chat.stop_reason': ('chat.html#stop_reason', 'fastllm/chat.py'),
|
|
127
125
|
'fastllm.chat.structured': ('chat.html#structured', 'fastllm/chat.py'),
|
|
128
126
|
'fastllm.chat.think_sep': ('chat.html#think_sep', 'fastllm/chat.py')},
|
|
129
|
-
'fastllm.codex': { 'fastllm.codex.
|
|
127
|
+
'fastllm.codex': { 'fastllm.codex._codex_hdrs': ('codex.html#_codex_hdrs', 'fastllm/codex.py'),
|
|
128
|
+
'fastllm.codex._codex_vendor_auth': ('codex.html#_codex_vendor_auth', 'fastllm/codex.py'),
|
|
129
|
+
'fastllm.codex._merge_tokens': ('codex.html#_merge_tokens', 'fastllm/codex.py'),
|
|
130
130
|
'fastllm.codex._post': ('codex.html#_post', 'fastllm/codex.py'),
|
|
131
131
|
'fastllm.codex._token_grant': ('codex.html#_token_grant', 'fastllm/codex.py'),
|
|
132
132
|
'fastllm.codex.codex_auth': ('codex.html#codex_auth', 'fastllm/codex.py'),
|
|
@@ -373,10 +373,11 @@ d = { 'settings': { 'branch': 'main',
|
|
|
373
373
|
'fastllm.types.Usage.__add__': ('types.html#usage.__add__', 'fastllm/types.py'),
|
|
374
374
|
'fastllm.types.Usage.__eq__': ('types.html#usage.__eq__', 'fastllm/types.py'),
|
|
375
375
|
'fastllm.types.Usage.__init__': ('types.html#usage.__init__', 'fastllm/types.py'),
|
|
376
|
-
'fastllm.types._flat_rates': ('types.html#_flat_rates', 'fastllm/types.py'),
|
|
377
376
|
'fastllm.types.approx_pricing': ('types.html#approx_pricing', 'fastllm/types.py'),
|
|
378
377
|
'fastllm.types.effort_code': ('types.html#effort_code', 'fastllm/types.py'),
|
|
378
|
+
'fastllm.types.effort_kwargs': ('types.html#effort_kwargs', 'fastllm/types.py'),
|
|
379
379
|
'fastllm.types.effort_levels': ('types.html#effort_levels', 'fastllm/types.py'),
|
|
380
|
+
'fastllm.types.find_models': ('types.html#find_models', 'fastllm/types.py'),
|
|
380
381
|
'fastllm.types.fn_schema': ('types.html#fn_schema', 'fastllm/types.py'),
|
|
381
382
|
'fastllm.types.get_api_key': ('types.html#get_api_key', 'fastllm/types.py'),
|
|
382
383
|
'fastllm.types.get_model_info': ('types.html#get_model_info', 'fastllm/types.py'),
|
|
@@ -390,6 +391,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
390
391
|
'fastllm.types.price_tier': ('types.html#price_tier', 'fastllm/types.py'),
|
|
391
392
|
'fastllm.types.provider_req': ('types.html#provider_req', 'fastllm/types.py'),
|
|
392
393
|
'fastllm.types.register_model_info': ('types.html#register_model_info', 'fastllm/types.py'),
|
|
394
|
+
'fastllm.types.register_sub_model': ('types.html#register_sub_model', 'fastllm/types.py'),
|
|
393
395
|
'fastllm.types.resize_b64': ('types.html#resize_b64', 'fastllm/types.py'),
|
|
394
396
|
'fastllm.types.resolve_effort': ('types.html#resolve_effort', 'fastllm/types.py'),
|
|
395
397
|
'fastllm.types.tier_rate': ('types.html#tier_rate', 'fastllm/types.py'),
|
fastllm/acomplete.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""A unified async API for model completions and streaming"""
|
|
2
2
|
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_acomplete.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
6
|
__all__ = ['vendor_mapping', 'defaults', 'split_vendor', 'mk_client', 'ContextWindowExceededError', 'acomplete']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/
|
|
8
|
+
# %% ../nbs/03_acomplete.ipynb #f2f57253
|
|
9
9
|
import asyncio,json,httpx2
|
|
10
10
|
from fastcore.utils import *
|
|
11
11
|
from fastcore.meta import *
|
|
@@ -13,7 +13,7 @@ from fasttransport.core import AsyncHttpCli
|
|
|
13
13
|
from fasttransport.errors import APIError
|
|
14
14
|
|
|
15
15
|
from .types import *
|
|
16
|
-
from .codex import
|
|
16
|
+
from .codex import *
|
|
17
17
|
from aidialog.msg_parts import (Part, PartType, Msg, Text, Thinking, ToolUse, ToolResult, InputImage, InputAudio, InputVideo,
|
|
18
18
|
InputFile, mk_tool_res_msg, Completion)
|
|
19
19
|
from .streaming import *
|
|
@@ -22,15 +22,13 @@ from .openai_chat import *
|
|
|
22
22
|
from .anthropic import *
|
|
23
23
|
from .gemini import *
|
|
24
24
|
|
|
25
|
-
# %% ../nbs/
|
|
26
|
-
_codex_path = codex_auth_path()
|
|
27
|
-
_codex_json = _codex_path, ('tokens','access_token')
|
|
25
|
+
# %% ../nbs/03_acomplete.ipynb #32ee2546
|
|
28
26
|
vendor_mapping = {
|
|
29
27
|
"openai": ('openai', 'https://api.openai.com/v1', 'OPENAI_API_KEY'),
|
|
30
28
|
"anthropic": ('anthropic', 'https://api.anthropic.com', 'ANTHROPIC_API_KEY'),
|
|
31
29
|
"gemini": ('gemini', 'https://generativelanguage.googleapis.com/', 'GEMINI_API_KEY'),
|
|
32
30
|
"openai_chat": ('openai_chat', 'https://api.openai.com/v1', 'OPENAI_API_KEY'),
|
|
33
|
-
"codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN'
|
|
31
|
+
"codex": ('openai', 'https://chatgpt.com/backend-api/codex', 'CODEX_AUTH_TOKEN'),
|
|
34
32
|
"moonshot": ('openai_chat', "https://api.moonshot.ai/v1", "MOONSHOT_API_KEY"),
|
|
35
33
|
"deepseek": ('openai_chat', "https://api.deepseek.com/v1", "DEEPSEEK_API_KEY"),
|
|
36
34
|
"inception": ('openai_chat', "https://api.inceptionlabs.ai/v1", "INCEPTION_API_KEY"),
|
|
@@ -43,7 +41,7 @@ vendor_mapping = {
|
|
|
43
41
|
"meta": ('openai_chat', "https://api.meta.ai/v1", "META_API_KEY"),
|
|
44
42
|
"zai": ('openai_chat', "https://api.z.ai/api/paas/v4", "ZAI_API_KEY")}
|
|
45
43
|
|
|
46
|
-
# %% ../nbs/
|
|
44
|
+
# %% ../nbs/03_acomplete.ipynb #e3ed40fb
|
|
47
45
|
def split_vendor(model):
|
|
48
46
|
"Split a `'vendor/model'` path into `(vendor_name, model)`: the prefix must name a known vendor or a registered api (installed `fastllm.apis` plugins load on demand), else `(None, model)`."
|
|
49
47
|
if model and '/' in model:
|
|
@@ -51,23 +49,8 @@ def split_vendor(model):
|
|
|
51
49
|
if v in vendor_mapping or v in api_registry: return v, m
|
|
52
50
|
return None, model
|
|
53
51
|
|
|
54
|
-
# %% ../nbs/
|
|
55
|
-
|
|
56
|
-
"`flexicache` policy: expire codex clients when the codex auth file changes"
|
|
57
|
-
def policy(state, args, kwargs):
|
|
58
|
-
v = kwargs.get('vendor_name') or (args[1] if len(args)>1 else None)
|
|
59
|
-
if not v:
|
|
60
|
-
m = kwargs.get('model') or (args[0] if args else None)
|
|
61
|
-
v = split_vendor(m)[0] if m else None
|
|
62
|
-
if v != 'codex': return None
|
|
63
|
-
try: mt = Path(_codex_path).expanduser().stat().st_mtime
|
|
64
|
-
except OSError: mt = None
|
|
65
|
-
return mt if state != mt else None
|
|
66
|
-
policy.needs_args = True
|
|
67
|
-
return policy
|
|
68
|
-
|
|
69
|
-
# %% ../nbs/06_acomplete.ipynb #79075d95
|
|
70
|
-
@flexicache(_codex_mtime_policy())
|
|
52
|
+
# %% ../nbs/03_acomplete.ipynb #79075d95
|
|
53
|
+
@flexicache()
|
|
71
54
|
def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_token=None, base_url=None, xtra_hdrs=None,
|
|
72
55
|
timeout=httpx2.Timeout(connect=30, read=300, write=30, pool=10)):
|
|
73
56
|
err_msg = f"please pass a valid one vendor: {', '.join(list(vendor_mapping))} or pass `api_name`,`base_url` and `api_key`"
|
|
@@ -77,12 +60,8 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_t
|
|
|
77
60
|
if vendor_name:
|
|
78
61
|
override_base_url = base_url
|
|
79
62
|
try:
|
|
80
|
-
api_name, base_url, env_api_nm
|
|
63
|
+
api_name, base_url, env_api_nm = vendor_mapping[vendor_name]
|
|
81
64
|
base_url = override_base_url or base_url
|
|
82
|
-
if auth_json and not api_key and not oauth_token and not os.getenv(env_api_nm):
|
|
83
|
-
fn,keys = auth_json[0] # pyright: ignore[reportAssignmentType]
|
|
84
|
-
auth_fn = Path(fn).expanduser()
|
|
85
|
-
if auth_fn.exists(): api_key = nested_idx(json.loads(auth_fn.read_text()), *keys)
|
|
86
65
|
if not oauth_token: api_key = get_api_key(api_key, env_api_nm)
|
|
87
66
|
except KeyError: raise ValueError(f"Unknown vendor '{vendor_name}', {err_msg}")
|
|
88
67
|
elif base_url and (api_key or oauth_token): vendor_name, api_name = ifnone(vendor_name, 'custom'), ifnone(api_name, 'openai_chat')
|
|
@@ -93,7 +72,7 @@ def mk_client(model=None, vendor_name=None, api_name=None, api_key=None, oauth_t
|
|
|
93
72
|
cli = AsyncHttpCli(base_url, base_headers=hdrs, timeout=timeout)
|
|
94
73
|
return cli, api_name, vendor_name
|
|
95
74
|
|
|
96
|
-
# %% ../nbs/
|
|
75
|
+
# %% ../nbs/03_acomplete.ipynb #df851a5c
|
|
97
76
|
class ContextWindowExceededError(APIError): pass
|
|
98
77
|
|
|
99
78
|
def _is_ctx_exceeded(code, msg):
|
|
@@ -119,8 +98,8 @@ async def _classify_error_stream(gen):
|
|
|
119
98
|
async for x in gen: yield x
|
|
120
99
|
except APIError as e: raise _classify_error(e) from e
|
|
121
100
|
|
|
122
|
-
# %% ../nbs/
|
|
123
|
-
defaults = SimpleNamespace(debug_mode=False)
|
|
101
|
+
# %% ../nbs/03_acomplete.ipynb #f626a4e1
|
|
102
|
+
defaults = SimpleNamespace(debug_mode=False, reasoning_effort=None)
|
|
124
103
|
|
|
125
104
|
def _debug_print(model, api_name, vendor_name, payload, url):
|
|
126
105
|
"Pretty-print acomplete inputs when defaults.debug_mode is set"
|
|
@@ -133,7 +112,7 @@ def _debug_print(model, api_name, vendor_name, payload, url):
|
|
|
133
112
|
print(f"\033[1;33mpayload:\033[0m\n{pformat(p, width=120, sort_dicts=False)}")
|
|
134
113
|
print('━'*60)
|
|
135
114
|
|
|
136
|
-
# %% ../nbs/
|
|
115
|
+
# %% ../nbs/03_acomplete.ipynb #497c8565
|
|
137
116
|
async def _raise_if_done(e, n, retries, retry_delay, yielded=False):
|
|
138
117
|
e = _classify_error(e)
|
|
139
118
|
if yielded or not e.retryable or n == retries: raise e
|
|
@@ -154,7 +133,7 @@ async def _retry_stream(mk_gen, retries=2, retry_delay=0.5):
|
|
|
154
133
|
return
|
|
155
134
|
except APIError as e: await _raise_if_done(e, n, retries, retry_delay, yielded=yielded)
|
|
156
135
|
|
|
157
|
-
# %% ../nbs/
|
|
136
|
+
# %% ../nbs/03_acomplete.ipynb #2379ec94
|
|
158
137
|
@delegates(payload_kwargs)
|
|
159
138
|
async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None, oauth_token=None, base_url=None, endpoint=None, xtra_body=None, xtra_hdrs=None,
|
|
160
139
|
stream=False, previous_response_id=None, stop_callables=None, retries=2, retry_delay=0.5, **kwargs):
|
|
@@ -168,7 +147,12 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
168
147
|
if own:
|
|
169
148
|
vendor_name = ifnone(vendor_name, api_name) # an own-transport api: no HTTP client to build
|
|
170
149
|
if oauth_token: kwargs['oauth_token'] = oauth_token # its `mk_payload` applies the user's token itself
|
|
171
|
-
else:
|
|
150
|
+
else:
|
|
151
|
+
auth = vendor_auth.get(vendor_name)
|
|
152
|
+
if auth and not (api_key or oauth_token or os.getenv(vendor_mapping[vendor_name][2])): api_key = await auth()
|
|
153
|
+
cli, api_name, vendor_name = mk_client(model=model, vendor_name=vendor_name, api_name=api_name, api_key=api_key, oauth_token=oauth_token, base_url=base_url)
|
|
154
|
+
if (eff := defaults.reasoning_effort) and 'reasoning_effort' not in kwargs and effort_levels(get_model_info(model, vendor_name)):
|
|
155
|
+
kwargs |= effort_kwargs(model, eff, vendor_name)
|
|
172
156
|
api = api_registry[api_name]
|
|
173
157
|
if previous_response_id is not None:
|
|
174
158
|
if not getattr(api, 'supports_previous_response_id', False):
|
|
@@ -179,8 +163,7 @@ async def acomplete(msgs, model, api_name=None, vendor_name=None, api_key=None,
|
|
|
179
163
|
body = payload | ifnone(xtra_body, {})
|
|
180
164
|
if not own:
|
|
181
165
|
headers = xtra_hdrs or {}
|
|
182
|
-
if
|
|
183
|
-
if not any(k.lower() == 'session-id' for k in headers): headers = {**headers, 'session-id':key}
|
|
166
|
+
if hk := vendor_hdrs.get(vendor_name): headers = hk(body, headers)
|
|
184
167
|
ep = endpoint or api.endpoint
|
|
185
168
|
path,params = ep(model, stream) if callable(ep) else (ep, None)
|
|
186
169
|
if defaults.debug_mode: _debug_print(model, api_name, vendor_name, body, None if own else cli._url(path))
|
fastllm/anthropic.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Translate Anthropic Messages API requests, responses, and streams to and from fastllm's shared message types, with tool replay, media, and prompt caching"""
|
|
2
2
|
|
|
3
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_anthropic.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
6
|
__all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'finalize_usage', 'norm_finish', 'search_results',
|
|
@@ -9,9 +9,8 @@ __all__ = ['ant_tc_types', 'norm_tool_call', 'norm_tool_calls', 'norm_usage', 'f
|
|
|
9
9
|
'denorm_tool_choice', 'denorm_reasoning', 'denorm_web_search', 'denorm_system', 'denorm_user',
|
|
10
10
|
'denorm_image', 'denorm_file', 'denorm_tool_result', 'apply_cache_idxs', 'mk_payload', 'get_hdrs', 'cost']
|
|
11
11
|
|
|
12
|
-
# %% ../nbs/
|
|
12
|
+
# %% ../nbs/06_anthropic.ipynb #02afd3d7
|
|
13
13
|
import json, copy
|
|
14
|
-
from collections import Counter
|
|
15
14
|
from fastcore.utils import *
|
|
16
15
|
from fastcore.meta import *
|
|
17
16
|
from fasttransport.errors import api_error_from_event
|
|
@@ -22,7 +21,7 @@ from aidialog.msg_parts import (MdStr, Part, PartType, mk_part, Msg, Completion,
|
|
|
22
21
|
from .streaming import *
|
|
23
22
|
from .streaming import mk_acollect_stream
|
|
24
23
|
|
|
25
|
-
# %% ../nbs/
|
|
24
|
+
# %% ../nbs/06_anthropic.ipynb #5656d1d6
|
|
26
25
|
ant_tc_types = ("tool_use", "server_tool_use", "mcp_tool_use")
|
|
27
26
|
|
|
28
27
|
def norm_tool_call(b):
|
|
@@ -37,7 +36,7 @@ def norm_tool_calls(resp, delta=False):
|
|
|
37
36
|
if tc:= norm_tool_call(b): out.append(tc)
|
|
38
37
|
return out
|
|
39
38
|
|
|
40
|
-
# %% ../nbs/
|
|
39
|
+
# %% ../nbs/06_anthropic.ipynb #0a1e2a8e
|
|
41
40
|
def norm_usage(resp):
|
|
42
41
|
"Normalize Anthropic usage shape."
|
|
43
42
|
if not (usg:=resp.get("usage")): return None
|
|
@@ -58,7 +57,7 @@ def finalize_usage(usg, parts):
|
|
|
58
57
|
return Usage(prompt_tokens=usg.prompt_tokens, completion_tokens=ct-rt, total_tokens=usg.prompt_tokens+ct,
|
|
59
58
|
cached_tokens=usg.cached_tokens, cache_creation_tokens=usg.cache_creation_tokens, reasoning_tokens=rt, search_queries=usg.search_queries, raw=usg.raw)
|
|
60
59
|
|
|
61
|
-
# %% ../nbs/
|
|
60
|
+
# %% ../nbs/06_anthropic.ipynb #7a8b1f8f
|
|
62
61
|
def norm_finish(resp, tcs=None):
|
|
63
62
|
"Canonicalize finish_reason to OpenAI Chat values: stop, tool_calls, length, content_filter."
|
|
64
63
|
reason = resp.get("stop_reason")
|
|
@@ -66,7 +65,7 @@ def norm_finish(resp, tcs=None):
|
|
|
66
65
|
r = mp.get(reason, reason)
|
|
67
66
|
return FinishReason.tool_calls if r==FinishReason.stop and any(~L(tcs).attrgot('server')) else r
|
|
68
67
|
|
|
69
|
-
# %% ../nbs/
|
|
68
|
+
# %% ../nbs/06_anthropic.ipynb #9796be28
|
|
70
69
|
def search_results(parts, blocks):
|
|
71
70
|
"Give each server call the sources its `web_search_tool_result` block reports, one `title: url` line each, or the block's error code"
|
|
72
71
|
calls = {p.id: p for p in parts if isinstance(p, ToolUse) and p.server}
|
|
@@ -76,7 +75,7 @@ def search_results(parts, blocks):
|
|
|
76
75
|
call.text = res.get('error_code', '') if isinstance(res, dict) else MdStr('\n'.join(f"{r.get('title', '')}: {r['url']}" for r in res if r.get('url')))
|
|
77
76
|
return parts
|
|
78
77
|
|
|
79
|
-
# %% ../nbs/
|
|
78
|
+
# %% ../nbs/06_anthropic.ipynb #20d2e803
|
|
80
79
|
def _ant_part_type(typ):
|
|
81
80
|
"Map Anthropic content block type to canonical PartType."
|
|
82
81
|
ant_parts_mapping = dict(text=PartType.text, thinking=PartType.thinking, redacted_thinking=PartType.thinking, tool_use=PartType.tool_use, server_tool_use=PartType.tool_use, mcp_tool_use=PartType.tool_use)
|
|
@@ -97,12 +96,12 @@ def norm_parts(resp):
|
|
|
97
96
|
parts.append(Text(b.get("text", ""), citations=cites, raw=b))
|
|
98
97
|
return search_results(parts, resp.get("content", []))
|
|
99
98
|
|
|
100
|
-
# %% ../nbs/
|
|
99
|
+
# %% ../nbs/06_anthropic.ipynb #f24e52db
|
|
101
100
|
def raw_msg(resp):
|
|
102
101
|
"The assistant message as the API sent it"
|
|
103
102
|
return dict(role='assistant', content=resp['content'])
|
|
104
103
|
|
|
105
|
-
# %% ../nbs/
|
|
104
|
+
# %% ../nbs/06_anthropic.ipynb #5d1440b7
|
|
106
105
|
def norm_tr_parts(content):
|
|
107
106
|
"Normalize an Anthropic `tool_result` content str or block list to canonical `Part`s"
|
|
108
107
|
if isinstance(content, str): return [Text(content)]
|
|
@@ -118,7 +117,7 @@ def norm_tr_parts(content):
|
|
|
118
117
|
else: parts.append(mk_part(**b))
|
|
119
118
|
return parts
|
|
120
119
|
|
|
121
|
-
# %% ../nbs/
|
|
120
|
+
# %% ../nbs/06_anthropic.ipynb #a3869e31
|
|
122
121
|
def norm_sse_event(ev, **kwargs):
|
|
123
122
|
ev = obj2dict(ev)
|
|
124
123
|
typ = ev.get("type")
|
|
@@ -142,12 +141,12 @@ def norm_sse_event(ev, **kwargs):
|
|
|
142
141
|
elif typ == "error": raise api_error_from_event(ev)
|
|
143
142
|
return Delta(text=text, thinking=thinking, tool_calls=tcs, citations=citations, raw=ev, **kwargs)
|
|
144
143
|
|
|
145
|
-
# %% ../nbs/
|
|
144
|
+
# %% ../nbs/06_anthropic.ipynb #33cd3bd3
|
|
146
145
|
def delta_index_fn(d, typ, last_typ, last_idx):
|
|
147
146
|
'Returns accumulation index for current delta and updated last idx'
|
|
148
147
|
return nested_idx(d, 'raw', 'index'), None
|
|
149
148
|
|
|
150
|
-
# %% ../nbs/
|
|
149
|
+
# %% ../nbs/06_anthropic.ipynb #de900e34
|
|
151
150
|
def collate_raw(deltas):
|
|
152
151
|
"The assistant message rebuilt from a stream's events"
|
|
153
152
|
blocks = {}
|
|
@@ -164,7 +163,7 @@ def collate_raw(deltas):
|
|
|
164
163
|
elif typ == 'content_block_stop' and '_json' in blocks[i]: blocks[i]['input'] = json.loads(blocks[i].pop('_json') or '{}')
|
|
165
164
|
return dict(role='assistant', content=list(blocks.values()))
|
|
166
165
|
|
|
167
|
-
# %% ../nbs/
|
|
166
|
+
# %% ../nbs/06_anthropic.ipynb #c994adb1
|
|
168
167
|
@delegates(mk_acollect_stream, but=['index_fn', 'api_name'])
|
|
169
168
|
async def acollect_stream(resp, **kwargs):
|
|
170
169
|
res = mk_acollect_stream(norm_and_yield(resp, norm_sse_event), index_fn=delta_index_fn, api_name='anthropic', **kwargs)
|
|
@@ -172,16 +171,16 @@ async def acollect_stream(resp, **kwargs):
|
|
|
172
171
|
if isinstance(o, Completion): search_results(o.message.content, o.message.raw['content'])
|
|
173
172
|
yield o
|
|
174
173
|
|
|
175
|
-
# %% ../nbs/
|
|
174
|
+
# %% ../nbs/06_anthropic.ipynb #bb11ab62
|
|
176
175
|
def _ant_cc(block, p):
|
|
177
176
|
"Add the part's prompt-cache directive to `block`, if it has one."
|
|
178
177
|
if p.cache_control: block['cache_control'] = p.cache_control
|
|
179
178
|
return block
|
|
180
179
|
|
|
181
|
-
# %% ../nbs/
|
|
180
|
+
# %% ../nbs/06_anthropic.ipynb #62e9a042
|
|
182
181
|
def _sanid(id_str): return re.sub(r'[^a-zA-Z0-9_-]', '_', id_str or '')
|
|
183
182
|
|
|
184
|
-
# %% ../nbs/
|
|
183
|
+
# %% ../nbs/06_anthropic.ipynb #6ec772cb
|
|
185
184
|
def denorm_tool_use(p:ToolUse):
|
|
186
185
|
"Convert canonical tool_use Part to Anthropic tool_use content block."
|
|
187
186
|
return _ant_cc(dict(type='tool_use', id=_sanid(p.id or ''), name=p.name or '', input=p.arguments or {}), p)
|
|
@@ -209,7 +208,7 @@ def denorm_msgs(msgs:list[Msg]):
|
|
|
209
208
|
elif m.role == 'tool': res.append(denorm_tool(m))
|
|
210
209
|
return res
|
|
211
210
|
|
|
212
|
-
# %% ../nbs/
|
|
211
|
+
# %% ../nbs/06_anthropic.ipynb #f762a6f2
|
|
213
212
|
def denorm_tool_schs(tools):
|
|
214
213
|
"Convert canonical tools to Anthropic format."
|
|
215
214
|
out = []
|
|
@@ -220,7 +219,7 @@ def denorm_tool_schs(tools):
|
|
|
220
219
|
out.append(dict(name=name, description=desc, input_schema=params))
|
|
221
220
|
return out
|
|
222
221
|
|
|
223
|
-
# %% ../nbs/
|
|
222
|
+
# %% ../nbs/06_anthropic.ipynb #6118d7ea
|
|
224
223
|
def denorm_tool_choice(v):
|
|
225
224
|
"Map canonical tool_choice to Anthropic format."
|
|
226
225
|
if v is None: return None
|
|
@@ -229,7 +228,7 @@ def denorm_tool_choice(v):
|
|
|
229
228
|
if v in ('none', 'off', 'disabled'): return {'type': 'none'}
|
|
230
229
|
return {'type': 'tool', 'name': v}
|
|
231
230
|
|
|
232
|
-
# %% ../nbs/
|
|
231
|
+
# %% ../nbs/06_anthropic.ipynb #310f1e75
|
|
233
232
|
def denorm_reasoning(v):
|
|
234
233
|
"Map canonical reasoning_effort to Anthropic adaptive thinking + output_config."
|
|
235
234
|
mp = dict(minimal='low', low='low', medium='medium', high='high', xhigh='xhigh', max='max')
|
|
@@ -239,7 +238,7 @@ def denorm_reasoning(v):
|
|
|
239
238
|
elif isinstance(v, str) and v in mp: return {"thinking": {"type": "adaptive"}, "output_config": {"effort": mp.get(v)}}
|
|
240
239
|
raise err
|
|
241
240
|
|
|
242
|
-
# %% ../nbs/
|
|
241
|
+
# %% ../nbs/06_anthropic.ipynb #8fa9fbb8
|
|
243
242
|
def denorm_web_search(v):
|
|
244
243
|
"Map canonical web_search_options to Anthropic hosted web_search tool."
|
|
245
244
|
_max_uses = dict(low=1, medium=5, high=10)
|
|
@@ -249,13 +248,13 @@ def denorm_web_search(v):
|
|
|
249
248
|
if (u := (v or {}).get("user_location", {}).get("approximate")): t["user_location"] = {"type": "approximate", **u}
|
|
250
249
|
return t
|
|
251
250
|
|
|
252
|
-
# %% ../nbs/
|
|
251
|
+
# %% ../nbs/06_anthropic.ipynb #6b485275
|
|
253
252
|
def denorm_system(sp):
|
|
254
253
|
if isinstance(sp, Msg): sp = sp.content[0]
|
|
255
254
|
if isinstance(sp, Part): return [_ant_cc(dict(type='text', text=sp.text), sp)]
|
|
256
255
|
else: return sp
|
|
257
256
|
|
|
258
|
-
# %% ../nbs/
|
|
257
|
+
# %% ../nbs/06_anthropic.ipynb #1b990cc9
|
|
259
258
|
def denorm_user(m:Msg):
|
|
260
259
|
"Convert canonical user Msg to Anthropic user message."
|
|
261
260
|
parts = []
|
|
@@ -267,19 +266,19 @@ def denorm_user(m:Msg):
|
|
|
267
266
|
elif isinstance(p, InputFile): parts.append(_ant_cc(denorm_file(p), p))
|
|
268
267
|
return dict(role='user', content=parts)
|
|
269
268
|
|
|
270
|
-
# %% ../nbs/
|
|
269
|
+
# %% ../nbs/06_anthropic.ipynb #edd87272
|
|
271
270
|
def denorm_image(p, max_sz=None):
|
|
272
271
|
if (b64:=data_url(p.text)):
|
|
273
272
|
data = resize_b64(b64[1], max_sz) if max_sz else b64[1]
|
|
274
273
|
return {"type": "image", "source": dict(type="base64", media_type=b64[0], data=data)}
|
|
275
274
|
return {"type": "image", "source": {"type": "url", "url": p.text}}
|
|
276
275
|
|
|
277
|
-
# %% ../nbs/
|
|
276
|
+
# %% ../nbs/06_anthropic.ipynb #fc6bbdfc
|
|
278
277
|
def denorm_file(p):
|
|
279
278
|
if (b64:=data_url(p.text)): return {"type": "document", "source": dict(type="base64", media_type=b64[0], data=b64[1])}
|
|
280
279
|
return {"type": "document", "source": {"type": "url", "url": p.text}}
|
|
281
280
|
|
|
282
|
-
# %% ../nbs/
|
|
281
|
+
# %% ../nbs/06_anthropic.ipynb #37b63cc2
|
|
283
282
|
def denorm_tool_result(p:ToolResult):
|
|
284
283
|
"Convert canonical tool_result Part to Anthropic tool_result content block."
|
|
285
284
|
tid = _sanid(p.id or (p.raw or {}).get('call_id',''))
|
|
@@ -293,7 +292,7 @@ def denorm_tool_result(p:ToolResult):
|
|
|
293
292
|
return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=blocks), p)
|
|
294
293
|
return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=tool_text(p.text)), p)
|
|
295
294
|
|
|
296
|
-
# %% ../nbs/
|
|
295
|
+
# %% ../nbs/06_anthropic.ipynb #e4eb8d3b
|
|
297
296
|
def _add_cache_control(
|
|
298
297
|
msg, # Canonical Msg to mark
|
|
299
298
|
ttl=None # Cache TTL: '5m' (default) or '1h'
|
|
@@ -304,7 +303,7 @@ def _add_cache_control(
|
|
|
304
303
|
if idxs: msg.content[idxs[-1]].cache_control = cc
|
|
305
304
|
return msg
|
|
306
305
|
|
|
307
|
-
# %% ../nbs/
|
|
306
|
+
# %% ../nbs/06_anthropic.ipynb #6bc52ef5
|
|
308
307
|
def _clear_cache_control(obj):
|
|
309
308
|
"Clear canonical cache marks from a message or part."
|
|
310
309
|
if isinstance(obj, Msg):
|
|
@@ -332,12 +331,14 @@ def apply_cache_idxs(
|
|
|
332
331
|
except IndexError: continue
|
|
333
332
|
return sp
|
|
334
333
|
|
|
335
|
-
# %% ../nbs/
|
|
334
|
+
# %% ../nbs/06_anthropic.ipynb #587d4fe5
|
|
336
335
|
@delegates(payload_kwargs)
|
|
337
336
|
def mk_payload(msgs, model, **kwargs):
|
|
338
337
|
sp = kwargs.get('system')
|
|
339
338
|
if (ci:=kwargs.get('cache_idxs')) is not None: sp = apply_cache_idxs(msgs, ci, sp, kwargs.get('ttl'))
|
|
340
|
-
|
|
339
|
+
max_tokens = kwargs.get('max_tokens')
|
|
340
|
+
if max_tokens is None: max_tokens = get_model_info(model, 'anthropic').get('max_output_tokens') or 1024
|
|
341
|
+
payload = dict(model=model, messages=denorm_msgs(msgs), max_tokens=max_tokens)
|
|
341
342
|
if kwargs.get('stream'): payload['stream'] = True
|
|
342
343
|
if sp: payload['system'] = denorm_system(sp)
|
|
343
344
|
if tools:=kwargs.get('tools'): payload['tools'] = denorm_tool_schs(tools)
|
|
@@ -347,7 +348,7 @@ def mk_payload(msgs, model, **kwargs):
|
|
|
347
348
|
if (temp:=kwargs.get('temperature')) is not None: payload['temperature'] = temp
|
|
348
349
|
return payload
|
|
349
350
|
|
|
350
|
-
# %% ../nbs/
|
|
351
|
+
# %% ../nbs/06_anthropic.ipynb #60d52c1f
|
|
351
352
|
def get_hdrs(api_key=None, oauth_token=None):
|
|
352
353
|
"An OAuth token travels as a bearer under Anthropic's OAuth beta; an API key as `x-api-key`"
|
|
353
354
|
hdrs = {"anthropic-version": "2023-06-01"}
|
|
@@ -355,22 +356,18 @@ def get_hdrs(api_key=None, oauth_token=None):
|
|
|
355
356
|
return hdrs | {"x-api-key": get_api_key(api_key, 'ANTHROPIC_API_KEY')}
|
|
356
357
|
|
|
357
358
|
|
|
358
|
-
# %% ../nbs/
|
|
359
|
+
# %% ../nbs/06_anthropic.ipynb #0d03643a
|
|
359
360
|
def cost(usage, m):
|
|
360
361
|
raw = usage.raw
|
|
361
|
-
in_tok = raw['input_tokens']
|
|
362
|
-
cache_read = raw.get('cache_read_input_tokens', 0)
|
|
363
362
|
cc = raw.get('cache_creation', {}) or {}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
# %% ../nbs/04_anthropic.ipynb #f7c0b989
|
|
363
|
+
prompt = dict(input_cost_per_token=raw['input_tokens'], cache_read_input_token_cost=raw.get('cache_read_input_tokens', 0),
|
|
364
|
+
cache_creation_input_token_cost=cc.get('ephemeral_5m_input_tokens', raw.get('cache_creation_input_tokens', 0)),
|
|
365
|
+
cache_creation_input_token_cost_above_1hr=cc.get('ephemeral_1h_input_tokens', 0))
|
|
366
|
+
tier = price_tier(m, sum(prompt.values()))
|
|
367
|
+
toks = prompt | dict(output_cost_per_token=raw['output_tokens'])
|
|
368
|
+
return sum(n * tier_rate(m, k, tier) for k,n in toks.items())
|
|
369
|
+
|
|
370
|
+
# %% ../nbs/06_anthropic.ipynb #f7c0b989
|
|
374
371
|
api_registry.register('anthropic', norm_tool_calls=norm_tool_calls, norm_parts=norm_parts, norm_finish=norm_finish, norm_usage=norm_usage,
|
|
375
372
|
finalize_usage=finalize_usage, raw_msg=raw_msg, collate_raw=collate_raw, acollect_stream=acollect_stream, mk_payload=mk_payload,
|
|
376
373
|
cost=cost, get_hdrs=get_hdrs, endpoint='/v1/messages')
|