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' , 'structured' , '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
@@ -81,10 +81,12 @@ def _repr_markdown_(self: litellm.ModelResponse):
8181
8282# %% ../nbs/00_core.ipynb
8383def _bytes2content (data ):
84- "Convert bytes to litellm content dict (image or pdf)"
85- mtype = 'application/pdf' if data [:4 ] == b'%PDF' else mimetypes .types_map .get (f'.{ imghdr .what (None , h = data )} ' )
86- if not mtype : raise ValueError (f'Data must be image or PDF bytes, got { data [:10 ]} ' )
87- return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ base64 .b64encode (data ).decode ("utf-8" )} ' }
84+ "Convert bytes to litellm content dict (image, pdf, audio, video)"
85+ mtype = detect_mime (data )
86+ if not mtype : raise ValueError (f'Data must be a supported file type, got { data [:10 ]} ' )
87+ encoded = base64 .b64encode (data ).decode ("utf-8" )
88+ if mtype .startswith ('image/' ): return {'type' : 'image_url' , 'image_url' : f'data:{ mtype } ;base64,{ encoded } ' }
89+ return {'type' : 'file' , 'file' : {'file_data' : f'data:{ mtype } ;base64,{ encoded } ' }}
8890
8991# %% ../nbs/00_core.ipynb
9092def _add_cache_control (msg , # LiteLLM formatted msg
@@ -250,7 +252,7 @@ def cite_footnotes(stream_list):
250252def _mk_prefill (pf ): return ModelResponseStream ([StreamingChoices (delta = Delta (content = pf ,role = 'assistant' ))])
251253
252254# %% ../nbs/00_core.ipynb
253- _final_prompt = " You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed."
255+ _final_prompt = dict ( role = "user" , content = " You have no more tool uses. Please summarize your findings. If you did not complete your goal please tell the user what further work needs to be done so they can choose how best to proceed.")
254256
255257# %% ../nbs/00_core.ipynb
256258class Chat :
@@ -285,7 +287,7 @@ def _prep_msg(self, msg=None, prefill=None):
285287 cache_idxs = L (self .cache_idxs ).filter ().map (lambda o : o - 1 if o > 0 else o )
286288 else :
287289 cache_idxs = self .cache_idxs
288- if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache , cache_idxs , self .ttl )
290+ if msg : self .hist = mk_msgs (self .hist + [msg ], self .cache and 'claude' in self . model , cache_idxs , self .ttl )
289291 pf = [{"role" :"assistant" ,"content" :prefill }] if prefill else []
290292 return sp + self .hist + pf
291293
@@ -306,6 +308,7 @@ def _call(self, msg=None, prefill=None, temp=None, think=None, search=None, stre
306308 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
307309 # temperature is not supported when reasoning
308310 temperature = None if think else ifnone (temp ,self .temp ),
311+ caching = self .cache and 'claude' not in self .model ,
309312 ** kwargs )
310313 if stream :
311314 if prefill : yield _mk_prefill (prefill )
@@ -348,29 +351,6 @@ def print_hist(self:Chat):
348351 "Print each message on a different line"
349352 for r in self .hist : print (r , end = '\n \n ' )
350353
351- # %% ../nbs/00_core.ipynb
352- def random_tool_id ():
353- "Generate a random tool ID with 'toolu_' prefix"
354- random_part = '' .join (random .choices (string .ascii_letters + string .digits , k = 25 ))
355- return f'toolu_{ random_part } '
356-
357- # %% ../nbs/00_core.ipynb
358- def mk_tc (func , args , tcid = None , idx = 1 ):
359- if not tcid : tcid = random_tool_id ()
360- return {'index' : idx , 'function' : {'arguments' : args , 'name' : func }, 'id' : tcid , 'type' : 'function' }
361-
362- # %% ../nbs/00_core.ipynb
363- def mk_tc_req (content , tcs ):
364- msg = Message (content = content , role = 'assistant' , tool_calls = tcs , function_call = None )
365- msg .tool_calls = [{** dict (tc ), 'function' : dict (tc ['function' ])} for tc in msg .tool_calls ]
366- return msg
367-
368- # %% ../nbs/00_core.ipynb
369- def mk_tc_result (tc , result ): return {'tool_call_id' : tc ['id' ], 'role' : 'tool' , 'name' : tc ['function' ]['name' ], 'content' : result }
370-
371- # %% ../nbs/00_core.ipynb
372- def mk_tc_results (tcq , results ): return [mk_tc_result (a ,b ) for a ,b in zip (tcq .tool_calls , results )]
373-
374354# %% ../nbs/00_core.ipynb
375355async def _alite_call_func (tc , ns , raise_on_err = True ):
376356 try : fargs = json .loads (tc .function .arguments )
@@ -401,6 +381,7 @@ async def _call(self, msg=None, prefill=None, temp=None, think=None, search=None
401381 tools = self .tool_schemas , reasoning_effort = effort .get (think ), tool_choice = tool_choice ,
402382 # temperature is not supported when reasoning
403383 temperature = None if think else ifnone (temp ,self .temp ),
384+ caching = self .cache and 'claude' not in self .model ,
404385 ** kwargs )
405386 if stream :
406387 if prefill : yield _mk_prefill (prefill )
@@ -460,20 +441,18 @@ def mk_tr_details(tr, tc, mx=2000):
460441# %% ../nbs/00_core.ipynb
461442class AsyncStreamFormatter :
462443 def __init__ (self , include_usage = False , mx = 2000 ):
463- self .outp ,self .tcs ,self .include_usage ,self .think , self . mx = '' ,{},include_usage , False ,mx
444+ self .outp ,self .tcs ,self .include_usage ,self .mx = '' ,{},include_usage ,mx
464445
465446 def format_item (self , o ):
466447 "Format a single item from the response stream."
467448 res = ''
468449 if isinstance (o , ModelResponseStream ):
469450 d = o .choices [0 ].delta
470- if nested_idx (d , 'reasoning_content' ):
471- self .think = True
472- res += '🧠'
473- elif self .think :
474- self .think = False
475- res += '\n \n '
476- if c := d .content : res += c
451+ if nested_idx (d , 'reasoning_content' ) and d ['reasoning_content' ]!= '{"text": ""}' :
452+ res += '🧠' if not self .outp or self .outp [- 1 ]== '🧠' else '\n \n 🧠' # gemini can interleave reasoning
453+ elif self .outp and self .outp [- 1 ] == '🧠' : res += '\n \n '
454+ if c := d .content : # gemini has text content in last reasoning chunk
455+ res += f"\n \n { c } " if res and res [- 1 ] == '🧠' else c
477456 elif isinstance (o , ModelResponse ):
478457 if self .include_usage : res += f"\n Usage: { o .usage } "
479458 if c := getattr (contents (o ),'tool_calls' ,None ):
0 commit comments