python-fastllm 0.0.62__py3-none-any.whl → 0.0.64__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 +31 -31
- fastllm/chat.py +50 -55
- fastllm/codex.py +13 -0
- fastllm/gemini.py +29 -29
- fastllm/model_prices.json +35543 -21660
- fastllm/openai_chat.py +26 -26
- fastllm/openai_responses.py +36 -34
- fastllm/responses.py +28 -28
- fastllm/types.py +49 -33
- {python_fastllm-0.0.62.dist-info → python_fastllm-0.0.64.dist-info}/METADATA +13 -13
- python_fastllm-0.0.64.dist-info/RECORD +18 -0
- python_fastllm-0.0.62.dist-info/RECORD +0 -18
- {python_fastllm-0.0.62.dist-info → python_fastllm-0.0.64.dist-info}/WHEEL +0 -0
- {python_fastllm-0.0.62.dist-info → python_fastllm-0.0.64.dist-info}/entry_points.txt +0 -0
- {python_fastllm-0.0.62.dist-info → python_fastllm-0.0.64.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,7 +9,7 @@ __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
14
|
from collections import Counter
|
|
15
15
|
from fastcore.utils import *
|
|
@@ -22,7 +22,7 @@ from aidialog.msg_parts import (MdStr, Part, PartType, mk_part, Msg, Completion,
|
|
|
22
22
|
from .streaming import *
|
|
23
23
|
from .streaming import mk_acollect_stream
|
|
24
24
|
|
|
25
|
-
# %% ../nbs/
|
|
25
|
+
# %% ../nbs/06_anthropic.ipynb #5656d1d6
|
|
26
26
|
ant_tc_types = ("tool_use", "server_tool_use", "mcp_tool_use")
|
|
27
27
|
|
|
28
28
|
def norm_tool_call(b):
|
|
@@ -37,7 +37,7 @@ def norm_tool_calls(resp, delta=False):
|
|
|
37
37
|
if tc:= norm_tool_call(b): out.append(tc)
|
|
38
38
|
return out
|
|
39
39
|
|
|
40
|
-
# %% ../nbs/
|
|
40
|
+
# %% ../nbs/06_anthropic.ipynb #0a1e2a8e
|
|
41
41
|
def norm_usage(resp):
|
|
42
42
|
"Normalize Anthropic usage shape."
|
|
43
43
|
if not (usg:=resp.get("usage")): return None
|
|
@@ -58,7 +58,7 @@ def finalize_usage(usg, parts):
|
|
|
58
58
|
return Usage(prompt_tokens=usg.prompt_tokens, completion_tokens=ct-rt, total_tokens=usg.prompt_tokens+ct,
|
|
59
59
|
cached_tokens=usg.cached_tokens, cache_creation_tokens=usg.cache_creation_tokens, reasoning_tokens=rt, search_queries=usg.search_queries, raw=usg.raw)
|
|
60
60
|
|
|
61
|
-
# %% ../nbs/
|
|
61
|
+
# %% ../nbs/06_anthropic.ipynb #7a8b1f8f
|
|
62
62
|
def norm_finish(resp, tcs=None):
|
|
63
63
|
"Canonicalize finish_reason to OpenAI Chat values: stop, tool_calls, length, content_filter."
|
|
64
64
|
reason = resp.get("stop_reason")
|
|
@@ -66,7 +66,7 @@ def norm_finish(resp, tcs=None):
|
|
|
66
66
|
r = mp.get(reason, reason)
|
|
67
67
|
return FinishReason.tool_calls if r==FinishReason.stop and any(~L(tcs).attrgot('server')) else r
|
|
68
68
|
|
|
69
|
-
# %% ../nbs/
|
|
69
|
+
# %% ../nbs/06_anthropic.ipynb #9796be28
|
|
70
70
|
def search_results(parts, blocks):
|
|
71
71
|
"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
72
|
calls = {p.id: p for p in parts if isinstance(p, ToolUse) and p.server}
|
|
@@ -76,7 +76,7 @@ def search_results(parts, blocks):
|
|
|
76
76
|
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
77
|
return parts
|
|
78
78
|
|
|
79
|
-
# %% ../nbs/
|
|
79
|
+
# %% ../nbs/06_anthropic.ipynb #20d2e803
|
|
80
80
|
def _ant_part_type(typ):
|
|
81
81
|
"Map Anthropic content block type to canonical PartType."
|
|
82
82
|
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 +97,12 @@ def norm_parts(resp):
|
|
|
97
97
|
parts.append(Text(b.get("text", ""), citations=cites, raw=b))
|
|
98
98
|
return search_results(parts, resp.get("content", []))
|
|
99
99
|
|
|
100
|
-
# %% ../nbs/
|
|
100
|
+
# %% ../nbs/06_anthropic.ipynb #f24e52db
|
|
101
101
|
def raw_msg(resp):
|
|
102
102
|
"The assistant message as the API sent it"
|
|
103
103
|
return dict(role='assistant', content=resp['content'])
|
|
104
104
|
|
|
105
|
-
# %% ../nbs/
|
|
105
|
+
# %% ../nbs/06_anthropic.ipynb #5d1440b7
|
|
106
106
|
def norm_tr_parts(content):
|
|
107
107
|
"Normalize an Anthropic `tool_result` content str or block list to canonical `Part`s"
|
|
108
108
|
if isinstance(content, str): return [Text(content)]
|
|
@@ -118,7 +118,7 @@ def norm_tr_parts(content):
|
|
|
118
118
|
else: parts.append(mk_part(**b))
|
|
119
119
|
return parts
|
|
120
120
|
|
|
121
|
-
# %% ../nbs/
|
|
121
|
+
# %% ../nbs/06_anthropic.ipynb #a3869e31
|
|
122
122
|
def norm_sse_event(ev, **kwargs):
|
|
123
123
|
ev = obj2dict(ev)
|
|
124
124
|
typ = ev.get("type")
|
|
@@ -142,12 +142,12 @@ def norm_sse_event(ev, **kwargs):
|
|
|
142
142
|
elif typ == "error": raise api_error_from_event(ev)
|
|
143
143
|
return Delta(text=text, thinking=thinking, tool_calls=tcs, citations=citations, raw=ev, **kwargs)
|
|
144
144
|
|
|
145
|
-
# %% ../nbs/
|
|
145
|
+
# %% ../nbs/06_anthropic.ipynb #33cd3bd3
|
|
146
146
|
def delta_index_fn(d, typ, last_typ, last_idx):
|
|
147
147
|
'Returns accumulation index for current delta and updated last idx'
|
|
148
148
|
return nested_idx(d, 'raw', 'index'), None
|
|
149
149
|
|
|
150
|
-
# %% ../nbs/
|
|
150
|
+
# %% ../nbs/06_anthropic.ipynb #de900e34
|
|
151
151
|
def collate_raw(deltas):
|
|
152
152
|
"The assistant message rebuilt from a stream's events"
|
|
153
153
|
blocks = {}
|
|
@@ -164,7 +164,7 @@ def collate_raw(deltas):
|
|
|
164
164
|
elif typ == 'content_block_stop' and '_json' in blocks[i]: blocks[i]['input'] = json.loads(blocks[i].pop('_json') or '{}')
|
|
165
165
|
return dict(role='assistant', content=list(blocks.values()))
|
|
166
166
|
|
|
167
|
-
# %% ../nbs/
|
|
167
|
+
# %% ../nbs/06_anthropic.ipynb #c994adb1
|
|
168
168
|
@delegates(mk_acollect_stream, but=['index_fn', 'api_name'])
|
|
169
169
|
async def acollect_stream(resp, **kwargs):
|
|
170
170
|
res = mk_acollect_stream(norm_and_yield(resp, norm_sse_event), index_fn=delta_index_fn, api_name='anthropic', **kwargs)
|
|
@@ -172,16 +172,16 @@ async def acollect_stream(resp, **kwargs):
|
|
|
172
172
|
if isinstance(o, Completion): search_results(o.message.content, o.message.raw['content'])
|
|
173
173
|
yield o
|
|
174
174
|
|
|
175
|
-
# %% ../nbs/
|
|
175
|
+
# %% ../nbs/06_anthropic.ipynb #bb11ab62
|
|
176
176
|
def _ant_cc(block, p):
|
|
177
177
|
"Add the part's prompt-cache directive to `block`, if it has one."
|
|
178
178
|
if p.cache_control: block['cache_control'] = p.cache_control
|
|
179
179
|
return block
|
|
180
180
|
|
|
181
|
-
# %% ../nbs/
|
|
181
|
+
# %% ../nbs/06_anthropic.ipynb #62e9a042
|
|
182
182
|
def _sanid(id_str): return re.sub(r'[^a-zA-Z0-9_-]', '_', id_str or '')
|
|
183
183
|
|
|
184
|
-
# %% ../nbs/
|
|
184
|
+
# %% ../nbs/06_anthropic.ipynb #6ec772cb
|
|
185
185
|
def denorm_tool_use(p:ToolUse):
|
|
186
186
|
"Convert canonical tool_use Part to Anthropic tool_use content block."
|
|
187
187
|
return _ant_cc(dict(type='tool_use', id=_sanid(p.id or ''), name=p.name or '', input=p.arguments or {}), p)
|
|
@@ -209,7 +209,7 @@ def denorm_msgs(msgs:list[Msg]):
|
|
|
209
209
|
elif m.role == 'tool': res.append(denorm_tool(m))
|
|
210
210
|
return res
|
|
211
211
|
|
|
212
|
-
# %% ../nbs/
|
|
212
|
+
# %% ../nbs/06_anthropic.ipynb #f762a6f2
|
|
213
213
|
def denorm_tool_schs(tools):
|
|
214
214
|
"Convert canonical tools to Anthropic format."
|
|
215
215
|
out = []
|
|
@@ -220,7 +220,7 @@ def denorm_tool_schs(tools):
|
|
|
220
220
|
out.append(dict(name=name, description=desc, input_schema=params))
|
|
221
221
|
return out
|
|
222
222
|
|
|
223
|
-
# %% ../nbs/
|
|
223
|
+
# %% ../nbs/06_anthropic.ipynb #6118d7ea
|
|
224
224
|
def denorm_tool_choice(v):
|
|
225
225
|
"Map canonical tool_choice to Anthropic format."
|
|
226
226
|
if v is None: return None
|
|
@@ -229,7 +229,7 @@ def denorm_tool_choice(v):
|
|
|
229
229
|
if v in ('none', 'off', 'disabled'): return {'type': 'none'}
|
|
230
230
|
return {'type': 'tool', 'name': v}
|
|
231
231
|
|
|
232
|
-
# %% ../nbs/
|
|
232
|
+
# %% ../nbs/06_anthropic.ipynb #310f1e75
|
|
233
233
|
def denorm_reasoning(v):
|
|
234
234
|
"Map canonical reasoning_effort to Anthropic adaptive thinking + output_config."
|
|
235
235
|
mp = dict(minimal='low', low='low', medium='medium', high='high', xhigh='xhigh', max='max')
|
|
@@ -239,7 +239,7 @@ def denorm_reasoning(v):
|
|
|
239
239
|
elif isinstance(v, str) and v in mp: return {"thinking": {"type": "adaptive"}, "output_config": {"effort": mp.get(v)}}
|
|
240
240
|
raise err
|
|
241
241
|
|
|
242
|
-
# %% ../nbs/
|
|
242
|
+
# %% ../nbs/06_anthropic.ipynb #8fa9fbb8
|
|
243
243
|
def denorm_web_search(v):
|
|
244
244
|
"Map canonical web_search_options to Anthropic hosted web_search tool."
|
|
245
245
|
_max_uses = dict(low=1, medium=5, high=10)
|
|
@@ -249,13 +249,13 @@ def denorm_web_search(v):
|
|
|
249
249
|
if (u := (v or {}).get("user_location", {}).get("approximate")): t["user_location"] = {"type": "approximate", **u}
|
|
250
250
|
return t
|
|
251
251
|
|
|
252
|
-
# %% ../nbs/
|
|
252
|
+
# %% ../nbs/06_anthropic.ipynb #6b485275
|
|
253
253
|
def denorm_system(sp):
|
|
254
254
|
if isinstance(sp, Msg): sp = sp.content[0]
|
|
255
255
|
if isinstance(sp, Part): return [_ant_cc(dict(type='text', text=sp.text), sp)]
|
|
256
256
|
else: return sp
|
|
257
257
|
|
|
258
|
-
# %% ../nbs/
|
|
258
|
+
# %% ../nbs/06_anthropic.ipynb #1b990cc9
|
|
259
259
|
def denorm_user(m:Msg):
|
|
260
260
|
"Convert canonical user Msg to Anthropic user message."
|
|
261
261
|
parts = []
|
|
@@ -267,19 +267,19 @@ def denorm_user(m:Msg):
|
|
|
267
267
|
elif isinstance(p, InputFile): parts.append(_ant_cc(denorm_file(p), p))
|
|
268
268
|
return dict(role='user', content=parts)
|
|
269
269
|
|
|
270
|
-
# %% ../nbs/
|
|
270
|
+
# %% ../nbs/06_anthropic.ipynb #edd87272
|
|
271
271
|
def denorm_image(p, max_sz=None):
|
|
272
272
|
if (b64:=data_url(p.text)):
|
|
273
273
|
data = resize_b64(b64[1], max_sz) if max_sz else b64[1]
|
|
274
274
|
return {"type": "image", "source": dict(type="base64", media_type=b64[0], data=data)}
|
|
275
275
|
return {"type": "image", "source": {"type": "url", "url": p.text}}
|
|
276
276
|
|
|
277
|
-
# %% ../nbs/
|
|
277
|
+
# %% ../nbs/06_anthropic.ipynb #fc6bbdfc
|
|
278
278
|
def denorm_file(p):
|
|
279
279
|
if (b64:=data_url(p.text)): return {"type": "document", "source": dict(type="base64", media_type=b64[0], data=b64[1])}
|
|
280
280
|
return {"type": "document", "source": {"type": "url", "url": p.text}}
|
|
281
281
|
|
|
282
|
-
# %% ../nbs/
|
|
282
|
+
# %% ../nbs/06_anthropic.ipynb #37b63cc2
|
|
283
283
|
def denorm_tool_result(p:ToolResult):
|
|
284
284
|
"Convert canonical tool_result Part to Anthropic tool_result content block."
|
|
285
285
|
tid = _sanid(p.id or (p.raw or {}).get('call_id',''))
|
|
@@ -293,7 +293,7 @@ def denorm_tool_result(p:ToolResult):
|
|
|
293
293
|
return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=blocks), p)
|
|
294
294
|
return _ant_cc(dict(type='tool_result', tool_use_id=tid, content=tool_text(p.text)), p)
|
|
295
295
|
|
|
296
|
-
# %% ../nbs/
|
|
296
|
+
# %% ../nbs/06_anthropic.ipynb #e4eb8d3b
|
|
297
297
|
def _add_cache_control(
|
|
298
298
|
msg, # Canonical Msg to mark
|
|
299
299
|
ttl=None # Cache TTL: '5m' (default) or '1h'
|
|
@@ -304,7 +304,7 @@ def _add_cache_control(
|
|
|
304
304
|
if idxs: msg.content[idxs[-1]].cache_control = cc
|
|
305
305
|
return msg
|
|
306
306
|
|
|
307
|
-
# %% ../nbs/
|
|
307
|
+
# %% ../nbs/06_anthropic.ipynb #6bc52ef5
|
|
308
308
|
def _clear_cache_control(obj):
|
|
309
309
|
"Clear canonical cache marks from a message or part."
|
|
310
310
|
if isinstance(obj, Msg):
|
|
@@ -332,7 +332,7 @@ def apply_cache_idxs(
|
|
|
332
332
|
except IndexError: continue
|
|
333
333
|
return sp
|
|
334
334
|
|
|
335
|
-
# %% ../nbs/
|
|
335
|
+
# %% ../nbs/06_anthropic.ipynb #587d4fe5
|
|
336
336
|
@delegates(payload_kwargs)
|
|
337
337
|
def mk_payload(msgs, model, **kwargs):
|
|
338
338
|
sp = kwargs.get('system')
|
|
@@ -347,7 +347,7 @@ def mk_payload(msgs, model, **kwargs):
|
|
|
347
347
|
if (temp:=kwargs.get('temperature')) is not None: payload['temperature'] = temp
|
|
348
348
|
return payload
|
|
349
349
|
|
|
350
|
-
# %% ../nbs/
|
|
350
|
+
# %% ../nbs/06_anthropic.ipynb #60d52c1f
|
|
351
351
|
def get_hdrs(api_key=None, oauth_token=None):
|
|
352
352
|
"An OAuth token travels as a bearer under Anthropic's OAuth beta; an API key as `x-api-key`"
|
|
353
353
|
hdrs = {"anthropic-version": "2023-06-01"}
|
|
@@ -355,7 +355,7 @@ def get_hdrs(api_key=None, oauth_token=None):
|
|
|
355
355
|
return hdrs | {"x-api-key": get_api_key(api_key, 'ANTHROPIC_API_KEY')}
|
|
356
356
|
|
|
357
357
|
|
|
358
|
-
# %% ../nbs/
|
|
358
|
+
# %% ../nbs/06_anthropic.ipynb #0d03643a
|
|
359
359
|
def cost(usage, m):
|
|
360
360
|
raw = usage.raw
|
|
361
361
|
in_tok = raw['input_tokens']
|
|
@@ -370,7 +370,7 @@ def cost(usage, m):
|
|
|
370
370
|
cost += cache_1h * m.get('cache_creation_input_token_cost_above_1hr', 0)
|
|
371
371
|
return cost
|
|
372
372
|
|
|
373
|
-
# %% ../nbs/
|
|
373
|
+
# %% ../nbs/06_anthropic.ipynb #f7c0b989
|
|
374
374
|
api_registry.register('anthropic', norm_tool_calls=norm_tool_calls, norm_parts=norm_parts, norm_finish=norm_finish, norm_usage=norm_usage,
|
|
375
375
|
finalize_usage=finalize_usage, raw_msg=raw_msg, collate_raw=collate_raw, acollect_stream=acollect_stream, mk_payload=mk_payload,
|
|
376
376
|
cost=cost, get_hdrs=get_hdrs, endpoint='/v1/messages')
|