From 478d9bb7f630da79783e22c8ecf9ab8c6aab97c0 Mon Sep 17 00:00:00 2001 From: nandini bagga Date: Sun, 20 Aug 2023 01:32:42 +0530 Subject: [PATCH 01/13] modified favicon path --- docs/my-website/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docusaurus.config.js b/docs/my-website/docusaurus.config.js index 81d1cc8d48..4af0e6f3b2 100644 --- a/docs/my-website/docusaurus.config.js +++ b/docs/my-website/docusaurus.config.js @@ -8,7 +8,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula'); const config = { title: 'liteLLM', tagline: 'Simplify LLM API Calls', - favicon: 'static/img/favicon.ico', + favicon: '/img/favicon.ico', // Set the production url of your site here url: 'https://litellm.vercel.app/', From 8710e54f6bad16200b3afd20688cb840900e08b1 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Sat, 19 Aug 2023 16:29:12 -0700 Subject: [PATCH 02/13] add logit bias --- litellm/tests/test_completion.py | 3 +-- litellm/utils.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 370668afb0..e168c23244 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -77,7 +77,7 @@ def test_completion_claude_stream(): def test_completion_cohere(): try: response = completion( - model="command-nightly", messages=messages, max_tokens=100 + model="command-nightly", messages=messages, max_tokens=100, logit_bias={40: 10} ) # Add any assertions here to check the response print(response) @@ -91,7 +91,6 @@ def test_completion_cohere(): except Exception as e: pytest.fail(f"Error occurred: {e}") - def test_completion_cohere_stream(): try: messages = [ diff --git a/litellm/utils.py b/litellm/utils.py index 290e64ddba..5346ce62a1 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -452,6 +452,8 @@ def get_optional_params( optional_params["temperature"] = temperature if max_tokens != float("inf"): optional_params["max_tokens"] = max_tokens + if logit_bias != {}: + optional_params["logit_bias"] = logit_bias return optional_params elif custom_llm_provider == "replicate": # any replicate models From 9564d92087fcfa899ad2387168aeb3793579c23d Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Sat, 19 Aug 2023 16:29:27 -0700 Subject: [PATCH 03/13] bump v --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aebd15b8b0..269bb42ed8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "0.1.426" +version = "0.1.428" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT License" From 665daa0d164427c93be40ea6149a65af82c979fd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 19 Aug 2023 16:33:41 -0700 Subject: [PATCH 04/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11fc9cbcba..40aa4fb2e1 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ messages = [{ "content": "Hello, how are you?","role": "user"}] response = completion(model="gpt-3.5-turbo", messages=messages) # cohere call -response = completion("command-nightly", messages) +response = completion(model="command-nightly", messages) ``` Code Sample: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing) From 5e53c9664c3bca688ad5c38fa427bca7249af59e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 16:34:52 -0700 Subject: [PATCH 05/13] allow custom provider via model name --- litellm/__pycache__/__init__.cpython-311.pyc | Bin 4668 -> 5519 bytes litellm/__pycache__/main.cpython-311.pyc | Bin 26030 -> 26630 bytes litellm/__pycache__/timeout.cpython-311.pyc | Bin 5193 -> 5154 bytes litellm/__pycache__/utils.cpython-311.pyc | Bin 48767 -> 49909 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 201 -> 201 bytes .../__pycache__/aispend.cpython-311.pyc | Bin 4923 -> 5025 bytes .../__pycache__/berrispend.cpython-311.pyc | Bin 4690 -> 4790 bytes .../__pycache__/helicone.cpython-311.pyc | Bin 4526 -> 4578 bytes .../__pycache__/supabase.cpython-311.pyc | Bin 5615 -> 5733 bytes litellm/main.py | 3 +++ litellm/tests/test_completion.py | 12 ++++++++++++ 11 files changed, 15 insertions(+) diff --git a/litellm/__pycache__/__init__.cpython-311.pyc b/litellm/__pycache__/__init__.cpython-311.pyc index 991b3d63589afc64a3770c78b929cafb40176148..480251bd565f14a8e15d43b411f5d9c807062199 100644 GIT binary patch literal 5519 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 G>AwN|sY49_ delta 1890 zcmc(gJ#5=X6vvOGWKpt6earHfB-^qj*^E2Wh9 z11Q8HLzkj8SRhN5c1TfV^iU5SG`ib)BLuX_>Mmv1cIi9XN(-YX(5cjWzxV$4?%f?p z3HoB>hm8EKB>50r>7RZktFdD_QNPC{^a2q?I2B=3LYM>R36BLLU>9*=H^er=VGps5|N#; z011-NRTd@@CyT;rm+Qsg0GCIIkvM$VZXGWJILf2<5lK8pb<(Mz-57f&?0O|2^cwu~ z!f_rc@mC;Tw}uezAYIQ9PQV>@f_H&;gZF^7R3F47#D0>3 z(E(6tP#Mxk>is1i50U|rfj9((K`3M)4nfQ*DJ6XbTPuAI`yCEA%s9+C9CSG3Fvp1V zutFAA7$!N`aD?Q^uv*XxxWFOEN7Ukd77OaAqK)A(Z5)ql6L>&EdJLrg^7nM7;qKk)nE2Tfht2B3{&P;al1gUea#k+c1~=5amZN*b}?Aa{mHx z2%Vg{j(IL`V$ZwX9WeeK(i0H$I77IFocX<{(B>4v=7ndSi<-+~2k?Q|9c5FSVwos= zweq7wJq@yGekM-LviW&YZ-=1!86?1wTzXO~P8X*p9~Z}GC#G}{t9Thi2A}z>nBan@ z=v{3KumSmG&0BQ&-)VYlJG!z-%6ldA7a@~rwpW$f#xq6Vs#FyyDypI>HT_U`L08kQ zv)qiiJ{LXPo7+3A>uXmw)%0lg8=F*~$|j64j5ACyOfpO{Of$?d%$k1$hU#-HooBegaFbzyp@-q^BmP}ng!4RA zl_k9l1$ZH-`XhYHZGq!B3k7M*tVj&umOr5h9n30Do~_c#K208gSv~2~9Q~M3NddF^ z22T&@R+)x(!K`%Vv_v14XlMt_$`%_(RDDJh`(RdT@brk1JsPZlS%dk;JUeK-0(MOe z4L4S4yi}nbPr&R{Q08p}Fnsoq*%cY-6;Y(?%j|LftGp4gd4Zd;(OdY;t;pm=0=YYE zf$!%jO4?#u2o`9;knEr<3Ii>`Td`%c5lPm&kUM0#qPAOzLu1&MuW6qYhrSlrjHD~T zR=R@GZu^m3prd!srMqXJo=YFmjmo)HvEe(x-Qj;jNWKHp(F(urh;=3~uR22IS3p(x zWr59G7C^iwtT|$hi3jkf!74;Bd~bqv>KNQi8*=x;t@gl)6Sj!BUh_q?w~n}nZ1Mk# YAHc>IiqMu>sGGLTLi_Z;nDblv6Ou&xBLDyZ diff --git a/litellm/__pycache__/main.cpython-311.pyc b/litellm/__pycache__/main.cpython-311.pyc index 7c1a53cf54f7ee32629a70f6d6e9af20942e5b9c..da220e0b9ba2ed202b74d8fc9b0f538fcf80368f 100644 GIT binary patch literal 26630 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 YG5(ZBuR*z=2a9RVO^8_^5|Q%%0nP+-j{pDw diff --git a/litellm/__pycache__/timeout.cpython-311.pyc b/litellm/__pycache__/timeout.cpython-311.pyc index 09f976993936f4b5c3eb904c22c7dadb0be4e9a9..68f0223aaa7f935d03d22dce1680f51d6f3275ef 100644 GIT binary patch delta 515 zcmX@9u}Fh=IWI340}wbpxu3FgB5#H?M=Ez3QwnD)50K<)VU6NtVsK|j;cj6_;h8u= zoRN3pieAoikPZ;oY|40>l~G}{Ap2}a0Xd*RF$W_9!;c?78W=vXOg_jVGx-KbJTn`w z&}3^)d0!S@p$`l&;)a0ObpeG-0ty!el&%OUT@X;ZA*OLbOyh<~`Gt_I3qTT#J~A_j z3w>b#5+4|t1cg4Z0`&vU|G>3*7v};-#@&;nxiy$MfEKiKN0~8$EPxS28OID%Jz0w< zuU;19_)Z`Ja{FCQzK)Vk=^p71OhTMe9~f{DADJ1rM4^hlYA|r|!!+@50^NawfNKKE zf0be2k)Kg=ky~Z59B;6^oI0!c2L@b3(B=uesf>(Xo4@fnGBWyYwh$;_WDJ_TQt%L? p=jI$CW=6&flM93^G}su_wRWU`V6b2m1rsw;5o{z@*qyOvTqkvBt{DU~~oDTO1I2S{?ZutxDRF}O3NaJ4X`a8H~d z&d4)yMek-i#@nonN}HwFXEU-X05uoOOy14WYRtmx$8p})?Xv_$arzG lmvDt92ZOxw4)G7nW{jdA7=XkLF=+$`ECL~lCchH40026Qkx2jm diff --git a/litellm/__pycache__/utils.cpython-311.pyc b/litellm/__pycache__/utils.cpython-311.pyc index b872895f9865e6836b2f8d31cc4383376dc91729..ba16f47ba96c13f27626b1c0038501226fdfeb4b 100644 GIT binary patch delta 20431 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 delta 824 zcmb`E&rTCj6vpo~g-A1P8UD7l6sEKt3N0xZ5r~qY5E?=$B513$8Z))DLMtswg$V3q zKk)%v$;x2ZOu}Xsny@@WuHkM-c!a_50G`o}A-ePCm+$1akmHlxZX)e#(l8`JHw_Ff15VrsyMVQ^COgE?t|NU*| zj2tf5epsVprhx)S$SyIgz6OEu?!5$fQgHvFsh*V1rzxKXK7HOat86((cD^-hOECY0 zPA=)=89JWP#Vi%Gr(*s@%)@d;7pqjP0`EIdJ~&OvCrQkq2rD%-xy4OduuNH!rpY;O z&a`Y6ou9#Upl^otP91may$RqY)}Ms=MZJF!_`Y**2MYw5zkh#&-uB;8ozx#^A&CWI z2kkU40;35Um}TM(=Iz;Ur``cJm|(G_(QIpV805FKIQJ$Dmy&DzTz}oPJfXM+pX8~V z;|%hL4tLcF2DvKAQ$gm6%2%O}V~Qb!cIrtmtcBr430lht>y|V(eb?qFGKq)#OQ2v c;P;^H+Kj+Gj>91St{47agUf%6D@_>iPl^>Zn*aa+ diff --git a/litellm/integrations/__pycache__/berrispend.cpython-311.pyc b/litellm/integrations/__pycache__/berrispend.cpython-311.pyc index 87b3f5e36e7e08e09f86bd49785df2266bda03fc..ccb4bb900f760d22c088cfde2706995d7791bd69 100644 GIT binary patch delta 975 zcmb`EPiPZC6o+S%ZL$fwo7t_&c9U&P%I0rPNfd39s*nZ|q0&V3AQpR3q$fe4>L~>e z=2+NEjvfSaR0@)#cjM;Va6G&)wxmBY^%FUGBNTqd?!D+&WIMv6gw~srU z-OA^eJzOWaKJa@LmEr=$CFVogl3Q%_aD$)Bfcf!sC)k_6*gUkVeXH8DYQ(C+;E>tg zbVE5@rOkseOUJnZ#c0R*8MR`-<4N4syi4OVjyxPnqHmM;PMp2IllV zaZhCDkZ!iuT`dc1TDxSSV?80sE7KdWh_fB~;=9i;=!R3m@oajgE-*C4x=@IC5ky-IS0Kqlp|%=9piM_!#1l$7PsHz=Vu(2{=nyG?5+ljPfJ^l7FSi zB|vVD+wFvPhVWDh-Cf&iC3or4p9Jo!}}6+FZ6H-7_`xF13Q delta 874 zcmb`E&rcIU6vt=k7O~m3+p5s5ZCS9tR<^O$P=N#qC~0FL?V*NW#i%KkR%oT5RE(6H zJ(6rpIC?OVqlU!napTG3D;dLhAnBj5B;33>s~3ZKbMnde<;}~yyziUOp@X2~gWYaJ z?7U5WE2v$c96=8I_iu6;uA`UTuDwy4oq_2iCHe6=QAXf)jwttGrT~k}WM%~_ugT0- zOW6WmK8g=(10+5Mv+!t8JWXjF<*CF+$tO;DQ%iloy**;GKuutf}7l7M~}49{7CQoowD|3jvT4#yLy3 zqnR=~Q5NU$VWx19VQ$N?TxV8Z;Ry>1DNDw*lEqomm>F99B-;V>Pf{@nLvt;0u6eJ~ z_J`h;TmJZ=Z}h-7+VYJN-&k{sI>p^QIAumVl%SHbH%+7g+NV${YG$)Ux}{+v4QoS0 zn$Sx`%F@2@`|F1Nyy!O2Z}D8*;dVGfKyGn&5?oQnU(F=ym-iyXpE&!u;Z`3xzjRzN zmvmQ^g+Rn>&0+)np@U(o%Rr|^tqLVvR=*4Vn5tG+xY2<^vM$4u3S6!-=+%fW!r}^C zsxYV=(_g~VHSn%8PG!-UUWalG#5D$uDEbnVR>4za&{*PY8mb!*s57Wf(xR|X0Q(|? z_J*`NJT8H|%xF+4s1@O15nM|Q8j0zfu>1l%8w}bL(l((`0invEY|ue-(5@=uR2IIb mr=eJd?q>|6M{uyOX0mNFf@zG|BF@(1zF&otf0SziTkubSL_RVA diff --git a/litellm/integrations/__pycache__/helicone.cpython-311.pyc b/litellm/integrations/__pycache__/helicone.cpython-311.pyc index 03de753b4e331937cdd9cda61a9965d3e5fa0413..972c339ed39ab1cafaa3a5632470bf3e4ee7db9c 100644 GIT binary patch delta 733 zcmZ{iKWGzC9LL}NZF1%=@5M`N?vf_y<-Bl)NJ^9nk%lw{5lZQ128V)zix%wUAa#<0 zOW|fEPC98NOTi8qMG)@FLEmN&hmJ3hfTO=xs9Eaq_}q`*z3=zF@6RXamt%a;bq&GO z`Tg_J1N_BEi-Iv&6yJ#IRp{$4bwpNG9*t$=% z7PWmgCl@t_0Aa#rDTKYjdu2^tZ*rd&;LjO4L$44+(n97OqiffhgtAWmacJIP(nWhV zVO9;Y+B0d-=T`4TumYPzS4a|?%I$AZy!lND@g|J1Bme@nYfK9)<6(^w`;@pR#Epm-6EE~z5orzO2zMc*$3uEXm~y5nDmlSh!`h=HcS@WS z;zY!ai5pf|BC>MSjc^+TE*H2UC_|)tDYk1gU!#RuSnnLk#|e1xkza#TsKD-WXvn<< zXI8mQtE;fmrFmFBKtq)#t=@%BiFW{3k~T;PXPTI>bAbKsv=VHm+YU8%!rpe=dm2Jv z?m!g6mrFpwp(3&v9H+mk{1)wQ1ut>#jw~q+aK3<+Ray-FL;aqh5QZ*e@EyN~%eF-d WZS{#Dz^uzTKJQQd^YuA~D1QNd9?hcw delta 704 zcmZ{gO=uHA6vt;bAGKk#*_H@4AzvmgS=Uqosi$wC_OL{s&ZF&DuLM!;fS~GlAF(Mlh*hN_EePOU=Qt~8iHVn)>fh6e=`K3v%lMJ zHf6_>B_Cq38c$<2WLo9kK0_F&Uj|PO=5I23a#+xapn;(6`hwm0)*~`|ml`-&B9y)o z@s+?`*+_4sVX0D28_J-5R1A1qLvX7IE9($X;dqtAt5_NYDcCq=+VvY^Ufw*vqmFH> zV^~cRH3_Km6`s+-4ll;Le<7%QVh8HMpVtjxjnERJSz zBu64S5JGMBG*L%jDg%qlbXBX|G}~2$OY-|n(yZ&49a-cqFf($oLnD$f5>t?U2+?I) zbTq?jY<6GelNPG$+(egnCt1VTV|9xrqNu07JM_A1+zb9SDBdH*$L!)?!%~?@Wsu5- zp-r0ND@-OKxrGW%*vBgbv2!~`FG0`C+QCpvh{)YZTdQvb@f6Y!%KSpkX+<@E$ ZlHgC7`~=6*54>u_Y>T^>KXt5 diff --git a/litellm/integrations/__pycache__/supabase.cpython-311.pyc b/litellm/integrations/__pycache__/supabase.cpython-311.pyc index c3f60037e56eaf011bcc069d2cb85dd607ef9077..3a77f3a0393e6b1298cd9a1aa315cf827b1849bf 100644 GIT binary patch delta 1086 zcmcJMPiWIn9LHbUrma~TU$VrSHtkw*Tf5pg|6x0wP~YD*GH?)2l1&vWlZE)Wr6e!x4fQ&w1og`5Bvq6e~zafJFlhq z)lD3!oFX{s9?B7%r&_$SNVLJ~exl`CUO~+>FA5EPlaLzSiRB3%`4w|hXNY!|=SF$X zcI7M!BE@tvJ?m5dbY)l;XV*^4vRHEX=D!FCl@ZfV?o+93KDk8k{5#Rb^ZdR&*M5Mm zLke^qQrI>Wq#6y}U&s9~XR9~YN8T*eaFO6*E8q<&6lW8R)msn%Ydv)R#)r;=vWf!Oyp_HmYDOH0~fFLqVqNf_VUDxgE0y%o2 zrjHYSylF&NuDMpWX4u5An})S={rU8Z=~e)#*0OJTmJcQ&&2;W-xx4?Zf{C=WtERrQ z!_E(_GIfKW=tV%eGd3bA*B55K9#LYbgUEnb6d3AaQ8*{64E+h`bMKPCoI83(*l?cd zg;EfOqcoAG$utZ24)_`3kh`V9C196~atZjGv}hvLUNgdz07(9pCYJ!Y)NT8?1+*81 z`6<|w!X;pr?Bx>BmV@o2#w7q`j3$O?G6T~)mAa+MlK>f&c=tOy4RZ_VRN_anr##4$ kfGeH}LGdueS(ez0XHgK~(D!C*+i&gus&pniJOTgtCjkdY2><{9 delta 967 zcmb`FK}-`t6oz*uRcyv}1Lx|p(`SQPc^CmCvz07yv$587h ztJT8L^(K6fee3+(`qgCiQg`{Id|-NI+s)!|Y~O+Jh?+Npy_vnLIof0%G#WC5oYsHSi-XvonfkM zj5}tS2W*@>VwlTu4)`F2Se^2v7A1A&xygB9FT(St&D%x;4VGcH7@g~FwiDj9Jr3?Q zn4B_{l3)3A(0B>+67W(Zd9R+#){?Zr0u&`${WJC!M@@`N8r4s*6GktF(f9;y8-^0Z z6^0Q}bBWtyjc(tEg4P|Xdq!)XbEBFkiak;Jn>~>NBtk50C=xYW2HP@V%MjFu(Qq9_ zYAB*O@W@>aO=2`zN7FSl4dQ}^iWn8^s9Hl+4Xt9d`nqMOX(wlFVt2~rw#dwxC)PWd zwgA}uKXs>UX@8gU7*ialvh{a2+xpf@Kp(r2>k%xRI2+a>me zyyzHWiTvIX-83`4fEs|uCFm$q$b|vb1-V6Vlqe(+RG-4aGPtXhQ=UJlN>HePYneg@ z1a%(rOW>?fNN{*R4&^mK5`}nr6hF*n!J4CxUY{btqdc?~D4V3GUzvl4b6}sRkb$7O z4#ib)u2G2Br>sM^1hz7T&_W|dp`B&QDevN-8i% diff --git a/litellm/main.py b/litellm/main.py index a8f7fbd5e0..d41f0e91fa 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -94,6 +94,9 @@ def completion( model_response = ModelResponse() if azure: # this flag is deprecated, remove once notebooks are also updated. custom_llm_provider = "azure" + elif model.split("/", 1)[0] in litellm.provider_list: # allow custom provider to be passed in via the model name "azure/chatgpt-test" + custom_llm_provider = model.split("/", 1)[0] + model = model.split("/", 1)[1] args = locals() # check if user passed in any of the OpenAI optional params optional_params = get_optional_params( diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index e168c23244..fc99544593 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -25,6 +25,18 @@ def logger_fn(user_model_dict): print(f"user_model_dict: {user_model_dict}") +def test_completion_custom_provider_model_name(): + try: + response = completion( + model="together_ai/togethercomputer/llama-2-70b-chat", messages=messages, logger_fn=logger_fn + ) + # Add any assertions here to check the response + print(response) + except Exception as e: + pytest.fail(f"Error occurred: {e}") + +test_completion_custom_provider_model_name() + def test_completion_claude(): try: response = completion( From 57efebb1c4265a56f28147aa316b040ae3b98002 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 16:46:15 -0700 Subject: [PATCH 06/13] fix replicate edge case for custom provider in model name --- litellm/main.py | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/main.py b/litellm/main.py index d41f0e91fa..d3e53f4c15 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -97,6 +97,8 @@ def completion( elif model.split("/", 1)[0] in litellm.provider_list: # allow custom provider to be passed in via the model name "azure/chatgpt-test" custom_llm_provider = model.split("/", 1)[0] model = model.split("/", 1)[1] + if "replicate" == custom_llm_provider and "/" not in model: # handle the "replicate/llama2..." edge-case + model = custom_llm_provider + "/" + model args = locals() # check if user passed in any of the OpenAI optional params optional_params = get_optional_params( diff --git a/pyproject.toml b/pyproject.toml index 269bb42ed8..31f424f006 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "0.1.428" +version = "0.1.429" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT License" From dbda81c6ec94564c34c58e7f26a330383f423ad6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 17:05:15 -0700 Subject: [PATCH 07/13] fix docs --- docs/my-website/docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/index.md b/docs/my-website/docs/index.md index 57d23215d4..b0b8b4c3e6 100644 --- a/docs/my-website/docs/index.md +++ b/docs/my-website/docs/index.md @@ -1,4 +1,4 @@ -# *🚅 litellm* +# litellm [![PyPI Version](https://img.shields.io/pypi/v/litellm.svg)](https://pypi.org/project/litellm/) [![PyPI Version](https://img.shields.io/badge/stable%20version-v0.1.345-blue?color=green&link=https://pypi.org/project/litellm/0.1.1/)](https://pypi.org/project/litellm/0.1.1/) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/BerriAI/litellm/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/BerriAI/litellm/tree/main) From 7c33d1157a31dd00d0c6ffeeddc14d2210cc5d6c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 20:03:31 -0700 Subject: [PATCH 08/13] making logging a class - adding input-callbacks --- .../observability/supabase_integration.md | 4 +- litellm/__init__.py | 4 +- litellm/__pycache__/__init__.cpython-311.pyc | Bin 5519 -> 5586 bytes litellm/__pycache__/main.cpython-311.pyc | Bin 26630 -> 28094 bytes litellm/__pycache__/utils.cpython-311.pyc | Bin 49909 -> 52578 bytes .../__pycache__/supabase.cpython-311.pyc | Bin 5733 -> 6648 bytes litellm/integrations/supabase.py | 33 ++- litellm/llms/anthropic.py | 16 +- litellm/llms/huggingface_restapi.py | 28 +- litellm/main.py | 267 +++++------------- litellm/tests/test_supabase_integration.py | 39 +-- litellm/utils.py | 161 +++++++---- 12 files changed, 237 insertions(+), 315 deletions(-) diff --git a/docs/my-website/docs/observability/supabase_integration.md b/docs/my-website/docs/observability/supabase_integration.md index 6ae4f65dae..d9fbc2b5ac 100644 --- a/docs/my-website/docs/observability/supabase_integration.md +++ b/docs/my-website/docs/observability/supabase_integration.md @@ -22,11 +22,13 @@ create table messages json null default '{}'::json, response json null default '{}'::json, end_user text null default ''::text, + status text null default ''::text, error json null default '{}'::json, response_time real null default '0'::real, total_cost real null, additional_details json null default '{}'::json, - constraint request_logs_pkey primary key (id) + litellm_call_id text unique, + primary key (id) ) tablespace pg_default; ``` diff --git a/litellm/__init__.py b/litellm/__init__.py index 688cd084fd..7cbb0e9963 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1,6 +1,6 @@ import threading from typing import Callable, List, Optional - +input_callback: List[str] = [] success_callback: List[str] = [] failure_callback: List[str] = [] set_verbose = False @@ -216,7 +216,6 @@ from .timeout import timeout from .testing import * from .utils import ( client, - logging, exception_type, get_optional_params, modify_integration, @@ -224,6 +223,7 @@ from .utils import ( cost_per_token, completion_cost, get_litellm_params, + Logging ) from .main import * # type: ignore from .integrations import * diff --git a/litellm/__pycache__/__init__.cpython-311.pyc b/litellm/__pycache__/__init__.cpython-311.pyc index 480251bd565f14a8e15d43b411f5d9c807062199..c998bff4ac6cd91de57b2024a44a884cb573a619 100644 GIT binary patch delta 985 zcmaiy%TE(g6o>D0fKGvFS@3RYftEfgls*8Zf;C^;bf{X5j zT$ikjK@BTy;@{v>T^Q1ZiA&w!(&)x>hiZ&5@y?v@cV2Vm-f17j-pABus@h0!eJy>0 zkB4fP^!hNg63r9pU@~jy5+!RfJJe^GkY`*Gl@J7y4>mKlm*#faA^w4&VmPw=e)f zyu@ZgM=t8Ikp%+bGJyyqcYA8*CC4%$Curx3dToSxk1;s;l_Jy!(K4aEfamW6iT0!U z(E?}#Xu&*z*b|9{U@$MyFh+5VBB(>CQ5eQ-45I`_gD?Wgyp6^o1!>eF%x5q^j5>;% zU?CRX!&kG2#i+%Y#lf5vFFV1nDKNxmE4RcMn_VH_`@1*fo=g0KT|rTey%tc z8~Q||w{p{wlljx;2ac+JX*-`UY~^{cx*;v{vN{tn6l|(C6`8qupBd}hMOJl~Ok+AT zja{Q%6zdJ)^D}kKX0&m?Gt*=pC?TdND;rKKwN8pYyUB_sPOsLN%z{?rA6#LZvB-b8 zg4rvg_o~1(fhB?K0z(47ztndnt8YU|W0S@`EJ$d@Nq)N}({}3dK6D<`9V|a~yd_?| Q-x2R=g|~dGs}oQE1XtDU1^@s6 delta 963 zcmah{OHUI~6u#4UfhkS2LV?nj0`0U6eS;$SD9^ZIp@!sc8suDSb*3e^0}&E~3pOSu z24>631!_o0NPmHezrclyCPriY5yqA0UeLHO@lMWnzVn@P&wb6u%&Uz0Mpe5Aj>`8h z@cuvzD@O;(wKTY?A3U@RytEsA4=7Y+F6Ie~QaP9xe6z^T-bR%@Hx1*A0k{kyxB`7g z&i}A}2*ZG^`xhI;?2yd<&4wT%wPA=t?1IH%M6f+bV7v2%qqu?dEsVk#equA@N4<2= zL6!+5_X(sJc|3G+3Ucfudza>J4LE9%ZidPUri=&O8CD2CE#%cui z(ytXd4w;%llNe55ltQng*I*LWG)7sBGLVCO%|R!i07djV>Lt`C(U;L@**Ht?;?gW7 zQInXKn2|UsaYCXlF}q8$gwPzen8Fr$n8pPQa22i@MYBkY3PC<&l9mwamrW2kD5PH)wE#MwGjH<}HLJ+-*hm5$1`S;MLVv)1cItEFId zOt$Oz>VTR0!yTTtPkrT8(Nptr@oqdKU>IRHP}{Whl3px4(Q{MzBJUTO0RiGi<%6Oo zC>GRVeY@V+sO!00QFHUSUGT4XVxlJ6e(qm%`n*Q7R;xE^_J^Kl_8oOYS+-Briq`3F zF{`@G_=9GPb^I1Hn8_@@!$UZ?H52Vas4Q_$DUA-L%s7%V1gg+Wr*$ I;c|(}Uo4{NCIA2c diff --git a/litellm/__pycache__/main.cpython-311.pyc b/litellm/__pycache__/main.cpython-311.pyc index da220e0b9ba2ed202b74d8fc9b0f538fcf80368f..e35b110bc194545e0ae20ce38d9c983f87b34e66 100644 GIT binary patch literal 28094 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 delta 10848 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 Avj6}9 diff --git a/litellm/integrations/__pycache__/supabase.cpython-311.pyc b/litellm/integrations/__pycache__/supabase.cpython-311.pyc index 3a77f3a0393e6b1298cd9a1aa315cf827b1849bf..43b7b234c0f2b2cff24a3475ade34adf23bc62f4 100644 GIT binary patch delta 1468 zcmZ{jO>7%Q7=~we?e(s`fBp$cO%m2_jkCq7!A41wwzP%@(F!U^nwC<)+GLjCYGYfw z>j2hT6vQDtAVr!(k!VFvZ4g{gFNhl;q#j)Z!|Fk>2=)RBoC73!n>h#GqO{0-f+-!^7x4kU#V7{itZRt3tEq2;SRTE*tWiE*d5NO!g9kltPX2Cy3?>bvRQEq z(_AaB=%!h&z$OcIN1szQ7M)j$j|b~krRr$BmBd26nl(+L8G>0NF8Ueeb;DY&*#`Pm zNxjSCd9~lHhe?*~AFyoJR?Eh^QFWSQi;cRztlP$c|EwUKZf{{+1YJ7LM?GQB#tWXZ zbjxTfOSs=8iHjZy*eh&jN zwweFv$<6#`{55tns?O>DzmJKPcF@s`p36WG(21a1UL z;&jXnK@Rd)xR!{`$HA~Gq6Kl4646)U%xh9hCYKK*zC;reiO4?^SeAxCT=##{*sAws zx(u!(LTPxNk$#zHAz*Q&ZRUy82G7VX6MrlNnuIt3ks&&*hU?fcdR3LfId{l&MHHVn%yy%ECe{TQZ6(!BATf5~ zSuZ~O<*T={+ea4Kanik1dh_HDnSwW=ZDqG73SK(rsZ)ay4j&-(dN>7QT01(`>*i26 zKAi~9OW<3nbQu49C#B{K%qe^;r{kFs@0qq;zjC3G?hpv&k&I!0r^}1#T~}?)-a8ytn)Wv;v4GdX_~?!!+F=A-+92QulhGD C?qLT2 delta 703 zcmexi{8Wc;IWI340}wbpxu0@PW+UHsCPtOXY|N@G!3>&gn@yP|I2AcS@=(AIq?v*E z^C>0#u3;%- zD5|UBu3<@Gk%St?z)-_A*+9UIFNI?cTP+XBs1(i`p2-yg>5M#+9|)K`^49XDFa$HC zaDlbwU}~>n0vgDLWFT`2H+Hi`VP*l%Q)XZUJ3NJFvWAc=Bk$xqp?oGyw#hGq6clc8 zpO&?7}^Ai@$v zSb+#z5MeWUwXo&nSG;<0oFzqx$*D<+$=SEK((;RP6HDS#E0Q(&i=;s6zh}{YxqC#va1SxU;q*q1Qb6AOwJL_n*3T+28(zcFcgI}F9>S<_@FYmMa)cu z!^Mu_Fpr}ikUC None: + def __init__(self, encoding, logging_obj, api_key=None) -> None: self.encoding = encoding + self.logging_obj = logging_obj self.validate_environment(api_key=api_key) def validate_environment( @@ -74,18 +74,10 @@ class HuggingfaceRestAPILLM: optional_params["max_new_tokens"] = value data = { "inputs": prompt, - # "parameters": optional_params + "parameters": optional_params } ## LOGGING - logging( - model=model, - input=prompt, - additional_args={ - "litellm_params": litellm_params, - "optional_params": optional_params, - }, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data}) ## COMPLETION CALL response = requests.post( completion_url, headers=self.headers, data=json.dumps(data) @@ -94,17 +86,7 @@ class HuggingfaceRestAPILLM: return response.iter_lines() else: ## LOGGING - logging( - model=model, - input=prompt, - additional_args={ - "litellm_params": litellm_params, - "optional_params": optional_params, - "original_response": response.text, - }, - logger_fn=logger_fn, - ) - print_verbose(f"raw model_response: {response.text}") + logging.post_call(input=prompt, api_key=self.api_key, original_response=response.text, additional_args={"complete_input_dict": data}) ## RESPONSE OBJECT completion_response = response.json() print_verbose(f"response: {completion_response}") diff --git a/litellm/main.py b/litellm/main.py index d3e53f4c15..a01bea4ad9 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -6,11 +6,11 @@ from copy import deepcopy import litellm from litellm import ( # type: ignore client, - logging, exception_type, timeout, get_optional_params, get_litellm_params, + Logging ) from litellm.utils import ( get_secret, @@ -85,6 +85,7 @@ def completion( azure=False, custom_llm_provider=None, custom_api_base=None, + litellm_call_id=None, # model specific optional params # used by text-bison only top_k=40, @@ -129,8 +130,9 @@ def completion( verbose=verbose, custom_llm_provider=custom_llm_provider, custom_api_base=custom_api_base, + litellm_call_id=litellm_call_id ) - + logging = Logging(model=model, messages=messages, optional_params=optional_params, litellm_params=litellm_params) if custom_llm_provider == "azure": # azure configs openai.api_type = "azure" @@ -144,16 +146,14 @@ def completion( if litellm.api_version is not None else get_secret("AZURE_API_VERSION") ) + if not api_key and litellm.azure_key: + api_key = litellm.azure_key + elif not api_key and get_secret("AZURE_API_KEY"): + api_key = get_secret("AZURE_API_KEY") # set key - openai.api_key = api_key or litellm.azure_key or get_secret("AZURE_API_KEY") + openai.api_key = api_key ## LOGGING - logging( - model=model, - input=messages, - additional_args=optional_params, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=messages, api_key=openai.api_key, additional_args={"headers": litellm.headers, "api_version": openai.api_version, "api_base": openai.api_base}) ## COMPLETION CALL if litellm.headers: response = openai.ChatCompletion.create( @@ -166,6 +166,8 @@ def completion( response = openai.ChatCompletion.create( model=model, messages=messages, **optional_params ) + ## LOGGING + logging.post_call(input=messages, api_key=openai.api_key, original_response=response, additional_args={"headers": litellm.headers, "api_version": openai.api_version, "api_base": openai.api_base}) elif ( model in litellm.open_ai_chat_completion_models or custom_llm_provider == "custom_openai" @@ -182,18 +184,15 @@ def completion( if litellm.organization: openai.organization = litellm.organization # set API KEY - openai.api_key = ( - api_key or litellm.openai_key or get_secret("OPENAI_API_KEY") - ) + if not api_key and litellm.openai_key: + api_key = litellm.openai_key + elif not api_key and get_secret("AZURE_API_KEY"): + api_key = get_secret("OPENAI_API_KEY") + + openai.api_key = api_key ## LOGGING - logging( - model=model, - input=messages, - additional_args=args, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=messages, api_key=api_key, additional_args={"headers": litellm.headers, "api_base": api_base}) ## COMPLETION CALL if litellm.headers: response = openai.ChatCompletion.create( @@ -206,6 +205,8 @@ def completion( response = openai.ChatCompletion.create( model=model, messages=messages, **optional_params ) + ## LOGGING + logging.post_call(input=messages, api_key=api_key, original_response=response, additional_args={"headers": litellm.headers}) elif model in litellm.open_ai_text_completion_models: openai.api_type = "openai" openai.api_base = ( @@ -214,20 +215,19 @@ def completion( else "https://api.openai.com/v1" ) openai.api_version = None - openai.api_key = ( - api_key or litellm.openai_key or get_secret("OPENAI_API_KEY") - ) + # set API KEY + if not api_key and litellm.openai_key: + api_key = litellm.openai_key + elif not api_key and get_secret("AZURE_API_KEY"): + api_key = get_secret("OPENAI_API_KEY") + + openai.api_key = api_key + if litellm.organization: openai.organization = litellm.organization prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - additional_args=optional_params, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=api_key, additional_args={"openai_organization": litellm.organization, "headers": litellm.headers, "api_base": openai.api_base, "api_type": openai.api_type}) ## COMPLETION CALL if litellm.headers: response = openai.Completion.create( @@ -237,19 +237,10 @@ def completion( ) else: response = openai.Completion.create(model=model, prompt=prompt) - completion_response = response["choices"][0]["text"] ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=api_key, original_response=response, additional_args={"openai_organization": litellm.organization, "headers": litellm.headers, "api_base": openai.api_base, "api_type": openai.api_type}) ## RESPONSE OBJECT + completion_response = response["choices"][0]["text"] model_response["choices"][0]["message"]["content"] = completion_response model_response["created"] = response["created"] model_response["model"] = model @@ -278,13 +269,7 @@ def completion( input["max_length"] = max_tokens # for t5 models input["max_new_tokens"] = max_tokens # for llama2 models ## LOGGING - logging( - model=model, - input=input, - custom_llm_provider=custom_llm_provider, - additional_args={"max_tokens": max_tokens}, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=replicate_key, additional_args={"complete_input_dict": input, "max_tokens": max_tokens}) ## COMPLETION CALL output = replicate.run(model, input=input) if "stream" in optional_params and optional_params["stream"] == True: @@ -297,16 +282,8 @@ def completion( response += item completion_response = response ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=replicate_key, original_response=completion_response, additional_args={"complete_input_dict": input, "max_tokens": max_tokens}) + ## USAGE prompt_tokens = len(encoding.encode(prompt)) completion_tokens = len(encoding.encode(completion_response)) ## RESPONSE OBJECT @@ -327,6 +304,7 @@ def completion( encoding=encoding, default_max_tokens_to_sample=litellm.max_tokens, api_key=anthropic_key, + logging_obj = logging # model call logging done inside the class as we make need to modify I/O to fit anthropic's requirements ) model_response = anthropic_client.completion( model=model, @@ -362,13 +340,7 @@ def completion( "OR_API_KEY" ) ## LOGGING - logging( - model=model, - input=messages, - additional_args=optional_params, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=messages, api_key=openai.api_key) ## COMPLETION CALL if litellm.headers: response = openai.ChatCompletion.create( @@ -395,6 +367,8 @@ def completion( }, **optional_params, ) + ## LOGGING + logging.post_call(input=messages, api_key=openai.api_key, original_response=response) elif model in litellm.cohere_models: # import cohere/if it fails then pip install cohere install_and_import("cohere") @@ -409,31 +383,17 @@ def completion( co = cohere.Client(cohere_key) prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=cohere_key) ## COMPLETION CALL response = co.generate(model=model, prompt=prompt, **optional_params) if "stream" in optional_params and optional_params["stream"] == True: # don't try to access stream object, response = CustomStreamWrapper(response, model) return response - - completion_response = response[0].text ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=cohere_key, original_response=response) + ## USAGE + completion_response = response[0].text prompt_tokens = len(encoding.encode(prompt)) completion_tokens = len(encoding.encode(completion_response)) ## RESPONSE OBJECT @@ -457,7 +417,7 @@ def completion( or os.environ.get("HUGGINGFACE_API_KEY") ) huggingface_client = HuggingfaceRestAPILLM( - encoding=encoding, api_key=huggingface_key + encoding=encoding, api_key=huggingface_key, logging_obj=logging ) model_response = huggingface_client.completion( model=model, @@ -492,12 +452,7 @@ def completion( ) # TODO: Add chat support for together AI ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=TOGETHER_AI_TOKEN) if stream == True: return together_ai_completion_streaming( { @@ -519,17 +474,7 @@ def completion( headers=headers, ) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": res.text, - }, - logger_fn=logger_fn, - ) - + logging.post_call(input=prompt, api_key=TOGETHER_AI_TOKEN, original_response=res.text) # make this safe for reading, if output does not exist raise an error json_response = res.json() if "output" not in json_response: @@ -562,16 +507,7 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "litellm_params": litellm_params, - "optional_params": optional_params, - }, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=None) chat_model = ChatModel.from_pretrained(model) @@ -579,16 +515,7 @@ def completion( completion_response = chat.send_message(prompt, **optional_params) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=None, original_response=completion_response) ## RESPONSE OBJECT model_response["choices"][0]["message"]["content"] = completion_response @@ -607,27 +534,13 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=None) + vertex_model = TextGenerationModel.from_pretrained(model) completion_response = vertex_model.predict(prompt, **optional_params) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) - + logging.post_call(input=prompt, api_key=None, original_response=completion_response) ## RESPONSE OBJECT model_response["choices"][0]["message"]["content"] = completion_response model_response["created"] = time.time() @@ -641,12 +554,7 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=ai21.api_key) ai21_response = ai21.Completion.execute( model=model, @@ -655,16 +563,7 @@ def completion( completion_response = ai21_response["completions"][0]["data"]["text"] ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=ai21.api_key, original_response=completion_response) ## RESPONSE OBJECT model_response["choices"][0]["message"]["content"] = completion_response @@ -678,7 +577,8 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging(model=model, input=prompt, azure=azure, logger_fn=logger_fn) + logging.pre_call(input=prompt, api_key=None, additional_args={"endpoint": endpoint}) + generator = get_ollama_response_stream(endpoint, model, prompt) # assume all responses are streamed return generator @@ -693,12 +593,7 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=base_ten_key) base_ten__model = baseten.deployed_model_version_id(model) @@ -708,16 +603,8 @@ def completion( if type(completion_response) == dict: completion_response = completion_response["generated_text"] - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": completion_response, - }, - logger_fn=logger_fn, - ) + ## LOGGING + logging.post_call(input=prompt, api_key=base_ten_key, original_response=completion_response) ## RESPONSE OBJECT model_response["choices"][0]["message"]["content"] = completion_response @@ -734,26 +621,14 @@ def completion( prompt = " ".join([message["content"] for message in messages]) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) + logging.pre_call(input=prompt, api_key=None, additional_args={"url": url, "max_new_tokens": 100}) + response = requests.post( url, data={"inputs": prompt, "max_new_tokens": 100, "model": model} ) ## LOGGING - logging( - model=model, - input=prompt, - custom_llm_provider=custom_llm_provider, - additional_args={ - "max_tokens": max_tokens, - "original_response": response, - }, - logger_fn=logger_fn, - ) + logging.post_call(input=prompt, api_key=None, original_response=response.text, additional_args={"url": url, "max_new_tokens": 100}) + completion_response = response.json()["outputs"] # RESPONSE OBJECT @@ -762,13 +637,6 @@ def completion( model_response["model"] = model response = model_response else: - ## LOGGING - logging( - model=model, - input=messages, - custom_llm_provider=custom_llm_provider, - logger_fn=logger_fn, - ) args = locals() raise ValueError( f"Unable to map your input to a model. Check your input - {args}" @@ -776,14 +644,7 @@ def completion( return response except Exception as e: ## LOGGING - logging( - model=model, - input=messages, - custom_llm_provider=custom_llm_provider, - additional_args={"max_tokens": max_tokens}, - logger_fn=logger_fn, - exception=e, - ) + logging.post_call(input=messages, api_key=api_key, original_response=e) ## Map to OpenAI Exception raise exception_type( model=model, custom_llm_provider=custom_llm_provider, original_exception=e @@ -825,7 +686,7 @@ def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None): openai.api_version = get_secret("AZURE_API_VERSION") openai.api_key = get_secret("AZURE_API_KEY") ## LOGGING - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + logging.pre_call(model=model, input=input, azure=azure, logger_fn=logger_fn) ## EMBEDDING CALL response = openai.Embedding.create(input=input, engine=model) print_verbose(f"response_value: {str(response)[:50]}") diff --git a/litellm/tests/test_supabase_integration.py b/litellm/tests/test_supabase_integration.py index 882d0bbc69..2326bcfdf8 100644 --- a/litellm/tests/test_supabase_integration.py +++ b/litellm/tests/test_supabase_integration.py @@ -1,27 +1,28 @@ -# #### What this tests #### -# # This tests if logging to the helicone integration actually works -# # pytest mistakes intentional bad calls as failed tests -> [TODO] fix this -# import sys, os -# import traceback -# import pytest +#### What this tests #### +# This tests if logging to the helicone integration actually works +# pytest mistakes intentional bad calls as failed tests -> [TODO] fix this +import sys, os +import traceback +import pytest -# sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path -# import litellm -# from litellm import embedding, completion +sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path +import litellm +from litellm import embedding, completion -# litellm.success_callback = ["supabase"] -# litellm.failure_callback = ["supabase"] +litellm.input_callback = ["supabase"] +litellm.success_callback = ["supabase"] +litellm.failure_callback = ["supabase"] -# litellm.modify_integration("supabase",{"table_name": "litellm_logs"}) +litellm.modify_integration("supabase",{"table_name": "test_table"}) -# litellm.set_verbose = True +litellm.set_verbose = True -# user_message = "Hello, how are you?" -# messages = [{ "content": user_message,"role": "user"}] +user_message = "Hello, how are you?" +messages = [{ "content": user_message,"role": "user"}] -# #openai call -# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]) +#openai call +response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]) -# #bad request call -# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}]) +#bad request call +response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}]) diff --git a/litellm/utils.py b/litellm/utils.py index 5346ce62a1..d340c2df36 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -135,48 +135,105 @@ def install_and_import(package: str): ####### LOGGING ################### # Logging function -> log the exact model details + what's being sent | Non-Blocking -def logging( - model=None, - input=None, - custom_llm_provider=None, - azure=False, +class Logging: + def __init__(self, model, messages, optional_params, litellm_params): + self.model = model + self.messages = messages + self.optional_params = optional_params + self.litellm_params = litellm_params + self.logger_fn = litellm_params["logger_fn"] + self.model_call_details = { + "model": model, + "messages": messages, + "optional_params": self.optional_params, + "litellm_params": self.litellm_params, + } + + def pre_call(self, input, api_key, additional_args={}): + try: + print(f"logging pre call for model: {self.model}") + self.model_call_details["input"] = input + self.model_call_details["api_key"] = api_key + self.model_call_details["additional_args"] = additional_args + + ## User Logging -> if you pass in a custom logging function + print_verbose( + f"Logging Details: logger_fn - {self.logger_fn} | callable(logger_fn) - {callable(self.logger_fn)}" + ) + if self.logger_fn and callable(self.logger_fn): + try: + self.logger_fn( + self.model_call_details + ) # Expectation: any logger function passed in by the user should accept a dict object + except Exception as e: + print_verbose( + f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}" + ) + + ## Input Integration Logging -> If you want to log the fact that an attempt to call the model was made + for callback in litellm.input_callback: + try: + if callback == "supabase": + print_verbose("reaches supabase for logging!") + model = self.model + messages = self.messages + print(f"litellm._thread_context: {litellm._thread_context}") + supabaseClient.input_log_event( + model=model, + messages=messages, + end_user=litellm._thread_context.user, + litellm_call_id=self.litellm_params["litellm_call_id"], + print_verbose=print_verbose, + ) + pass + except: + pass + except: + print_verbose( + f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}" + ) + pass + + def post_call(self, input, api_key, original_response, additional_args={}): + # Do something here + try: + self.model_call_details["input"] = input + self.model_call_details["api_key"] = api_key + self.model_call_details["original_response"] = original_response + self.model_call_details["additional_args"] = additional_args + + ## User Logging -> if you pass in a custom logging function + print_verbose( + f"Logging Details: logger_fn - {self.logger_fn} | callable(logger_fn) - {callable(self.logger_fn)}" + ) + if self.logger_fn and callable(self.logger_fn): + try: + self.logger_fn( + self.model_call_details + ) # Expectation: any logger function passed in by the user should accept a dict object + except Exception as e: + print_verbose( + f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}" + ) + except: + print_verbose( + f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}" + ) + pass + + # Add more methods as needed + + +def exception_logging( additional_args={}, logger_fn=None, exception=None, ): try: model_call_details = {} - if model: - model_call_details["model"] = model - if azure: - model_call_details["azure"] = azure - if custom_llm_provider: - model_call_details["custom_llm_provider"] = custom_llm_provider if exception: model_call_details["exception"] = exception - if input: - model_call_details["input"] = input - - if len(additional_args): - model_call_details["additional_args"] = additional_args - # log additional call details -> api key, etc. - if model: - if ( - azure == True - or model in litellm.open_ai_chat_completion_models - or model in litellm.open_ai_chat_completion_models - or model in litellm.open_ai_embedding_models - ): - model_call_details["api_type"] = openai.api_type - model_call_details["api_base"] = openai.api_base - model_call_details["api_version"] = openai.api_version - model_call_details["api_key"] = openai.api_key - elif "replicate" in model: - model_call_details["api_key"] = os.environ.get("REPLICATE_API_TOKEN") - elif model in litellm.anthropic_models: - model_call_details["api_key"] = os.environ.get("ANTHROPIC_API_KEY") - elif model in litellm.cohere_models: - model_call_details["api_key"] = os.environ.get("COHERE_API_KEY") + model_call_details["additional_args"] = additional_args ## User Logging -> if you pass in a custom logging function or want to use sentry breadcrumbs print_verbose( f"Logging Details: logger_fn - {logger_fn} | callable(logger_fn) - {callable(logger_fn)}" @@ -206,10 +263,10 @@ def client(original_function): try: global callback_list, add_breadcrumb, user_logger_fn if ( - len(litellm.success_callback) > 0 or len(litellm.failure_callback) > 0 + len(litellm.input_callback) > 0 or len(litellm.success_callback) > 0 or len(litellm.failure_callback) > 0 ) and len(callback_list) == 0: callback_list = list( - set(litellm.success_callback + litellm.failure_callback) + set(litellm.input_callback + litellm.success_callback + litellm.failure_callback) ) set_callbacks( callback_list=callback_list, @@ -299,13 +356,16 @@ def client(original_function): result = None try: function_setup(*args, **kwargs) - ## MODEL CALL + litellm_call_id = str(uuid.uuid4()) + kwargs["litellm_call_id"] = litellm_call_id + ## [OPTIONAL] CHECK CACHE start_time = datetime.datetime.now() if (litellm.caching or litellm.caching_with_models) and ( cached_result := check_cache(*args, **kwargs) ) is not None: result = cached_result else: + ## MODEL CALL result = original_function(*args, **kwargs) end_time = datetime.datetime.now() ## Add response to CACHE @@ -399,6 +459,7 @@ def get_litellm_params( together_ai=False, custom_llm_provider=None, custom_api_base=None, + litellm_call_id=None, ): litellm_params = { "return_async": return_async, @@ -408,6 +469,7 @@ def get_litellm_params( "verbose": verbose, "custom_llm_provider": custom_llm_provider, "custom_api_base": custom_api_base, + "litellm_call_id": litellm_call_id } return litellm_params @@ -567,7 +629,8 @@ def set_callbacks(callback_list): global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel, heliconeLogger, aispendLogger, berrispendLogger, supabaseClient try: for callback in callback_list: - if callback == "sentry" or "SENTRY_API_URL" in os.environ: + print(f"callback: {callback}") + if callback == "sentry": try: import sentry_sdk except ImportError: @@ -623,6 +686,7 @@ def set_callbacks(callback_list): elif callback == "berrispend": berrispendLogger = BerriSpendLogger() elif callback == "supabase": + print(f"instantiating supabase") supabaseClient = Supabase() except Exception as e: raise e @@ -743,7 +807,6 @@ def handle_failure(exception, traceback_exception, start_time, end_time, args, k "completion_tokens": 0, }, } - print(f"litellm._thread_context: {litellm._thread_context}") supabaseClient.log_event( model=model, messages=messages, @@ -751,9 +814,9 @@ def handle_failure(exception, traceback_exception, start_time, end_time, args, k response_obj=result, start_time=start_time, end_time=end_time, + litellm_call_id=kwargs["litellm_call_id"], print_verbose=print_verbose, ) - except: print_verbose( f"Error Occurred while logging failure: {traceback.format_exc()}" @@ -769,7 +832,7 @@ def handle_failure(exception, traceback_exception, start_time, end_time, args, k pass except Exception as e: ## LOGGING - logging(logger_fn=user_logger_fn, exception=e) + exception_logging(logger_fn=user_logger_fn, exception=e) pass @@ -849,11 +912,12 @@ def handle_success(args, kwargs, result, start_time, end_time): response_obj=result, start_time=start_time, end_time=end_time, + litellm_call_id=kwargs["litellm_call_id"], print_verbose=print_verbose, ) except Exception as e: ## LOGGING - logging(logger_fn=user_logger_fn, exception=e) + exception_logging(logger_fn=user_logger_fn, exception=e) print_verbose( f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}" ) @@ -864,7 +928,7 @@ def handle_success(args, kwargs, result, start_time, end_time): pass except Exception as e: ## LOGGING - logging(logger_fn=user_logger_fn, exception=e) + exception_logging(logger_fn=user_logger_fn, exception=e) print_verbose( f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}" ) @@ -912,15 +976,6 @@ def exception_type(model, original_exception, custom_llm_provider): exception_type = type(original_exception).__name__ else: exception_type = "" - logging( - model=model, - additional_args={ - "error_str": error_str, - "exception_type": exception_type, - "original_exception": original_exception, - }, - logger_fn=user_logger_fn, - ) if "claude" in model: # one of the anthropics if hasattr(original_exception, "status_code"): print_verbose(f"status_code: {original_exception.status_code}") @@ -1030,7 +1085,7 @@ def exception_type(model, original_exception, custom_llm_provider): raise original_exception except Exception as e: ## LOGGING - logging( + exception_logging( logger_fn=user_logger_fn, additional_args={ "exception_mapping_worked": exception_mapping_worked, From 2f8461c16048425ac05b9ade86de39f4d4e6ba6a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 20:16:54 -0700 Subject: [PATCH 09/13] linting bug fix --- litellm/llms/huggingface_restapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/huggingface_restapi.py b/litellm/llms/huggingface_restapi.py index 997c322bd7..624fb4f055 100644 --- a/litellm/llms/huggingface_restapi.py +++ b/litellm/llms/huggingface_restapi.py @@ -77,7 +77,7 @@ class HuggingfaceRestAPILLM: "parameters": optional_params } ## LOGGING - logging.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data}) + self.logging_obj.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data}) ## COMPLETION CALL response = requests.post( completion_url, headers=self.headers, data=json.dumps(data) @@ -86,7 +86,7 @@ class HuggingfaceRestAPILLM: return response.iter_lines() else: ## LOGGING - logging.post_call(input=prompt, api_key=self.api_key, original_response=response.text, additional_args={"complete_input_dict": data}) + self.logging_obj.post_call(input=prompt, api_key=self.api_key, original_response=response.text, additional_args={"complete_input_dict": data}) ## RESPONSE OBJECT completion_response = response.json() print_verbose(f"response: {completion_response}") From 0d168ded4006add46a40c76679c8871efd3d89b3 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 20:20:02 -0700 Subject: [PATCH 10/13] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 31f424f006..f0c4a95262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "0.1.429" +version = "0.1.430" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT License" From 26b35e5bb6e0f5afdf1893ecf129d45faaa7b1c5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 20:30:00 -0700 Subject: [PATCH 11/13] fixing linting issues --- litellm/llms/anthropic.py | 9 --------- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/litellm/llms/anthropic.py b/litellm/llms/anthropic.py index 8008531e9d..fecc655f2f 100644 --- a/litellm/llms/anthropic.py +++ b/litellm/llms/anthropic.py @@ -85,15 +85,6 @@ class AnthropicLLM: ## LOGGING self.logging_obj.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data}) - logging( - model=model, - input=prompt, - additional_args={ - "litellm_params": litellm_params, - "optional_params": optional_params, - }, - logger_fn=logger_fn, - ) ## COMPLETION CALL response = requests.post( self.completion_url, headers=self.headers, data=json.dumps(data) diff --git a/pyproject.toml b/pyproject.toml index f0c4a95262..216a058811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "0.1.430" +version = "0.1.431" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT License" From fa26205dabbbb9010d88da152db6c42743f33b21 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 20:57:38 -0700 Subject: [PATCH 12/13] test issues --- litellm/tests/test_supabase_integration.py | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/litellm/tests/test_supabase_integration.py b/litellm/tests/test_supabase_integration.py index 2326bcfdf8..3fd4b5247f 100644 --- a/litellm/tests/test_supabase_integration.py +++ b/litellm/tests/test_supabase_integration.py @@ -1,28 +1,28 @@ -#### What this tests #### -# This tests if logging to the helicone integration actually works -# pytest mistakes intentional bad calls as failed tests -> [TODO] fix this -import sys, os -import traceback -import pytest +# #### What this tests #### +# # This tests if logging to the helicone integration actually works +# # pytest mistakes intentional bad calls as failed tests -> [TODO] fix this +# import sys, os +# import traceback +# import pytest -sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path -import litellm -from litellm import embedding, completion +# sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path +# import litellm +# from litellm import embedding, completion -litellm.input_callback = ["supabase"] -litellm.success_callback = ["supabase"] -litellm.failure_callback = ["supabase"] +# litellm.input_callback = ["supabase"] +# litellm.success_callback = ["supabase"] +# litellm.failure_callback = ["supabase"] -litellm.modify_integration("supabase",{"table_name": "test_table"}) +# litellm.modify_integration("supabase",{"table_name": "test_table"}) -litellm.set_verbose = True +# litellm.set_verbose = True -user_message = "Hello, how are you?" -messages = [{ "content": user_message,"role": "user"}] +# user_message = "Hello, how are you?" +# messages = [{ "content": user_message,"role": "user"}] -#openai call -response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]) +# #openai call +# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]) -#bad request call -response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}]) +# #bad request call +# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}]) From 4e223d8ac4b9c10abe542d4bfa056ee123ae2373 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 19 Aug 2023 21:17:17 -0700 Subject: [PATCH 13/13] fixes to embedding logging --- litellm/main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index a01bea4ad9..ea2dd9f255 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -676,9 +676,10 @@ def batch_completion(*args, **kwargs): @timeout( # type: ignore 60 ) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout` -def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None): +def embedding(model, input=[], azure=False, force_timeout=60, litellm_call_id=None, logger_fn=None): try: response = None + logging = Logging(model=model, messages=input, optional_params={}, litellm_params={"azure": azure, "force_timeout": force_timeout, "logger_fn": logger_fn, "litellm_call_id": litellm_call_id}) if azure == True: # azure configs openai.api_type = "azure" @@ -686,7 +687,7 @@ def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None): openai.api_version = get_secret("AZURE_API_VERSION") openai.api_key = get_secret("AZURE_API_KEY") ## LOGGING - logging.pre_call(model=model, input=input, azure=azure, logger_fn=logger_fn) + logging.pre_call(input=input, api_key=openai.api_key, additional_args={"api_type": openai.api_type, "api_base": openai.api_base, "api_version": openai.api_version}) ## EMBEDDING CALL response = openai.Embedding.create(input=input, engine=model) print_verbose(f"response_value: {str(response)[:50]}") @@ -696,19 +697,16 @@ def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None): openai.api_version = None openai.api_key = get_secret("OPENAI_API_KEY") ## LOGGING - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + logging.pre_call(input=input, api_key=openai.api_key, additional_args={"api_type": openai.api_type, "api_base": openai.api_base, "api_version": openai.api_version}) ## EMBEDDING CALL response = openai.Embedding.create(input=input, model=model) print_verbose(f"response_value: {str(response)[:50]}") else: - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) args = locals() raise ValueError(f"No valid embedding model args passed in - {args}") return response except Exception as e: - # log the original exception - logging(model=model, input=input, azure=azure, logger_fn=logger_fn, exception=e) ## Map to OpenAI Exception raise exception_type(model=model, original_exception=e, custom_llm_provider="azure" if azure==True else None) raise e