55# %% auto 0
66__all__ = ['sonn45' , 'detls_tag' , 're_tools' , 'effort' , 'patch_litellm' , 'remove_cache_ckpts' , 'contents' , 'mk_msg' , 'fmt2hist' ,
77 'mk_msgs' , 'stream_with_complete' , 'lite_mk_func' , 'ToolResponse' , 'cite_footnote' , 'cite_footnotes' , 'Chat' ,
8- 'random_tool_id' , 'mk_tc' , 'mk_tc_req' , 'mk_tc_result' , 'mk_tc_results' , 'astream_with_complete' ,
9- 'AsyncChat' , 'mk_tr_details' , 'AsyncStreamFormatter' , 'adisplay_stream' ]
8+ 'astream_with_complete' , 'AsyncChat' , 'mk_tr_details' , 'AsyncStreamFormatter' , 'adisplay_stream' ]
109
1110# %% ../nbs/00_core.ipynb
1211import asyncio , base64 , json , litellm , mimetypes , random , string
@@ -63,23 +62,37 @@ def _repr_markdown_(self: litellm.ModelResponse):
6362</details>"""
6463
6564# %% ../nbs/00_core.ipynb
66- register_model ({
67- "claude-sonnet-4-5" : {
68- "max_tokens" : 64000 , "max_input_tokens" : 200000 , "max_output_tokens" : 64000 ,
69- "input_cost_per_token" : 3e-06 , "output_cost_per_token" : 1.5e-05 , "cache_creation_input_token_cost" : 3.75e-06 , "cache_read_input_token_cost" : 3e-07 ,
70- "litellm_provider" : "anthropic" , "mode" : "chat" ,
71- "supports_function_calling" : True , "supports_parallel_function_calling" : True , "supports_vision" : True , "supports_prompt_caching" : True , "supports_response_schema" : True , "supports_system_messages" : True , "supports_reasoning" : True , "supports_assistant_prefill" : True ,
72- "supports_tool_choice" : True , "supports_computer_use" : True
73- }
74- });
7565sonn45 = "claude-sonnet-4-5"
7666
7767# %% ../nbs/00_core.ipynb
68+ _sigs = {
69+ (b'%PDF' , 0 ): 'application/pdf' ,
70+ (b'RIFF' , 0 ): lambda d : 'audio/wav' if d [8 :12 ]== b'WAVE' else 'video/avi' if d [8 :12 ]== b'AVI ' else None ,
71+ (b'ID3' , 0 ): 'audio/mp3' ,
72+ (b'\xff \xfb ' , 0 ): 'audio/mp3' ,
73+ (b'\xff \xf3 ' , 0 ): 'audio/mp3' ,
74+ (b'FORM' , 0 ): lambda d : 'audio/aiff' if d [8 :12 ]== b'AIFF' else None ,
75+ (b'OggS' , 0 ): 'audio/ogg' ,
76+ (b'fLaC' , 0 ): 'audio/flac' ,
77+ (b'ftyp' , 4 ): lambda d : 'video/3gpp' if d [8 :11 ]== b'3gp' else 'video/mp4' ,
78+ (b'\x1a \x45 \xdf ' , 0 ): 'video/webm' ,
79+ (b'FLV' , 0 ): 'video/x-flv' ,
80+ (b'\x30 \x26 \xb2 \x75 ' , 0 ): 'video/wmv' ,
81+ (b'\x00 \x00 \x01 \xb3 ' , 0 ): 'video/mpeg' ,
82+ }
83+
84+ def _detect_mime (data ):
85+ for (sig ,pos ),mime in _sigs .items ():
86+ if data [pos :pos + len (sig )]== sig : return mime (data ) if callable (mime ) else mime
87+ return mimetypes .types_map .get (f'.{ imghdr .what (None , h = data )} ' )
88+
7889def _bytes2content (data ):
79- "Convert bytes to litellm content dict (image or pdf)"
80- mtype = 'application/pdf' if data [:4 ] == b'%PDF' else mimetypes .types_map .get (f'.{ imghdr .what (None , h = data )} ' )
81- if not mtype : raise ValueError (f'Data must be image or PDF bytes, got { data [:10 ]} ' )
82- return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ base64 .b64encode (data ).decode ("utf-8" )} ' }
90+ "Convert bytes to litellm content dict (image, pdf, audio, video)"
91+ mtype = _detect_mime (data )
92+ if not mtype : raise ValueError (f'Data must be a supported file type, got { data [:10 ]} ' )
93+ encoded = base64 .b64encode (data ).decode ("utf-8" )
94+ if mtype .startswith ('image/' ): return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ encoded } ' }
95+ return {'type' : 'file' , 'file' : {'file_data' : f'data:{ mtype } ;base64,{ encoded } ' }}
8396
8497# %% ../nbs/00_core.ipynb
8598def _add_cache_control (msg , # LiteLLM formatted msg
@@ -267,7 +280,7 @@ def _prep_msg(self, msg=None, prefill=None):
267280 cache_idxs = L (self .cache_idxs ).filter ().map (lambda o : o - 1 if o > 0 else o )
268281 else :
269282 cache_idxs = self .cache_idxs
270- if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache , cache_idxs , self .ttl )
283+ if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache and 'claude' in self . model , cache_idxs , self .ttl )
271284 pf = [{"role" :"assistant" ,"content" :prefill }] if prefill else []
272285 return sp + self .hist + pf
273286
@@ -288,6 +301,7 @@ def _call(self, msg=None, prefill=None, temp=None, think=None, search=None, stre
288301 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
289302 # temperature is not supported when reasoning
290303 temperature = None if think else ifnone (temp ,self .temp ),
304+ caching = self .cache and 'claude' not in self .model ,
291305 ** kwargs )
292306 if stream :
293307 if prefill : yield _mk_prefill (prefill )
@@ -324,35 +338,6 @@ def __call__(self,
324338 elif return_all : return list (result_gen ) # toolloop behavior
325339 else : return last (result_gen ) # normal chat behavior
326340
327- # %% ../nbs/00_core.ipynb
328- @patch
329- def print_hist (self :Chat ):
330- "Print each message on a different line"
331- for r in self .hist : print (r , end = '\n \n ' )
332-
333- # %% ../nbs/00_core.ipynb
334- def random_tool_id ():
335- "Generate a random tool ID with 'toolu_' prefix"
336- random_part = '' .join (random .choices (string .ascii_letters + string .digits , k = 25 ))
337- return f'toolu_{ random_part } '
338-
339- # %% ../nbs/00_core.ipynb
340- def mk_tc (func , args , tcid = None , idx = 1 ):
341- if not tcid : tcid = random_tool_id ()
342- return {'index' : idx , 'function' : {'arguments' : args , 'name' : func }, 'id' : tcid , 'type' : 'function' }
343-
344- # %% ../nbs/00_core.ipynb
345- def mk_tc_req (content , tcs ):
346- msg = Message (content = content , role = 'assistant' , tool_calls = tcs , function_call = None )
347- msg .tool_calls = [{** dict (tc ), 'function' : dict (tc ['function' ])} for tc in msg .tool_calls ]
348- return msg
349-
350- # %% ../nbs/00_core.ipynb
351- def mk_tc_result (tc , result ): return {'tool_call_id' : tc ['id' ], 'role' : 'tool' , 'name' : tc ['function' ]['name' ], 'content' : result }
352-
353- # %% ../nbs/00_core.ipynb
354- def mk_tc_results (tcq , results ): return [mk_tc_result (a ,b ) for a ,b in zip (tcq .tool_calls , results )]
355-
356341# %% ../nbs/00_core.ipynb
357342async def _alite_call_func (tc , ns , raise_on_err = True ):
358343 try : fargs = json .loads (tc .function .arguments )
@@ -383,6 +368,7 @@ async def _call(self, msg=None, prefill=None, temp=None, think=None, search=None
383368 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
384369 # temperature is not supported when reasoning
385370 temperature = None if think else ifnone (temp ,self .temp ),
371+ caching = self .cache and 'claude' not in self .model ,
386372 ** kwargs )
387373 if stream :
388374 if prefill : yield _mk_prefill (prefill )
@@ -449,9 +435,9 @@ def format_item(self, o):
449435 res = ''
450436 if isinstance (o , ModelResponseStream ):
451437 d = o .choices [0 ].delta
452- if nested_idx (d , 'reasoning_content' ):
438+ if nested_idx (d , 'reasoning_content' ) and d [ 'reasoning_content' ] != '{"text": ""}' :
453439 self .think = True
454- res += '🧠'
440+ res += '🧠' if not self . outp or self . outp [ - 1 ] == '🧠' else ' \n \n 🧠'
455441 elif self .think :
456442 self .think = False
457443 res += '\n \n '
0 commit comments