55# %% auto 0
66__all__ = ['sonn45' , 'opus45' , 'detls_tag' , 're_tools' , 'effort' , 'patch_litellm' , 'remove_cache_ckpts' , 'contents' , 'mk_msg' ,
77 'fmt2hist' , 'mk_msgs' , 'stream_with_complete' , 'lite_mk_func' , 'ToolResponse' , 'cite_footnote' ,
8- 'cite_footnotes' , 'Chat' , 'random_tool_id ' , 'mk_tc ' , 'mk_tc_req ' , 'mk_tc_result' , 'mk_tc_results ' ,
9- 'astream_with_complete' , 'AsyncChat' , 'mk_tr_details' , 'AsyncStreamFormatter' , ' adisplay_stream' ]
8+ 'cite_footnotes' , 'Chat' , 'astream_with_complete ' , 'AsyncChat ' , 'mk_tr_details ' , 'AsyncStreamFormatter ' ,
9+ 'adisplay_stream' ]
1010
1111# %% ../nbs/00_core.ipynb
1212import asyncio , base64 , json , litellm , mimetypes , random , string
@@ -79,11 +79,34 @@ def _repr_markdown_(self: litellm.ModelResponse):
7979opus45 = "claude-opus-4-5"
8080
8181# %% ../nbs/00_core.ipynb
82+ _sigs = {
83+ (b'%PDF' , 0 ): 'application/pdf' ,
84+ (b'RIFF' , 0 ): lambda d : 'audio/wav' if d [8 :12 ]== b'WAVE' else 'video/avi' if d [8 :12 ]== b'AVI ' else None ,
85+ (b'ID3' , 0 ): 'audio/mp3' ,
86+ (b'\xff \xfb ' , 0 ): 'audio/mp3' ,
87+ (b'\xff \xf3 ' , 0 ): 'audio/mp3' ,
88+ (b'FORM' , 0 ): lambda d : 'audio/aiff' if d [8 :12 ]== b'AIFF' else None ,
89+ (b'OggS' , 0 ): 'audio/ogg' ,
90+ (b'fLaC' , 0 ): 'audio/flac' ,
91+ (b'ftyp' , 4 ): lambda d : 'video/3gpp' if d [8 :11 ]== b'3gp' else 'video/mp4' ,
92+ (b'\x1a \x45 \xdf ' , 0 ): 'video/webm' ,
93+ (b'FLV' , 0 ): 'video/x-flv' ,
94+ (b'\x30 \x26 \xb2 \x75 ' , 0 ): 'video/wmv' ,
95+ (b'\x00 \x00 \x01 \xb3 ' , 0 ): 'video/mpeg' ,
96+ }
97+
98+ def _detect_mime (data ):
99+ for (sig ,pos ),mime in _sigs .items ():
100+ if data [pos :pos + len (sig )]== sig : return mime (data ) if callable (mime ) else mime
101+ return mimetypes .types_map .get (f'.{ imghdr .what (None , h = data )} ' )
102+
82103def _bytes2content (data ):
83- "Convert bytes to litellm content dict (image or pdf)"
84- mtype = 'application/pdf' if data [:4 ] == b'%PDF' else mimetypes .types_map .get (f'.{ imghdr .what (None , h = data )} ' )
85- if not mtype : raise ValueError (f'Data must be image or PDF bytes, got { data [:10 ]} ' )
86- return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ base64 .b64encode (data ).decode ("utf-8" )} ' }
104+ "Convert bytes to litellm content dict (image, pdf, audio, video)"
105+ mtype = _detect_mime (data )
106+ if not mtype : raise ValueError (f'Data must be a supported file type, got { data [:10 ]} ' )
107+ encoded = base64 .b64encode (data ).decode ("utf-8" )
108+ if mtype .startswith ('image/' ): return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ encoded } ' }
109+ return {'type' : 'file' , 'file' : {'file_data' : f'data:{ mtype } ;base64,{ encoded } ' }}
87110
88111# %% ../nbs/00_core.ipynb
89112def _add_cache_control (msg , # LiteLLM formatted msg
@@ -271,7 +294,7 @@ def _prep_msg(self, msg=None, prefill=None):
271294 cache_idxs = L (self .cache_idxs ).filter ().map (lambda o : o - 1 if o > 0 else o )
272295 else :
273296 cache_idxs = self .cache_idxs
274- if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache , cache_idxs , self .ttl )
297+ if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache and 'claude' in self . model , cache_idxs , self .ttl )
275298 pf = [{"role" :"assistant" ,"content" :prefill }] if prefill else []
276299 return sp + self .hist + pf
277300
@@ -292,6 +315,7 @@ def _call(self, msg=None, prefill=None, temp=None, think=None, search=None, stre
292315 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
293316 # temperature is not supported when reasoning
294317 temperature = None if think else ifnone (temp ,self .temp ),
318+ caching = self .cache and 'claude' not in self .model ,
295319 ** kwargs )
296320 if stream :
297321 if prefill : yield _mk_prefill (prefill )
@@ -328,35 +352,6 @@ def __call__(self,
328352 elif return_all : return list (result_gen ) # toolloop behavior
329353 else : return last (result_gen ) # normal chat behavior
330354
331- # %% ../nbs/00_core.ipynb
332- @patch
333- def print_hist (self :Chat ):
334- "Print each message on a different line"
335- for r in self .hist : print (r , end = '\n \n ' )
336-
337- # %% ../nbs/00_core.ipynb
338- def random_tool_id ():
339- "Generate a random tool ID with 'toolu_' prefix"
340- random_part = '' .join (random .choices (string .ascii_letters + string .digits , k = 25 ))
341- return f'toolu_{ random_part } '
342-
343- # %% ../nbs/00_core.ipynb
344- def mk_tc (func , args , tcid = None , idx = 1 ):
345- if not tcid : tcid = random_tool_id ()
346- return {'index' : idx , 'function' : {'arguments' : args , 'name' : func }, 'id' : tcid , 'type' : 'function' }
347-
348- # %% ../nbs/00_core.ipynb
349- def mk_tc_req (content , tcs ):
350- msg = Message (content = content , role = 'assistant' , tool_calls = tcs , function_call = None )
351- msg .tool_calls = [{** dict (tc ), 'function' : dict (tc ['function' ])} for tc in msg .tool_calls ]
352- return msg
353-
354- # %% ../nbs/00_core.ipynb
355- def mk_tc_result (tc , result ): return {'tool_call_id' : tc ['id' ], 'role' : 'tool' , 'name' : tc ['function' ]['name' ], 'content' : result }
356-
357- # %% ../nbs/00_core.ipynb
358- def mk_tc_results (tcq , results ): return [mk_tc_result (a ,b ) for a ,b in zip (tcq .tool_calls , results )]
359-
360355# %% ../nbs/00_core.ipynb
361356async def _alite_call_func (tc , ns , raise_on_err = True ):
362357 try : fargs = json .loads (tc .function .arguments )
@@ -387,6 +382,7 @@ async def _call(self, msg=None, prefill=None, temp=None, think=None, search=None
387382 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
388383 # temperature is not supported when reasoning
389384 temperature = None if think else ifnone (temp ,self .temp ),
385+ caching = self .cache and 'claude' not in self .model ,
390386 ** kwargs )
391387 if stream :
392388 if prefill : yield _mk_prefill (prefill )
@@ -453,9 +449,9 @@ def format_item(self, o):
453449 res = ''
454450 if isinstance (o , ModelResponseStream ):
455451 d = o .choices [0 ].delta
456- if nested_idx (d , 'reasoning_content' ):
452+ if nested_idx (d , 'reasoning_content' ) and d [ 'reasoning_content' ] != '{"text": ""}' :
457453 self .think = True
458- res += '🧠'
454+ res += '🧠' if not self . outp or self . outp [ - 1 ] == '🧠' else ' \n \n 🧠'
459455 elif self .think :
460456 self .think = False
461457 res += '\n \n '
0 commit comments