From 5089dbfcfb3aeafa6eb2a35e5a93ccdf4378585e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Mar 2025 17:58:26 -0700 Subject: [PATCH 001/244] fix(invoke_handler.py): remove hard code --- litellm/llms/bedrock/chat/invoke_handler.py | 7 ------- litellm/proxy/_new_secret_config.yaml | 4 ++++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 84ac592c41..5b02fd3158 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -1274,13 +1274,6 @@ class AWSEventStreamDecoder: def converse_chunk_parser(self, chunk_data: dict) -> ModelResponseStream: try: verbose_logger.debug("\n\nRaw Chunk: {}\n\n".format(chunk_data)) - chunk_data["usage"] = { - "inputTokens": 3, - "outputTokens": 392, - "totalTokens": 2191, - "cacheReadInputTokens": 1796, - "cacheWriteInputTokens": 0, - } text = "" tool_use: Optional[ChatCompletionToolCallChunk] = None finish_reason = "" diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index fbe3471663..cf09749d81 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -9,6 +9,10 @@ model_list: litellm_params: model: gpt-4o-mini api_key: os.environ/OPENAI_API_KEY + - model_name: "bedrock-nova" + litellm_params: + model: us.amazon.nova-pro-v1:0 + litellm_settings: num_retries: 0 From f1a8b1984ad21705e3307b85021b63406b65d8dd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Mar 2025 21:34:18 -0700 Subject: [PATCH 002/244] fix mcp test deps --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 091369c147..b93a9d81e8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2039,6 +2039,8 @@ jobs: pip install "google-cloud-aiplatform==1.59.0" pip install "anthropic==0.49.0" pip install "langchain_mcp_adapters==0.0.5" + pip install "langchain_openai==0.2.1" + pip install "langgraph==0.3.18" # Run pytest and generate JUnit XML report - run: name: Build Docker image From 18dfb70023a475a23f1a2406714f8b73d78a21fe Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Mar 2025 21:35:29 -0700 Subject: [PATCH 003/244] mcp docs, exposing tools now live --- docs/my-website/docs/mcp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 9f3343e9cd..408a70d96d 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -272,7 +272,7 @@ async with stdio_client(server_params) as (read, write): -## Upcoming Features +## Advanced Usage :::info From da16cef4ba3c9ac76aeb4a998d57d731ca7ab4af Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Mar 2025 21:36:02 -0700 Subject: [PATCH 004/244] Expose MCP tools --- docs/my-website/docs/mcp.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 408a70d96d..744c23644e 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -274,13 +274,6 @@ async with stdio_client(server_params) as (read, write): ## Advanced Usage -:::info - -**This feature is not live as yet** this is a beta interface. Expect this to be live on litellm `v1.63.15` and above. - -::: - - ### Expose MCP tools on LiteLLM Proxy Server This allows you to define tools that can be called by any MCP compatible client. Define your mcp_tools with LiteLLM and all your clients can list and call available tools. From a619580bf8b91613d052f6ee95d240b22ae58b29 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Mon, 24 Mar 2025 22:42:38 -0700 Subject: [PATCH 005/244] Add vertexai topLogprobs support (#9518) * Added support for top_logprobs in vertex gemini models * Testing for top_logprobs feature in vertexai * Update litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py Co-authored-by: Tom Matthews * refactor(tests/): refactor testing to be in correct repo --------- Co-authored-by: Aditya Thaker Co-authored-by: Tom Matthews --- .../vertex_and_google_ai_studio_gemini.py | 3 ++ .../test_vertex_and_google_ai_studio.py | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 9ac1b1ffc4..73215b4048 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -207,6 +207,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "extra_headers", "seed", "logprobs", + "top_logprobs" # Added this to list of supported openAI params ] def map_tool_choice_values( @@ -365,6 +366,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): optional_params["presence_penalty"] = value if param == "logprobs": optional_params["responseLogprobs"] = value + if param == "top_logprobs": + optional_params["logprobs"] = value if (param == "tools" or param == "functions") and isinstance(value, list): optional_params["tools"] = self._map_function(value=value) optional_params["litellm_param_is_function_call"] = ( diff --git a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py new file mode 100644 index 0000000000..5e870b5af3 --- /dev/null +++ b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py @@ -0,0 +1,33 @@ +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, +) + + +def test_top_logprobs(): + non_default_params = { + "top_logprobs": 2, + "logprobs": True, + } + optional_params = {} + model = "gemini" + + v = VertexGeminiConfig().map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + assert v["responseLogprobs"] is non_default_params["logprobs"] + assert v["logprobs"] is non_default_params["top_logprobs"] From 12fdd2584135b715683fa40f82379060db4dfb64 Mon Sep 17 00:00:00 2001 From: superpoussin22 Date: Tue, 25 Mar 2025 06:44:00 +0100 Subject: [PATCH 006/244] Update model_prices_and_context_window.json (#9459) add mistral-small for vertex_ai --- model_prices_and_context_window.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index eefa739198..3124ae2697 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -5153,6 +5153,29 @@ "supports_function_calling": true, "supports_tool_choice": true }, + "vertex_ai/mistral-small-2503@001": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000001, + "output_cost_per_token": 0.000003, + "litellm_provider": "vertex_ai-mistral_models", + "supports_function_calling": true, + "mode": "chat", + "supports_tool_choice": true + }, + "vertex_ai/mistral-small-2503": { + "max_tokens": 128000, + "max_input_tokens": 128000, + "max_output_tokens": 128000, + "input_cost_per_token": 0.000001, + "output_cost_per_token": 0.000003, + "litellm_provider": "vertex_ai-mistral_models", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "supports_tool_choice": true + }, "vertex_ai/jamba-1.5-mini@001": { "max_tokens": 256000, "max_input_tokens": 256000, From 75994d0bf03347eb8b91035579027da1a3e95dc8 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Mar 2025 23:15:04 -0700 Subject: [PATCH 007/244] test: improve flaky test --- ...odel_prices_and_context_window_backup.json | 23 +++++++++++++++++++ tests/local_testing/test_caching.py | 3 +++ 2 files changed, 26 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index eefa739198..3124ae2697 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -5153,6 +5153,29 @@ "supports_function_calling": true, "supports_tool_choice": true }, + "vertex_ai/mistral-small-2503@001": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000001, + "output_cost_per_token": 0.000003, + "litellm_provider": "vertex_ai-mistral_models", + "supports_function_calling": true, + "mode": "chat", + "supports_tool_choice": true + }, + "vertex_ai/mistral-small-2503": { + "max_tokens": 128000, + "max_input_tokens": 128000, + "max_output_tokens": 128000, + "input_cost_per_token": 0.000001, + "output_cost_per_token": 0.000003, + "litellm_provider": "vertex_ai-mistral_models", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "supports_tool_choice": true + }, "vertex_ai/jamba-1.5-mini@001": { "max_tokens": 256000, "max_input_tokens": 256000, diff --git a/tests/local_testing/test_caching.py b/tests/local_testing/test_caching.py index e4815f2023..30feb09f9d 100644 --- a/tests/local_testing/test_caching.py +++ b/tests/local_testing/test_caching.py @@ -1734,6 +1734,7 @@ def test_redis_semantic_cache_completion(): # test_redis_cache_completion() +@pytest.mark.flaky(reruns=3) @pytest.mark.asyncio async def test_redis_semantic_cache_acompletion(): litellm.set_verbose = True @@ -1761,6 +1762,8 @@ async def test_redis_semantic_cache_acompletion(): ) print(f"response1: {response1}") + await asyncio.sleep(2) + response2 = await litellm.acompletion( model="gpt-3.5-turbo", messages=[ From 92883560f03c8044474165fa15b36f0564a1d570 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Mon, 24 Mar 2025 23:23:28 -0700 Subject: [PATCH 008/244] fix vertex ai multimodal embedding translation (#9471) * remove data:image/jpeg;base64, prefix from base64 image input vertex_ai's multimodal embeddings endpoint expects a raw base64 string without `data:image/jpeg;base64,` prefix. * Add Vertex Multimodal Embedding Test * fix(test_vertex.py): add e2e tests on multimodal embeddings * test: unit testing * test: remove sklearn dep * test: update test with fixed route * test: fix test --------- Co-authored-by: Jonarod Co-authored-by: Emerson Gomes --- .../embedding_handler.py | 10 +- litellm/main.py | 1 + .../test_embedding_handler.py | 43 ++++++++ tests/llm_translation/duck.png | Bin 0 -> 548293 bytes tests/llm_translation/guinea.png | Bin 0 -> 1528863 bytes tests/llm_translation/test_vertex.py | 96 ++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py create mode 100644 tests/llm_translation/duck.png create mode 100644 tests/llm_translation/guinea.png diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py index f63d1ce11e..cd3de6e526 100644 --- a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py @@ -226,7 +226,15 @@ class VertexMultimodalEmbedding(VertexLLM): else: return Instance(image=InstanceImage(gcsUri=input_element)) elif is_base64_encoded(s=input_element): - return Instance(image=InstanceImage(bytesBase64Encoded=input_element)) + return Instance( + image=InstanceImage( + bytesBase64Encoded=( + input_element.split(",")[1] + if "," in input_element + else input_element + ) + ) + ) else: return Instance(text=input_element) diff --git a/litellm/main.py b/litellm/main.py index 1826f2df78..3d4152d634 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -3715,6 +3715,7 @@ def embedding( # noqa: PLR0915 aembedding=aembedding, print_verbose=print_verbose, custom_llm_provider="vertex_ai", + client=client, ) else: response = vertex_embedding.embedding( diff --git a/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py b/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py new file mode 100644 index 0000000000..cfd1167836 --- /dev/null +++ b/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py @@ -0,0 +1,43 @@ +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.vertex_ai.multimodal_embeddings.embedding_handler import ( + VertexMultimodalEmbedding, +) +from litellm.types.llms.vertex_ai import Instance, InstanceImage + + +class TestVertexMultimodalEmbedding: + def setup_method(self): + self.embedding_handler = VertexMultimodalEmbedding() + + def test_process_openai_embedding_input(self): + input_data = [ + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", + ] + expected_output = [ + Instance( + image=InstanceImage(bytesBase64Encoded=input_data[0].split(",")[1]) + ), + Instance( + image=InstanceImage(bytesBase64Encoded=input_data[1].split(",")[1]) + ), + ] + assert ( + self.embedding_handler._process_input_element(input_data[0]) + == expected_output[0] + ) + assert ( + self.embedding_handler._process_input_element(input_data[1]) + == expected_output[1] + ) diff --git a/tests/llm_translation/duck.png b/tests/llm_translation/duck.png new file mode 100644 index 0000000000000000000000000000000000000000..541ce2fc7536a3dc5c517c1356f00f9346540b39 GIT binary patch literal 548293 zcmaHRWn5fK^7i2Ft^tA!?(Xhx!6CTAFt`MF9o*gBf&>fh?ykW@AOwfJ+1-2Z?*GeM zU%F49?&nlLRbBn7(;cOzB8!Ghj0^w((B$Q$GynkDKUG)&BJ`i)$EU8vKShX-)(5bL znJ0w{$l1!)!IA>(?P5t`>jE|h01z{kV+7Ir*)ZXJ^QmN4x4K?S%8H72p+6s_CQ`w< z{)Uc$&R9{pE;Dq#pSbbMedLqT6R}C*rdVyj`PRecM%T#R8q$dLRoOA4lTfg1=Dr^9 z)tG;|lz#|ErmmisIj0!1lsaAC%Q7B%MK) z6x=M_ENoPNMuRM@1T>^%{u=w|Ntnt840aJA~#g?F2USVs>(S|2Kk^rJFhE52v=yP89z_H2di64i={R^W}f5B>#tPu0L8zd;Mwr z3;tgg_&2y4*n;(cIP_$Z~!UUNjRGwVL;;ucD{P=#2p^0R zy0dWrFZl9Zn`D^g(vtu4w&zav6IRa3{cWY2N~vafRdqEKAh-Y>k^H}sjY;?$XOLY~ z(dNHx{16o|_kh!}rM!W=>t!4&W6$0vBsm4Sbxm`Uy7e;yBr0dD3bt$M**d0+Lu z$mcz0%zcuPLn`a|Rz}bJZ>R)&{ItTIFuq_m$6u8Ud5shApl4m76KN_^YC*TRplIWY z0``QjPGWVW6XYBOjUfle@SR^syMJglJ~2bq{_|aP$v$jPJmP&>m!MP<9RiZr#=z=cU!~=cl_7zlXG^c6W<=~)VUT?r4t*Ae_#M? zBev7m^NG;aMIH>U1@t1|0)hqm;M1SxrP0ok@XaB)0K|N!b;7+*jxW1tP-6@V`wxkD<1alCKqLHVO>mA|jozr{fcRiMsgGr179 z$A)dPDIkkhYoGB8QE3v%JIJ;1v|k{Ogc>81hRUgr*7($*sg%oBkBRY<_Y?td8G zYot`8GA3?au}4{xWcgW+`&}qV?>8Aug)%u)GExX^mPw`rf7zLAkDPe4(|+5+S2o6} z)Z$I-$Bqs^rpKejt*Qs3?`a35$`0V?e^bnZe{tZh?lb3AkeEw9|3DXK01Ba*sNLin z$L-C$LD5>?oS0${V1(l6?Ep)+4v}-9bX=DQ$2B3@cmeNmhyO8Ji&&ke40G^&ZIgqG z!kgs8aGv4g>*^Xo#zxC*i!@TyB!wDH=`7jdd$}XUQTo9hQ8I1;XaEK8tUJF=;`=k& zmLE_rPTv47y)=H%0)JbmizTNb-EV(Zy<1PEyLrk%>92X zzS>UMdhKuHs-N$w&N4m}k?~RtyNtZWT);b4&U&F&_<%)vV<~=Q5GYg2o~@RetF0XW zw>R<#@Qi;f`;x17>x4WQGs+q*K6ZZvz+45!{k=lXi_N?DtFcvilnk7Z$B5wm*LxA7 z)X@(ns6IHu{Ehdg3`h7(U(WsaqWDu%zu*A=4hjFagpxBn?d5^xDH!{w`wC__e*N9! zX)sy{!^L)m_g_=|@uU57;ZS3StZcA81v$@{r=pH=y>!Mu>mecn`AiTnIAi$qWWL}B z*VlOBX8-X(yYO%CwlQ7YFO|4ExfUJ=19owH<WczA#O7=zl&Qf zC8b^zs^PE}dx)l|(=f%UlH@hb)=7)X1pkrxx7^%ctC{B=kJ^h>p)PiL=83{fxK1ai z+Vg9eeKN9<`JC4V#}wOGW5o}szjcU+KtO{9^u5thnkF`*%jE-BYw1Do*!YJth4!4} z$$pEE3&?IIg5MMy5m0WK zR#7`jFC{NUKHRp$d}`_DMT|$1!$H&6bd<7-23);qe9Pq80z+PRf%Cb>S^+G*k}%wJ z>edsjbm`v-BeP8*2Y)X_bu}(y28MEm?vCri3D^On_~~ziygDSG?!Ft_9|Ml&R}_2x zCevz2CMpIjIA#e(*`gj%@oX#4A;rtKf;JtHu?7EQ$pP^rLGh z$DbHtsQG8d_#f{`*!wC9^-mo8(H_1_2lxk({E;xM%~Sf;EFmBIKLO}p2ooRbVS<*C zihrSt?S$zyrSZsFdjIv-!3#Ix3n@$&R~_fv>0$hf&F+Zbo5<;zG=Bih3b2UbKt>?> zAM~ZKw@WOt0bD;Ob^y}^PK5*zP3V@2l(Tjyx!);`ujkhBvlA;Kw(Lu?ETW#tCRt8T zpWVLm<}03ae^qt=%m1H3z30|s+6E)yU6bVFxh?>sQfHz}) z^o5mSCNt@|PG*vnx~YRdmMA>Kxt@L`b^b-LEI)2^Omy9B_Mw5!J~Jog``mQ}i*(g2 ztw^WfFj4N*b6zcoSx-OOWZD;efl&tBh0EJTA+F8&qm0|EiFaueHzUi+kFH{wUlj+xiM<|w_ios7qFK)QG9xptv9NG zDx+?msd;#1R=b3#1*7^eGWTD|$yeC?jtMC$;22DH|Iju7W4E&0j)L}L6SZ&~=G|;e-e4)$69(0l6qXXV2{|zHC`mo?X57@ zIkjMjyIh5 z5ysY`uWJK5Gt4uTvD`73Z>eq&$ZqS>jWIA1zEZ9T$E(PuA=HbnhPhU-!af7HqF6&N zOgs9m5|Ix1N(IL@t;#~WpjI%mHPw_=z_Y?n`| zvG6`UFNOmAl2vMNk+Z@=;lavlfEBtPCYul+&jx&R(%*ESoUUis zg5S}CIZ1yCkZQ^n{&G@7Ej;1zSc=up(PYj&S%u4u7vioIiLb>+420S}aV*$<;MVZG?Cg++_{Xh=9Qq}5y( zfJrd`SmYo5tTvv zLDtTJzR=hveao=-r6)4kJ__4r$kg^QD_tX>pHHieI39G5=Oqst-N+Yx6gcbK%hVz9 zNXE&HJF`$1R{Z&9$|lN~zJSIgs;(=6`pA@5dyj53`c%1Xt&Gk}S7 z6SiQ)fxoAzVt&r`8~F-3^v;`*5D)6A3_05qVQOh${p?^t4qjQqya3#SxuAIlBb!B1 zU{|+@vpOkM(;bEMc9|YgblR+EX(a;m4U_tn4RTAk$mfF1Xz?ZU)_uqgW%fs?N`}b!!^=IJt$-$oVgVawJtd#G9~vRDIEA4z7)*QZ3V? zDlZ_@i5#b7dyScQ51w`DWSnXtC;!^z%KHAghf1OMKrom6qeL@B3CoUv%V@~ug?qyv^TI;#=C>Kkm`S z61i-adGY~)U`Xtm`6*>4yTk;v)EXcd>sS6~(*dtimQ)vbV@NHmNVl^*h-?f3+408 zI!TS+!0f40!U#Yt93{3>wEYrFQnD~j`?@cANaxTh@79UvYZ@XpH%m%SH6pa+9jleR zkI-oV0lmsNk_oq9>V#4-g*MW%CE4LK3`AH6J;{4MF&Rx-{sYj0nN!L*Qq}qP?Wjur zs5k|BhfXflj-BaoQzptpU5+F@D#i|%*m~@4$Foh|VS0~UbH9TG!L&O<=re**n4?kp zoV;yTDOuQ?A(gHXfB09*DG^7>urxe8ZS75MtpF#EaxYZ0+oBhAkj>90CUiIYg)Nf{ z8JH|U$|%-ai?Xmt+gH3&K>}O!E%lxe8?glc!Q%;fWlFV45H&gwV;aq;+rc`TXs$vEgh&bxP#cj> zw=2z^ilwe_OGF3-y-JoQFaEVYWuR*I*v8(eDZ_S|A7LG1XA2St4D2+k!_2vD10n%|Pvlb+2grC;&**FPvt)sI(QRvHG-fSCH9g-z{ZRoGR{XI*TdV?a2qZ%q(3ezGKw9>%*!EKwq(ki%;eqpT|+wB z45$KF#&Go=@VXY<7!5t$31vyk&KSvv8!}|iW=lvVH;%`%aT)C zd}?6l14H$G>%NE?o7b$DZDvExsq9!BUzFmB)T${Yf(&HF?zyxC(L|r;&ZD8Ub+tdn zd}}l5hKX|v$MnPt+amAK#RNy0Z2U|fa87e>8X>y_z3(bZ4C^V&F1xK%0BK75#I$^Y z%}r6IWk+HMK()(@(2qNzs(Fr4KYfOi?F7oMbrt39sgt8pU=KNYl^DRO8Kx-5OP=~e zM>&Vb*0ILc4sFNI>&tzjKsZ^71gn~>tR;=O=hXI8XG?O)56FHD=F^Z$#KS<|mYAe8 z180G6`F zQKmo*4O+v+0}7saHIT}brx~^_ATh0yBbkqrw5rBM}rI zoc8DrNR8r)b7^spSfe*@#+VTVbBPHN65oeNZKxCA4n_51bjkh-JF)}+0Za*n3JcUFJ& zzE?;WVi3KxCZMGWA$8Q&DOmdL>NH54dlBnvf5jrPzKZ)=}W_pSs%b=n(?) zbkWb`fgQIZ6PksZie<1g7}5kiO2nj=xV#ORgQZ6gvKp6$C*p$=rDFlfUx)FCIiqo# z$rd>p!S4-_hW9y0w2`e@_`cxNOt zdxL1g0)H8aA5zqgDkcwXMT~C0O!a{%?e6l>2hfA6D#GUOW&keHMUTXBgiwmD-({JuE%`8 z21D0HYjk$$@W~N`4Yiag&EG4fNJ+nz2sK&G%b7EMYb^1*4TTO1(f6-1jL9u?4JsiZ zBVZuJ9NI-|Y69Z01{o^p@WUNu?~{J<6OLyv>m5^J@n=-WqL^29HCK?8{K znduiA%7v#QbSnxc*+M?hFi=b!MpW%3i&UaVyIx*I+%rXUF(BEGh}JhUUx%Oe?TxT{ z1XF9m5iK`bTiMZkfd;>2dJNrO<4c8z)*<5zC}Ex~Djska9H7DsoGwZyXA20&`5i$Y zQVZfCntyNaqi60!L(g@PLx(EO6a`0Hr>=vi8+%*u9v%mhs$o2}+z5S!7yE?Ggce+@>-PqUm6l_-FYTqk zUJ~JC$#L1{?L}px6WRc}&a(7Tl{vqtd!{!SD3{0`s0PdE&U6Dryjk^PRM{(vQ(7&J zISZJLE(#875I61Ts&QG=Xk}I@eQeG2a~;oUwA1Poc?79M6@+MACER0rY6M|M;l_0a z)n(JL_u*`*y+j+F45IrxW40295v&k}%1P)B!HPP9u->#nzXffyKxWNTvIT*9K4Z(6 z=Xoy1Q(@Z~jp)_yQwsQA8Y7D*0woG92ojxW6-Jf`Ors8{ef@UqTtQ|1q~I=Vi7~#! z>YVQ$ke3dqKDogl!u9})Vhox*5sq8+k*7gZjW+3Axp`D*$POt(Yynf}^CD%gy+}sU z$RaPY2$pnb-)eKj+nZLF%qK6#9L6kp{LiG3@Fz*~hMKY1J>E&$o%|Dn>4@X><35Ql zv6ZtoYB`CKcP?M2gO=mdhA6!$n^m#0xzzTm7fYKy=W=_v|Aw_Xs?=q=rVXFE;>S%` zT&PhV`|ho<7%D7p!oCowmKxvHXh%WV%2y6cR$0V3^xnthVu4(ipEe~11}hL@WijJ; zj$quKF?WXTn0n`c6yBm+iYo`T&hRbsDbT^BUZ9gUskDI|l4w2G^0o>}!+H9+FT(->yM2<710nV{}|9S$z7)tV}0{=C(L2uBwd=15e>^TG29 zRSaa@Il*1sa-8hU8Y$yN#CG_k^1oPOp4gZxDSuM^hQOQdy>>8RzN>pT+C*`f z9%5725LT``YN(>97|VKKvJQ;7QD}a0N<{#)b2S7E(&~PEZ)lxJdC<@@MD6%S-w8KY zngOl9$83W8CO*3jOYwrl;CX}UVLtHCXZ(~GV4JXHgf@%Z*zy`|&Oj4Xr6Y%cYgA*o zN&O{y=(lh0wz|ETOjaG{5L|HD*a(Slq{2I` zmZa6)S9L#pDdcLtmr|#m=V;>M2orL%@^|yQ?AMPPm=#ePiWX3A1)0R&-@44vC=v9I z(ezveY5Zn0E)ji-!a~M%5#_}2WtdP+=TQr|D`3Ah%Co%n@gn?JLNjElUr4DVqI4Q% zy{cW1uqUKKN3NA#BO2rC%R#+xu2+P&7G#QOJ6VabJLsdWo;?w+CMP$0e~Rw!(8v@x zL{l&Fu25O2c|;o;nGXA@(2a~@#u#eKcMskXdcL~(eEux;J6#=9B-$EJNP(o+nmL{C zw#`-0e5jtA8_mR!@R}I%_V=miVNsqP6E0<%`c*Hll!!m6P6~9gOi2yaSV-cd6u+IT zff1#om{#v=N=*N>)-ppEUUuAEh42fI0b%+0P_~^w>DO-XrO4^tJ_35=9W3|L%m(& zIdh@^EiE8~WnKN;w!?=E4LEK>R#LIj^GUsYBcPMz^KH7hH-rkcYJ40^;qoC^xEWTY76OfuQk}k! zf-f^~@#`-|3}nY%1SXO)o6we2YKOVW%NGEEgMWTNCQj2UtPfh-;c}mg17&{X zK|CV4$e54{OWINvK_>F#a>=DkIC^haG6D=fVbO>n?i2B}G#vXmy5_%U$Y@>yyo0Wl zrC{h##6hC7q>QAry`w=5qO?>m;V+cBmpZDqJ?+LMRlO%xA_1m-K2#IM`z3kz*JD zDA+-2rjbnjNUXB7N)Re8ORisY*Re$pr?hW;y^D$|YdB7C@Pea9A-&$x`7M3T7Qf)$ z=$GLqVIt|pDjC8Db^3Sf(*a3zTE9cpvz%uAm&}_3U3BqK{aY9(ofz09ZdP!!favn_ zve>__w$vq(1rgVD?{HVF5O+t7tuH**r(Vz%j(C&UD|SmR)XvlD>J$3-@K$X)h5Z37 z7_qbgUsmTE27Qk%E<}67F1XkWMC3gaL)Ze50>{YK6{W@kjpJzra1RMieIj*Lre#X# zc5&xmwF1m|Y7!3+6}3y~=d19)!BkKe+l5O-xut|9On(%bG<~6_?ukis3XrzlrT$ga zZ|PwZs*eBs>#XTPrVJC}lXN+Gb)+Xvan(J{)KXj$9p0{=SaGq2L(=?KjYpo1I6Yk- z82VzS`*vytb82LG3w*{~@CAC<(WfxeH*Fl&k2ZF~W*q7^E5TXb zU!UK&qeMMvhr=r8xW7=fD;-FnM~qq^5pMooc>Kz=EHx7cGslELdUy^RbPbzCmp=|S zpndEBD?YX#PJqR4tPxGA3yuv7vmeF`zJqbxkodLNG^29HO;4Mud@f!?)?ik^zbh=s zNNl5f`bva;b9`TKO2J9z^KLp#jZyuo-57XH(p!1=u<0em-WGgSR5MHB`4QHP_B9!p z2-|TiBiMq~DAA#jSL|Yi{xrK|4mj!6Bk^@mM`|nw69nq`YZ!*C*HXVt35cWzXW`CU zcXX8D9c3`D{34Qt{z*l9synenymGNP40rnZv70^&3+LPI5E3@Fk9~kp;YhuKj{hfYCfGZt?TKkGhR57d%KxLFB zIlz0v$nfhpSpjBVnL0l>>!g#^NF@C#D<$iqv6|4* zIM6*Ofoto6T4&#u!9qsArLfvl*(sF0G{*Y%A@BI2*3pgrE`1X2>$gL?NC~Wie&ZnDErS z;<@tbsNS|exzoGAn4So7J&Tld!{ZPGP0RG!RjDosWNY|4OmI)&MnqnR-_?t#V7~}# zm26SS&UnwYS68*GjMvYqGelx2zwA0B81>|lDsLXGLiK64`q;1^tg^tTb1(W--_$UpoDEnI45)l#T#>$wj9|bRR=o z8>FCB6>>M+l(BtP2{X*1oldt5J11|B0Hv4^p`RDF3E4T(i5Z`!m}rj8W=lmE*DqmG za?kCbpO#VS>gK_QH#?A@3tirmIgcD#y((o-I5EDepR&^ik`i%#a)b;}MzE?G>3CQ& z@K)l6_JV>66iV=alnQ!Ziji0CTUU>t6vPq)o93}$vYKNK2&PZu<5pGD?PDe?YW_~g zrMbyCz?X-SzS=s70#+&jA46X|ldOC}mnlxAX)dXvx*|r?)Sv2JcWLu&a^hK`3_6ou z_0sd$l^ta=h+1C9*5*LbyixXpF zqw(Kj-NF$Ag(hq!^KZqEh~VaQ2Suvg+zbH0NhPEB0U?@iEUc(n^v5gDqAPz~c zidaM*ox->#%=LAG!+wQ)w^$SeQ;p5pv5eBhkNQ#48H#u9Y+cMu6o*#tjd_^`rj$Gh8GR~!nSigbT%IU--aV*`bneaD-;EzS5$8tI zLMnqvv9avc|D5)mLyh!Q3J4?UUr1qSY z8^oZ>z|=nm^4z;ZNMkF=@f&%7lI~123yX&)lM2ufvCGMNDe}-ife4rQCFgR?X`!e1 zRT>aox)vWFGv?>10IJB2pE;|>CG;^KNfMl3Tk-NB3__f=iz$&JN99ZXY>r*LSEltH@~Ry(#|kh!1yT##>Ui8HhfX zJuP_f*uhHLcv7PQCTJP4}=63PIs;33aDW z9k60*bi-2_TT)8!^zp#(UrI4&&8QZxUQ~1_2c^vNr@mjqgPR0DX+pz3{UWN&iGaSc z5*Cs{W@!X}_o`!y#0lI^l`7O_gZ0dQa?^{S^Ci5H95PzxRKo|ZR$3YAQpG_b31^yC zA{}MyScxP1nBxu6kGfC2TDk9t7}w$kK<}D`T1wMi=Z)!*tz{PtW*Zgxm4KhhYHHE+ zb)8^&nXfGc*}@2XQ{Bc0305AGeGRd*STc|_VRbW>SrMm0Q+Ic20yfdmpJ)CY^ax_V zOL2!w+nVzZy2-kmHH;E$-rdfTYEu5pSwCZC&uZz@<_p-{lrQ*k07nr+tfd4x z1JO@o&?XXfxS`dmZ$%37%QTB!NS5LW7XA;-+wC+O8rpXqxe*cYf6~2CaiM4m01R%I zQkQCqJPZ1-*QYU7&FAsEpD5?zYlZnwQBJ{rwks-kLr{sHcek(#Zm@C=SEu`DZG)JB zzk2+0h?8ySY1zt{n|W-GaAI067@0i)hSG$aANFPGF)(-!@9`d$#96!$L8lbFd2~*P zGlZlkOh$g{r_*JJqvf7*#1EBU8B}|O0G0=29r9bO4VJI6KD7RDrPd7P-kQa0<^SQt2(8Au~OoLFSH&7!Owd>--3pNEVTQkrp-ZI@nz@=wPqxBN_EZPmpkf z+=*L;!c8Oeu5S^6#0~|DZ6k`>t;j{p7Q(%k3{|A>wpW@*XN5KWruq&SwMv1ZLIWc~ zqrn3?VK2A}HOb1|Gr|N>qPSpgA&@DF4osm@~AJ49*K$L_e7A*pE9T#8p$S`%4 z#l~L}QGRtb>?&H&!j$)@39Ast)pz*JaT?MSWq6=VzR;ntOaQTsDyj5r5v4jtVhnDu z2nDR!Mfgy`*|I&Z*D;$&J2`R~3i5CH{cvw%DR`yEPXEB6fIm1`rxr(psfJ>}C5TLa zmOPLKQE=pA&1aB4EpJ}n!9`zpI!4ADyJMXTTvTbW5OitbSSm@+tc=TexV#~onkdcw zbD7~PKYvmp3PcDL{_-_vrUuk|)UPTV4n5aRE60DTD%f2iY_SCS#(#4T-#)5(xd+c9 z>}@-f#dKx;9!gbx27-lO6yCKv9|dmE>yF2JjeX{%otCu>1LEz?+`9@tr0PLrCo&yqBa2FB^lb(*hU=um3; zQ9hNzgkTh(Yx20V7)E+Zo6)r33*GPr#45z!Z@UkAJ2D8>O|4frOlgn}^!grv-E&uD zDf2}TwlOIA**-KraT?>}yTl6->@gB- z;6_B07nRAl-yrSwKrvOu7nM&K9d$%n`tTNp_$EnLC}U7{PBDb8YH86sh1?rZRUzZ$ z#}claKGXD#<1Uiz&QnSA8iq{{LFAG}9@T1%CvZvM8dUkc{m{YeX-hkuyQe=9w9aMI zIfKF7p9fWne~WxIB0X{cafX~7NVNn{J= z&^&CT*uSBI>5!Tfrc&TE-zp2PHa+G=>1}!<+)0RLUG9O+0H8QK=kvPE3xHb92HuvPoK0 z)Tma7y!ud_REjPoCi~kN8`XI(>^LUT@NI^;*i+Je9$)pm&_;_caZ^8gBtwQjMuK{X zbH6`SYktO?JL}5id4b{kB1Zfp#zUs5c6?oasK&YDuTD*Bcx7JoZboo zu5TGs-w){WwSL{{(oTHSD-_Ey*gNP|@IlY99&w~CP0kEj53AI6V|NP#2)(jFmAzY^ z@?!s@bQfv24@ex@fS^ure!c<}R7lN60-kKkD#i%PufeSj0v{CS<;uTjp77|ALze`= z0{YABaC)I6jTV^}RH8?TI3QtX59Md76_gUN$QoQb>Fw^XaC%?a8HNv+Ed5NJ7&7q; zyXPlW{Mh+jqFywxmC^a7PH_tj@lSP;i7f#am5QWGPy!?;ptwdXrCl`1yD~ zYU&y6Bg{qzex_GdAva?95L73kkNucbAb5&5uK*{vmONiND&pCvbFv?9@u-t#l6YFN zR{z4dkP6Z)p>gp;0HC2$l%B&m9@Cv#e2oILo8xy^xJMZZUV@oMcgl^mZ26DWvP?>1 zPK`ETsi#@Iy>o*UI#w> zrxHy{l1x9m4hdFztFB~L1rOe6dxXj~oOpgb}1M$RZ~IAsdy&{K5ley{DK%p}udVRkf$n*+mb z#wbvY=}K-)Wrehb_I| z=SLB{9nmMmgMEtxK%8q{6{#kc5VO4qW~n`ve`WFg7TEtRFeW-SwvKp)#7kio9r#qx~*k z;n&*a0?=tsZSDsxN=^!)z-whXhFxYV^5~cfrxAm7q85!Ecx?K0R7)h(-QRqXZ_r;2BW>#Y*g={f3BLUApp*#IfPef=I@-vIb>WPtzS@5F*boF+vCRAV&O+7 zbuhp6%I{vCi9c*prJ9F5d@uw_$i0*pFQs=HdK@~xK{h&hAJk>ua24Ba zZ|I6Pz@pm}ajeO$d1vpE8T6AId@0CB1*%8AKje};gy>9%#m@U`>f_U9o*DK}jYga1 zi=j`&Qc_)8rEQQP{7J9oSZBZLos%OyZxUE|?gu-Pj-?Y1apS=5YF|FDFg4Z^9Wmx+1aTQ{7ug!eP{nx>9K5WV?6W$|4aoND4ok@^}Ib{Q{X{@irS8b@quo>w*24ZXRr@Af@jVH=!AA)^DyXw-Ck;QT#NStEpGzAnXIF zT#MJQXR%WaAW9fEENI!r;)k;lPCSQ(uc`z0&*xQCK>2gUx}U&<3?){R>SA1y7`MDL z{qEZg%Tn3lwR#IWky=$+xl`<&)h&OTw|5uWDCKf+TJ-VTSU1K@lxA?PjWKI(RSbN= zQai&;gh7C{Xt?Y&x>Tv&_)aAWxL)un#;0}w$Vq>DVJYLlEf`L$U*TC%OxHz5Y!Mfo zZUQP9bFF+CpA$XJ3lqI1&14fvl9YOuzL4_ezt^*ihk_@*u`S#fLI+Ma&R%+;>%~v+ zj_P!E4&gd*?Z*ZjuS|o3f+oN!H@fBNX;-rXB_TGp{K#bTK68%Q>QKhgT(7i!YJr&n zDbu@$MV8m$hhE6DD9Me5id&WnDA&ohHpIr0Oqt zBI5v7OSUo9alFH{*?mxIA2wwVC18&Gd}omEHr-n~{AH37D6!0Px((j3#8xVZMe{0wC)ZI4hBbm&e$Zj|1}hHJ#k-=D znPlFcfh*T%m&g^+AxDYVl0BrOCmN5(X(7}mP&6hzf`9Jf;D>#zsAMK^JTUx1$PsN7 zTILoUFOW;}7-O|FWpMuP`cl$TzT{1SgzSigDwJc*eIE`Yt|oNS4p=mWhYGUP8D#Jp z{wB2Fd3HyLm0_nt>TFomW7C>tN#$y}D+vf3GcvGFAPqFmAopa1olateK{_V2Lcb!} z;10c0s{sL%B=XvBs?ZvsI}3 zVcKny(mlcG~F?Q?ra{i+!$l*+iv3t7Lh(OS2Bf zYnL%PD!o-plvy_uqxAL#A}5Uof=4orLp1~1!WeIwv%}RFn{W%L$mz7^GHP$JjSQBI zQRQA^n)t3Ikl4Ti%(AZhS(@xaocIAH5AOViJuyh?<>hUoLT8hpo+fpZ4UWn;*->`F z;zJamZy*O@rGeB<3ooAvVABYh;EkD5Pmg9IS8%N~3R>MtO~Tvh=os`okvj16g!VDs z1JW}Dj?p|q;&7Xzw;?B(>DUq7HwJo2DVqziMixW70uO~QFGdeshP+o>`0~Jzq(QMv za=enyo`hjl*^e33qigYB_1R32|Pcr8q+f4 zBpQm!D9W1j=mV*Ze)48tero^&x$5b-8KRF%7_L!fPT~;^C?M)aDryR1xfI|JZJK3w z7_AX0Ejv&ILY^Co%C+>6zB$$}oj4{o%1kSK)K9nVSay^NVJW$Qjb_=NikJoAIOlt@ zGEofUm8{6uH@(f02=P@JUKa_|Oi0)710dV)2xy=i@@DX#I)(5-Kt0+{sA6Patu81I zKq60OiN})Akn+}Qu%R2#g!L!@00R$XV(utSL-T+}kn2f-5@*iH$;#T~bUh5xWyuko zp#X#)O3f-MtK!**a(=P;y#3kvo(y4C>;0G18ZC}ts%^Ny#ITjE&%KYkMv88_I(1Bq zY&9!(z9yf1e}*%6FNhyl1IASlNMl!ioDi(v!Lw7~d(tMqXRe4#cB;~9#mVW|&W;`R zx=UWPgxDcbrp~@gmQ#)^8%j#EJX6oP9?|-`2NVg%5vAFJD`+Pi`hrEKF^WQ@m~nEq z8qNjNl7F`<--d(Zd)t+9RLsJeTr(2~EAR*#sAXLg5~TUN@>}`>Pd2sn-8l}xK+v;C z+XA=g+Takk=)7422kVfaIOV&gT+#e5D82kKdU>!<)trQ`RzZziS#ezomprQgW5aQs z)RkJr8DsF&^^gjF>h@MbX3;a=laXPH&^9rnHc|pFp=IlbhYXa*iSc6e=Z7xxxDuhm zy)=7ppD}_ahL`(2)1_G2z{b)v05FBXmJGd=iqu7Flv}cvf<(>Un)A^hj^jT~2X@T-M#xPHz#mZw*qlFN%hWvrSc zJ-s@`c)`RyU6o9BBNJ1Ji31+Qp-dL#?%bE$2q@6a%dxhIoJdxDiSj!3>tKt%jf#XdJA8K$KtF|YrGKR%fq(c2D&97xhJ);R3`Efc43;UDlg;@Ai*AYGn>UR2* ze=J=w(<DvFs7U)DkwcCg3#j~J3Kx>)ia7u59KH zha495*^g$L)`}pT9(si9ik;~gRk;oy5~|C?`7KgkF5AJWP$%LPha=Nq%WGE04^0X* z#>&~m!BR7*pLIW?j?e*2Gt5@4$*IM2m&S{vpm}GNUp)A;=5t=(;Xl#_JZ2@Trjob) zFyPXs^#}*OhduKN`=l5;YB;JHEskoZu##5uO~`Y6&hr-IqccvpXx=s^4BPy5bolCd zv;$U|q<;0NK6?pTMnSoY4J?GTyL|Ns17}`js|MNEIYTq3&C8C&b(IPFI7c?H@7`}E zsh0QBIxuNH29XgKN?75ldgY_(aB`{pqv&vuD#tqM5pTy|%)c*QJ-hZF zQ1Tyq;oT7dM0X{F>U{l!i&Fj@4|fPx`(HdKNw<9udj!)A+)!8zk?qmsrV+8p^>^#C zJ+|Li0~$C5k&f#`ey=2i{*o#023;j|6?gb{q7#~hq7IQ-@GH6zK!J}$j4it{I??b_ zdz~CR*+!$qQb)}bA~B()^pR73s~?_=oujFEQ@M3BjtFqG|PUZ$d+d-dCpb;=0L;efLS>P=yY!?=}xdl zx%2G}%EjPB9v!6F2X=XBFd5L)HFL*|n?UP4kimoK;FC!$2~$pi^R2lr>#7lRQ<0}k&5iH-d_7L0x;0U+qpxAeH@-^2tywwr+TB$kcBvl0I4bc+0U5?__ z+H!cx9BffPGH~t_%$I zcKe82_WvIMc0h^0fxl__(>%jeDbgYl4?+fEZSS#zJea%teQ!Pb?)St5xf=igAOJ~3 zK~!CO;lztyc%sI}vPmohV8A1&jWwor&2;YA^`>9^iRCMEdu}+?=F`ikE-t@(H7S}b zv2kP>#pq zK@0RKOPrScC)p*(8^(AhmmjQhbiH7jy`DhhJZB`HJ%090z2_eLJ>xR1IhGDFaq?<#;)9dCqQYwKSd64I>G(93k+ zivS$s2Yu7mZEJ3uO6l>3asvozwc0q``;#B2?b^0{=3)Rrq>fvXOqHC)M_f4THq#To zY0Kh1f-FElK&IRogO8iar~h7e56jvxQfKd8U?-e`JkJP4lt z=;tRJTNan*-uOR$|NPc^Fn4}ta%&JG@Sb&?2?LRz6l@f@&*kaxm~lPz z>6I%3)RUfIybifvR;UZ1}UGk->v?Lcu&ciT@J#?dRny;3^e=Zel*`RDwa-{egr3RFk*p$9 zy%iHq(ldzr{*m?}K3=L8WWAuU0Iz#k1dWt5oY=(%Y!YsL|3l6FTf&|oL=GGfSXjr9 zA9?5f`@j3Gr~dwn-E+$w=t@gG>6N_=qjs%O(nr++hdPAYw@lA&U6@>Y+s}XS=662y za`)oHzxUrB`{b9x8sj04q7HB#)LFgR#O-YS)w4T4{Kn;D``+>12Ty$ND~Ip7d;Hif zSLZLE`0Fp!8)3WC0uJ#^2E)d6&AtEVHAg=5u6A>rGydcseP;d}&rD5>jUS)A@%!J| zI(6#&cTTK!+wFFXF+}9pDy={A!1<^vxWfNv~x8ZR`nr=2u%9}J%-CldzNG&j2!2vbd(aBc+G>HQtL!6U&rAFM?}u!wSUeS)B)>bJ9qDz>n=^*y8Fle?Jq4grstcjtKWR$ z8z1@82Ak|oVtZjNin=gU+cv#@xz=tS-+lkT`l&O#zU7&7%TJx$vVG?019zQlFI;)_ zTXRpJU0GRzZUl8~)SJ<2wDrigi66LY&kwzAHE7nx*p<^~AOA0(p53|YT<7Ymf8i$^ z+iTDN!6znXkFB;_$Ppr7h{>lpI5OVsF7r zQ2cZZ01fXuaoSHJN>9!*V^Hdxbo6ESuVnK}j7LcH3KTNC>Az_d%Eb~yH9`7XQGB6v z1>LIA>LaF@wOG<>4bdoWp2ftk*P4jIc`dcSm4TlCcvJz!ePQa|;$_ZiyFun{lixhH zA-;ns_m1MKIdPRpB(SiHq$VsbLr!SDIy53Meo zzk1?g05xD7Lcm(1QEyD#F!{#+`JV&_uRHTsUpn*H^B6GTz)dfCr%DYS3NuRqdbH_S@d~)`LgxeD2eK%Pzif>z&Nw^ro_E}I-plG&$lgAWq7Y}gnCEZxXjSSQ+2((%!to`^hRkf z>OmJ&fAakVz@tiNtx*c2(q)~}qO*U*UYB^`)J>*`|MJr7{~&p1Wsn+C_cyYJZ8VV2 ztJ3|X#+8@eLA0C$0|e{>hZ@YxY^~L!qaS$V)V;6x>d*YE8U$_DfhL=o-nP2j+H&Kz zdw=-@;hndxwU!?K=qI5Sb)kd$c_})KtCc+6D2BC2~Hc*8@l4|nc+^r_P`!P1Mr`-z3eFHdaScKM}qQxg;2Y1sPKS8jRdYxs>j#>U25 z?dZZ%=ll~-U-;CQuHQAb^&j5%il6=wSiSJAzx<L8#47wd2e}J}^rE2#)cvOqxE=U*`gmrDqjsAs)vnJOk(NP;kVk*di zx0}sLDtTJUxowD(Mh|kXVt4|ej(5561A&5m21jnVHc>VX`~<+GY9#$Z@pR^`G*&19 zc>16r{*{azaabV8FXS#22r4qaoc{Gs>d zTowczBLr-YPuFU2?EAj!@caKUT)2GlGvAJa5Nhp-nXSn9_)Rmn|J?h=@4RiXv)q2_ ziRVA_O#oy8Ko>XwGWj`xIukV_{_GM1;D`ZYjn!(6T4Nk*yfe{0@Xl8r`={^Ov-j|e zYp2@Xi}mN0KlUrX!ymsixn*i?X(mRu3(Cy#8a&E^>ljlDE7pFe)m6^uGm9rO`TUdDOwp~ARf8&lrY`VslaI)4I+d8)V z)TJ|j_?hvQ^ZP#dBZq(ZgSCnIM?Ut6ty>RG-f_?KU;3*nFP>#l?|+EEIPfUq5C8%M z5OU}sX8;(6!Q_rDYpW|OR~Nf&U;&G|5idEd_gFs=JA}l5oGZ=SSi=&X+g6m)ks@2a z=a@(a!3+^Jl;&Mh6k=f`$_T_BcOr{lFa!RBbgsB^}XAGrJn;VR#9@9D| z*84L#a>I6NwxfhiGy%}2&N*uXj&hv%9n$2oIJTYKd{sp+2(EP*gMaH=P4W~j3B9b# zMSV>ArQW|@-M5h5(eyDR4^&t3vfzQMkqH3y!VYx>5g{JBgMf9SE+C9gO?0~Ln;$rO z(?5MraQ)8j{O=!GJiP>sU~(ofBfBh z-v7Pb#>$Id{oCl&6F0p7r_WBbE>gi{fFD~|udzUs`Z0Mk8^T9sEC~eiUu*wN`g)LoCLUE(Kz=;4nQa042 z9b4kJH6Nwlnb(ziW~Aa>nj{M5AOIv`WmZ~TVgqshFrcu~6mRq?rzo4L&6xVTgBKAqOv2l|r{j)v=Ws=XY3F4MVzo*PY<;Wrsf*;7<+(R>MXX})W! z=;|6kx+qemW1{LIU1{M8TW{Yr*$YegBdym~2h-Ay$Iy{9G34Tjds;}5&Tq5N8v*n- z<+7w-vH&~X2tsT$#=}Oi=eF77KlR>=2XELpclyi!`C|x;u^lrJYt1}x(`*0vKUskl z)><4paOlc2Ctmo(mwAVAYzN4qAi^kp)j(KQ>Y1eY621K}sMl*41}v-vVc4m6#%`Is z^B=wOz?)t_KOQWy)x}Ha7fzqMp)v90-~P++g#|V>HeXwte&CjUZ@i;+^Zs!A%>#8xMcihu_cdKD^XeTRHLAbAR>cZ+OG^&o^(_x^(gHKK}O* z1sDMD_OikQL>ASec6-ljk59e&z+=Dl7mSBd(Ba)^d2V@We*WHffA8P^{vSn`+P#kw z#P0~??l|`t0!*vXdaP_`6UMC`v%O+7smYKh4T=4h;baS{RRhD3n>BH=~wUw(# zkrOkCb2?Qi0&q&NxqLLUb-Gl}%58hxf&e@m%q((fa5vpPEgu}+eL0|n{2UjhQ3qVv zwwZJv&=x*>j!Bq5_o4yhoO4s}b2NvD{xhBKCOat_-L6;EvLX&^^nd`;Dk1=+2hj+v z57W2=W4aiE1Pm}>Z64tm8*7et+U>o!?!V(#KX_)(o{2{H)4%e6u|>9J&(6zh7w`P= zJNCcp`!6nEp1Qg;ec+aP?0oGbzt@^u;z2jS0HSWJiziza3|pb4bxDL?Pz(sHRs#%! zAm}ur@mqHue(!y|9(ql8`_|`LdwW)Ay$Lv&htzDmNEVNgbyO*bH;lhh&&wcib7e4ny&<@yJj_kbu`1ozR zuh!SXrGlQy<|mBWWTC(W%1v{oy_A$BR4@-!g*0k(sS@cpkQ=o0>BY?D zY!}R7WVQfR6(1`M`YJ@xNTulfT9-p6F;!X7a?k_r+|?Kpr4l65?!JX%8q+}X#c@qd zuQy@r_2d3UyfP&TEn)$r{DX-zD)Dm4VpTO5(0q0DkTllbNtqZQ=f~4Fm2jo8MLb+1 zKrbbOc(XnREaDL|tcPQ@Fud;0y|4P!pZ)eM&hEmKfAn`3KKWQ<%^xw4&OPoZDM?U>FRO^x*fiX^{CP5oc`Ry=N^6rS60VfcgxJHj<5q;mb#tV z%9WM#t@-(OZ8_Sr>&W4Q*RS%<7ys)!rD zS0|f0=db+9NB+3G7PX^J(90a-y$;^t9gg9pM_#&mb?Jd$`-LZd?f*XWH&3@C-tKn8 zcJSpt`P29P$N%kyw|wK$=bm0}uR@nY6iKxM+AsT9Kk{vJ!=R`iOUqwr$$bvaWs?xJ za;sQw{7UFAwbpd`IAP`Kdk&bcFk^g27-`j~f|Xh9SdN0l#S)e_%nm);Nr|R-OeuO= zl-Vq05|$eW4rggB-_woA7N8XU&xQ1$zX#Jwe9XN8-2Ma z60XpeGfIg_?Tf&6Tw4@#W_?%x^0$aWQT2d&sEgP_dPl4OZZ!0U&$#_?fb20q+@jJ4 z0=>U!+d=>U);qI?a~5JJio#|%)*Np`_qx~K{HlNRp$mJq!OH2@W6wYH+n?I8bL;sV zgMaWVKe6S;W9_*s=fD2wjd$O%wrv-5qsRXJAFf_l0*t~S?6%rLh&vT+cFr${ zd$&!z{q8NtuG_YI2Mb`V8$h?y?LfpiHrZ6Y$vC^ZxDbp5^+t1Te&O9t?XgzzD-@ z_QFBghOEaAqUJ(zk%@tfPsj*Ow8uS8b7>!X{9`fLD97SaiqCxP(}SYLZmGwdni`!g zK&Ak<5IL+Yi5V^|6x}btfNu#U1Z{`^^D{W~8xK6B@>wdtAG%w(qqV-bXNSI(b4`{FYvmafim#&+F()AYU@#;%)&F$|zx zk2vdKyVYs~N5+~#J>Wrmtqnn!Pq5Y1*44+Ky86tic55xzyJKeG_1)=a2ivvI{OV$- zgS>;S`dEE-a__G3E%l%|*KM76c8`A2WP^^V5w z8`gr(OMm{C-}=bMc$Wc(fI$>R7z8}x?Wo(Rvl`O_Q_gp450g!|K|@MeBXO+d&j+x{C{5n2-=;ttyb0N?Q%;{>vQvA1o~;An1qVv zZ33ymN_zCn<5pl7${ia4xx{WBr82SZ^^lHVay!jhaVo3#(Tr^VFRW4&t8;t&;J6%V zh_AA*rRZ8Lkr-6bbW!+Mgr&Bv;XR$klq6r2%v@-k)#%b!X-xdaBE-Ez(Gk@yzbLG?--fA_rYz=m9XVYU5L^a-mZpg74 zG$ROVHLOoGC&wH0Fk)AiR-XUbdE_zKTY{6B14Ab6M$SM$BY%J04F&*e+s3cXwx#O)3{eKDc{n7 zH2!=!CVn^h1Ray*0mp_e^4q456~I)Jki7F`)ttL*W8^S7^h78E^#3M90XVvp6oWI( z$r0=PnoUIqfYRt=de%!+uh%-A?zSU)J4?%#PhCV{5k_(K%X7eN$d5l(1&y(0Jsj(> zi0=xH{_MMV|Ij;E8tq{1%1eLuovYt|x;|dp`NsQeuRgfI`QnL3AOGBEckexL{57vR z4~y5=YhU{}|8?#8c?c0B9`#;mjW`G1`j>~^~mMp1w)YK~0? z$QrCspKNf3YrJ*l<%mEmK`1JoEi0XXDy*Dj<{*m^@&iL5WH$VI6fw#XdT3cS*J$?KC^ns`T z*WX-x?lQLEtAF~*cmL`yAAJ2S&;P}fe2uqO*0eT;Vh7G?Bm-DAs2*j4gsS>)5xw?= z5|L-hNGER@b2K_NJJ&<(969L}Qc0bI%ubgy>8L6jin2Y)qlRn@$9SR zpHR5hT#-1$7M}>BROMQ+I?6H9b)p{6P;%6UqE93&DohZjeyW_LS)1ZdH4CIt{izOF zKK6Lk))*XL0?utudJirtr<}9VQ)!ee$_J_%NPdewnw%WNi^{Rv$yI`r9Inw7IaKw@ z{<;cK&5_F&gp-s7D18ix3=zT|Z}F@kxk9fBMSI5Zqmz;vP5o%_C>6$~4Tsf2=t3}2 zpSkyF^xXMNr!Gj+(&%68VZCnxH?T1_-VE!!iOt)tJNk3)oqq6vHbl*p%P)QDt5=@9 zIEK@Q-*x+zTMu_yOD}!r@snTp#;(2lj=lAvQ;T!8$@Fh{%yOj3&z3_80S3T9gfhBx(J*z9tIdNMBV_7Ee74Fy>w+^<=n-U zt5=p6R)ev|_|DypE#vj^u};*5wIz%oLafzUb850tZ`7lD7zR9mrKr0!cj@wp=jWci z7_^!*`;WDu)mmHJa{X+(wKjL@>hAq}ZrpbRuQeHtfBvU`?O%R8v$XavFhkq{j0`bGsPYMU({rRb*du5_8`;kTuP zBZ}xge~D{7Iqgwra4rekOSBey3`)eoGf8b2*m?!TrFJWcNJb^y!uX8<1RiO_y(^sK zBGWC$0#_OAaf>NsA8`>F=u(_hh##e&rdfnjJZYw@_shkrMvU4iK>bf71`3m%U~Y(x z0*N&enoZ)T>~W$y*kjT=7>HA}SgSrZaFw}A#j=WP8&yqHhEkg1dYD@ZOpe*c5NoV9 zG1UmZ>&BgLy7$y?d^B5Ti#ZVhI2XPT0Kh_EjYhLM$-;ndi^kq}&(ROPr@nPxjkmf> z=f3sXFO2i{)}u$7JGRsg%`PopJ^9t|oP6Rt*X=oY^fj+Of9Z5*x)wa~>|_7_W4y(? ze678_R%AP-j+_1Mfvn>o+lXGZy+Doe! zmKH9~U%5JeX=VB9#Q50kfrC4)-w{qUco;>zi_k$}s~uSDwmQ562n-nG*k-T_UEV=h z?kvt-Ub}Rm+iLSJ;ACU!#=Ya)Cc=7+MZjCFkOdIb8jX5AsMT1FamFL)Fj!-)g)3L* zUwnD-g;TASPOUi^%#6)1Uv6D(ZJpi1y4{84<^B8j9=h>BjnzUJ`^>-oPcQw>XAnYw z4g(eek2vqm0YYS*aSS5PS6lO!Pv8ABA6R_uOy@fn8@T7v-rE8y7e+1nYqk)pEd|@Z6_C<5#M6L5VNlu559(~m z06>03Akw9!Eg@Pe^T5RXQ@6)NH_MSy)1qYii^4%cUP|k@Z;DJ%EO5t2rg(fJ$#;y4 z%+?y|mU5FV_9Yyi{wt!w8B zMr6`NRGrNN#Ud6AOJ~3K~!PAK0Y=!zQk6> z?wGyt=ifDP?*n7N8o*D#_{6i{{@Rv_ZF~0~IJ2_4tt_st&0Sr)bhSG_9|Zx{!f^Za_|`4s(_>-15k*~C?I36M zMh)u$vH-gQc6k@O?VuAeT)J}g%8M^8y>y|y*sax?(BvzfmF|_L8nP`nZXMq>&7$yz z!v|)kcOa{G03QCe-#+<&{CSOyx5H)VWW!s;fdPbL;p~04oqz0^sl`sch39K<;C*j8 z^M8I~d12|;zU#m7=!pY2@7cY7_VVS^cfS8!U;o0x%a5I1eEP)B!*4wBx>ue3%E!a7 z-s!9X2Z+)G(|aMI{^Zlrp2$ee5!b9ca6=!(6DN(Np<6ot9#pzZAg2->Ldsk@L_$)I zPL!T$o13d^8CKC0IjA|zVsCM}%mq3S{m2&QP$g)iyV*&=p?6b*J#dAt%aI>4N*e9@ z@?grBDYVRK^f?&u2|&?|RTUJM*6Zj_EI~_O4q${3N6XYV0qjpyl{HwBjmtlQIH;xE zM?$4fc9ZlWRD};JiU2{a`g|}5n_;($W7Cb``0k@W`lFxw)SvS2oL!w?=Hi1V1Z2Ip z?vODEn?a*76=KMmu(Fr!{=h%n|IW9=);$5XT8o#TI&os|?5Wx75A58t^Tdm%w(guh zd*bQyk3Sc5VD{kt9mfw`xN?5x`n?lN@TFh;owdi#Pi@&cfBEV}W4tysj=RFz!7by* zcTe4Nc;e6v&DmXz@$nFX007(V)s>Yw-db$0u0kt99w2O;n%p@xQxAg>f&iipUyXux zCt6$Sv{&1m4n!C*2tt6cz1CV?Ug$2i+Dpr8mlitni&489b=tfU22<0Mvs>%ajrw>a zWQbkBwN^c>*P9_S76d_;1CNlis149wUA}tx^5W@pt@Br-Rwv-K`q+54y}CNL9B|$k z8ynv>Hnw9bs5f@a&aml52QavB_1pj9qo;oV>(jMuEm-X=ARv$8H+JKJs~xq%x7@k) z*cSM&pRfP_?7exACdYLr_I~eWR()4@PxssaW^msaf&@qqAi)a~mlVZAB57$UnJbaf zu4E~sm9v=3$I7SVm1wP2qPaX)2PyFcNh}Y5I0<4-^xfZ4 zRhjSkkF2lHuY0;@dN4hh`2~sTFDol6D>Eze-LLP;7pBJ_ytDP_P37mGU-;gs8-Do2 zx1W9XvA^~3^i$JQx8Hl{{covG|JnT6Ge+)#_rK%o|I4St>>z}q*3!`VKAh#{F;W(| z%`a}vy%!*@y_&Gu@NDMXHHz%qfK|KtS|z~B)0SEIQ76Un0yzJ^F|r&R%P(}l-k_}7 zGdOQ&wn67?Ch6a*{oN>EbL?LbJ!Ts(*{DJqsu#C>#8za;1;5vpckcvyoB_C!aaECV znQ}VXvj*-Xl${sta<@H?osL$LsJAgLM%U8zFd~X0E~$ZM1YyXvoyT(%{*^JM0Y5sx8Hx;&%L*O z$K4r0;{2)Uv(qoV&;Z?b?;YLQ?$b|wuepCR%=H&u_(A3y*$orTyKX$UxNz*oTlay3d{(dvUgUb9?N4kMZ~Z;Kj$k_4=QBGro`6GcQl>>ny%JfArBeKL2~4SvogI z6c62a>%QZY)AOg{LB(=@Hd>XgnKqNoZA-zv+3r1>4!DL<*lc)4+5In*iyI`a%jd)z z+5c9C63gwcGu+TMH?DX}Zzhe}f~;M22G#9t&`fUD@VfWibJ*l4-!V433-k&FhuGxV zyV1s~rL*-aI?r==(w|E+X4e>iONPDqdG8+9>~UL?)a&>3ReR?e7Gb3LXQK#b-IaAr z`?^N9N$#(1|J6QmS2mW9|5{&keofDcf1MFK&f1Nt(xz|bhuw9*@KdjQ*Zsfu_kQ_r z{p?52e&K5_Qgnk&^vVC zp!It*GiMiP=Epm2^t$JsIorw{ADL*~a=071Q&ZRD)ZnlG`9EL!;$Muv{lOy-pE&&R zZFCEvwKkS(PB1W%)!qo1DnodDh6_pln1ILp6vb?1{OOs*KS{%}|tO>{f%9 z=gZly^#)p+Mk3-W23S|T%QBC4Q(DeXjf&bG>*x?C=G0;ZK_&$HaFVcQsGs5O$B0>Or}Mx!c>EZNxXNMX}xY;tvUKR1Y) zR>V)6ESU8Ldq-Tj{o^uWrv&}^w(HHFu^aWbVoEOXLEB>E&b!N8L>m`}YYf1yVDEgs zuPCo^FLH#e6ZXswH`8=$D?%ET6?cH0e=+Dm^yn@qum9C(Rz-IFPwba_mieM7) z>Znl5eOB?(%|G`Ow}1HE&-~S2=W63O-TB4e{TErIHHZT<>@8Hpo zz4Pe19~0?xsF?5ezQ1_3=+88~&$v}C&CkrdT=kbGI{U)hVD9CatkIxDW9?&yeW!gm zusb*RozH%Ld}`l4|Koq-j$P*u@S+Z2l?`I-n<x^S z(eF4_(gzyD3oKl9!Bet%-zFT|zE*WSGFI#JWIK8m(jSTx!`_KL%rkK_^^V;9`!MXED)gK*#5xXKxT3s%jntFSFL zd1qbQ^VsrzV8j2UsPhCBlHsaDM>i@hv%2qS;3QpOpZFO8S zFp;i0a9~uEbF`6QTd8K`T_$-ph$RuBL%09(kKX&ypPv}?|K<<=@XohCHrt<{fATqH zHKELB3}?cb_uA0v=JpLg`>yNW^)5_KHL2>y#nW^1^Zf;8Yv EiKK=EX~b3;tqBC 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

9d) z62{`pne%UdLwUo!W{dR~?_2-j|8vjxeEa@?@?T%NZ3#cRc=g)W+H`bHAAYD^xTM4A zN5`?)X_VI3Mwtw4f5g0 zhL|FgeK#bZSA zw|KdUD>(1_d`$S{pMG-UAOhRqT94_b?w-;P6;Y84jd<13v2Wo#EJGb<)^Mw zHQLnBg2{rm3GM-wqAU_{0g-Sr^MDgnDM&~f1xi&31QpH{`jL5QDn$x~bjwTNT$-vT zT2Lq^kkq&(11O~yso_pgyIQnYC@CHRMh>VH)S{&*DTPv`FA)#?(TDGU-yd9h|HpQ& zY+~CTUmFbJ$45+|U62rKGwUeJ6fwa4#3STM7x|~WDcn%$i^;NYYmp z0C+K>P%nJkEC6oC*K=IwusqA(ktEo z^N6TUfJSCjkja~5CU;%>Ej$=<0#31rSs;c1N6-nTeUEs!TN7s}1E5*f8VL?hX-AK+ zVT5^3-V=r-MUhTG;n}98kgBRBI)sIy8X0t{8hueIDC`zXSybt2)deG_WE&XvU2XSR4$hq!OphPF_|UKX^9#TGud0>)mA6ym${&1Gzi`cuwzKrUKp-T5fPjQq zKu{0%>iJXaarDn$*aPq_ci#H5|MU2!SH$|Ude z<|1TKsZ^?fk;n=p1W14o6@nI?1WhTD7@o+AKoI6aQUroTVkCceHpf!Q_2L8^Dk6Xs zjWE?Bh?z5XTB4cUqfO1-012B>PHxT9zP7=K=3@|aix|yEvrWU~-U1drrEEOs z>PP@G(MKu7Ng^obo`tm&6e2Z(ibPPfKqb;;S;fRr3rh7cDJsj#vwPi{vt0Bps5`s6 zrw-5;8#XW7xoxjoj!HSa`o&-VmtXkZ58$rT%Xh#2%)PI>_homFpZ)B|Kkz}>J$>*k zZ#v%VOxNS#IwpaQcC@b4K!i7MBezY$+L%(BUFoZ2ITr>p+{MGjDXhUl0Fah8+7!(Z zqEeyeE!^RerJhJG5)YAD%WRS$gh(ZoeQqkyLI4T4!6OSxsDPrPGwd)6+GL-D6clP- zmsIs=rIb=S)jls#rBor)?vF4EtJa;eEZRByNjP3UzV_jdfA0T$-__swpkJ9-x@j6P zO`sAK2h4p|Jr3A)%F_)N&z!Bj^ts&4_^RWpo?tvrsLxBkXl@n&H{*th=RO{NPFqiN z8Hi*%5G5_PswOh$+(N$f)wlk&zbLPLxe=$EZ`!my+8V7=C1TpH+R-qrwlTQ~5ukXf zI*p@+b={(N#U(kkp(S(Jd~5)vl%nErvj!F+sfbiCVX8u6raES;F7LgfNc+=(o72Mr z2%qn`sX0Rd;Nfs0P0CuhyIX{X6iJvy7&B!=8U#lK34@zqYGL690#3l3q=8hCsz`BC zO4bqpOD$;$8&$Nc5Dk%Tr$;I5yKd332wj_58`>CcacXC=*y$$jEq8Ypo!9>8%J#}f zKla#fzkj@N2Yej24K0!g@vMqJTpEmHJdRS@Rk0D*acDoyoGq zh2il}KD-{T-16o(`q@**W$VN7(cwm#0x=z~v>80Mo2`r^CaWlsLcq%kDk|Nk7@Gub z93^6QYJfuoN<>I0Nz|pBz;vb-^NA%2C|YV!Es&suXqw0oqTvvPKvT>LwEU8ow!6Za z{R3omDU}|A$vr(t97Y#Vbty_wI9#YziWF6rBDK^~yJanlq5^TK7cWZorBlTSnnW4( z=%b(h#4rBlwcr2v_`nfbbz&Me>Ghli&P*HsD&+s4gb~GykySn2cAv5r{FHs+XFjZ_ z-2OA}^r`>xxqWXhL<0J3QMoS(OXIn|{&_-ozWxdTy#bElX8f5B{^>7y4ljOATl@0& z283WBlkK2DtFYX4dij0dbj#oV-ZSrg4{klZ8?9?tZ?{9+_O(=@Oby4|;o8cNh6+Y_ z8;4A=#$oHtsuppKZELL+Rn9P zj7S42nn^R5VVY)yI#MUd6HY}7jFZ5@m^OnT)EzdtH6p4igS6OQI~To=;c7D7bFJ8cvtK4j5yT$P_hf&Pg#u!JMsTs_IL53*c&IG~>K!hatKK(Wc zAQ(`xQz?={KtV#Nnyyg33~BROUT;31Me*+3x2Nhf4_&RpV|X6;P3 z?J$aHS3OL^87_sT_EJlmwh`$19)d(<6$7`9i|GC{Y~bnz;Vw=#^w(KR_<>F7Nb5fqfF0l=EAw|=;a_2Ao4TB9@ElT*Q> z9#*ALB&`K)ntU?4i)Eg#6p;cgqAFoVHzLAaf)Yrx54RTH+@|JJc*~Wbl(_;-y#iIj zvuYx!rDkqVElHft`AZtT(cNr=FR=pMvRxa+m<*x5} z=gYtM?d|MheeuF{`ARVjYPg?xg9~TyCZF4to?rI=uStAq)$ymk?D@Xt$uEBKKIsd& z-52|8yinVJuG`^P{Biw?_>>!by6t~WSL;*8pYsCXr9S~T<4=q0dC&{7CC|2+iWJ@J z`ftAb><@m&`M>qocfR*q>YeBJoQoF2;dHoOm98##b_LuXZ#I{%ZZ92SW4+dK8i(zs zuoOt!3_eWsP=r|vM$t-Pz=(i|b}B_AHFJbgQt>@fZ4&0p9wjra6fK$Q1u$#F=V80G zFiR*#n1@+48QUxj{bXsj~wcQ+Tj;>q_39WVX zQI@5=;$_@Bv-VY6*e!2=OZSSi!{ZOiI*N2I&CEdr;1+J!IkSKEj$4N}zw$Nz&40eV z=g!S=;rQyYQHS-mUA$~p594^N;~>W?3`0y!!qFz0sRhkoX&H-x#OxRmB7_F##(GfH zJtUodi)g1pU^b7-rg-W#IRsLand_%huSf_;s!Em-6l$HVzj9Cz5fqu7xgadb+C(}; zi;AeQ5Q0=?cYOVXlE001BWNklN+~uQ{lsis!PP z*LCXEif2onAS1Uj7q=dXW@lBY;&4%D&65;Ti7IK{h7%j)JU)~ZwT-M6nC;TE(Djp| zXQsiD!t^i`N`!+VwM^k`js_H!oh5;BY(nn!%b`sU+_pTp=hUr@esKy}?wz{#6+Cxv zZCI_3SL>tI>f%LgM>cD#l@DVvxAn%42bsdQU^oF(4R>FUcC?P8wTvFk=n#rq0FgxA zto4K&Q}bzbYbn-bsi+#zSxMGsuj?!}fcYU( z_|_;Wg`(2cqO}y&T04qHM7YzLSbe&=d-JJz8>V5hHri-mE)Z&JPI;T8#ZI75`~AIh z-}=VazV}^c-gM{DbYXb>LK#U^OT&pLU9E5Q;Flidi+aRgwxg%q*A4fT-Ear=H1R?d z0MF|A=ege(KK`5+05{{t52lOJb9>LzJSI>}t!H)@?|S`RfBU`X|NZaZeaGu!uP)4& zV{EUkuda?pjQ#%ZqE=sx)0JzR$1b<4D;)w;4AW*DwpIEVZ8}oaW|)j)=J*pD zB;ezKsR4-AG-4VD+YB)_SisFZO06*S?c~R+<4cc^-ekF$+EfbmPVb$%<7LB@t4+pR z-}t8ZhF6c{u|IU7c+IXL5GW$M-OFBn7hZYhoBoF%Ti^TYA&!n#TdCsP&2Z(4UpvHl zTc!a=S7W{5FbO<{kyBF-v?+@mm5>O!ML4rdtUxMXCL|(nuL4$KB}=jb6htVs!OZphXx0cx^x2~tI624y4Cp=|bKLLh}#=I)<> zk`n+j(#s)EssmoP3!5#85hwKlAS#klKiNv>yyGAedUnE?Kk3!y)&luYIkA{$2z=5D zD3MaKGteyeomLYBJQLXywOLXVPZj~Vxe$P6`yc7O5JUu2q^J~0(=sZlij&W&MNu_o zlQJUJissMDNHtKVT|>+jb%bbY+#{7+L4l-w7om*rq5b%iS=uX)Mw$cXl>y zD8L__0gVh08qN_-Z>{J@mt4Y+I~0SPhtHW~CJE z#pp4#c5Q{@EheJ{906#d!;(U)DN5_c2p^}Inzx4LG|!)Qq67mTG`B!NCFvBYW;XfM zU>@Nx4|8OlA3+b8xjB+!uPUjT#DE9V4FaGDf*PqeFf)i}qCqDVYDvaS6!u8gydk_z zZ5Y~S8po+Mw-JJ_R%!vb*={%6!N+N~5&4GKzT$`e^6lUK7qGWDy!g0XI${)1pA1Sq z>4CiTsnS!#&D`m06fe~z;ZJ@3D-(S_&GRqFeqZ>wSpeLO8!(=fuwT#ppWiAKsMJzk zee1zrf9Gv~=ZE|Eeh2Tp%Y7@`p|Z0nE?>SlPNVMa?jD?KlTDW|ZyvgM^x$Q#r&v$p zY83TpY8Vm6+t?1m(9+ee6G6bV6r~7&!cwYs%F19mp$bzb-90QxiL+q2N=m*c5D_Mc za}NtMYg2fOmN|5UN4U@J%xokF3kz_f{1Kp2AhVi=a|UR8Ai}MM#|+^Q*woxY{(tt~ zv{{n#Ium=IbKcCVTle0+y`UEWBtQ^cKu7>3k<>S;W%*uSuInQDd?(R;9djvem)Bs`O)ivG64N)BkK_e%JyN?#tVV)#)TG)1q z$zrN*h9IiNP<=g0MGTYqRJ7|#CtB)u9PgfOj!#H+bBeH=Ev{eNxX)+x+DD$m)9)W+ zGrM_DePNz#-Ze+WOvr}KLu^q$= z8;C1O?#mb6)8kP$|4wmfHR>|g-`|ofn+!rfmG)v7l@6clU!<1 z!A>cWoFYnyC`d!Vqa~>#rw3UcWWf-!`*#Qf4dIcL7TVg9ARz7i(f~A*g~NP@_o1Sh zGS;AZ6OHdUJgtn#hB>o(Ng2tml4&(S-WsHtl{Qk6?8vg2QkcY8|N+*i|5guC9Q$HbUZg7{bfF;Js>%~2Ecf;#IVshll2W>bfijN3!n?AqnWe)$tm|K4vNJoE7BbXrgD#>)HBy9oTP$P>Re)Wn^Y{#$y= z+k(*Eefalaebf3bcm3Pd>3?^RH1VJSc(C4k>ziumH~W%&hf6TOxm!fAEcWIff9%FT z`;}||#Xp|^+)wRaUeLGW>1huw(w!dPIX%6{S%2l~b=lur-91@;`K9fPckO=dS+?8N zu-W#rc}!-@Ayxz3RaN^^W+fzmDqYc1REdr<6DdkXz*RcQxXn7k!aYc^(@N8>XxF8K zo=1Z82(z%DBRqXPLrfXsQGpQjg5-y{ZYGr?4oRL&P=g8x;D(3@%e*iI6q#9-?BYPs z&4}a_)r1RShN+4Oy$Kdjy+K3)CX3EM1gRJjS~xQ#@pKE9!-sG9Gats4{qe=yW#!bliJqR` zkzf4y_5bQWJul;WJ@~v#>-F;fsV!H&TqLBu}QJpIh2xJHhxNDoEOPfd>#)e5@=)|mWHsqQX*ldM-nU`kdl@y z8PS`eThyXilkCizTAow7xT-3NQZyUasv@dFVU}cxb(L9frb>Z<3~x#Sb^1!BjG3TC zh@`DNg-nXRP?$be;kjAa^aXZ={v`1<-VO5P5Tw}MfnerNNERtbg>ZI{*|g`L7&2!E zK6$gR*=?buOy85Mgz(6&e+uBGbk5yj5V9ycWl3L3$w`=~h!z444;Vv)N<_2)8KSm( zo)l@4097~3NkYIqj7p+#j9Ry4-YxbQ!!VBG^KQ}id&_a_l!v^Gb(LlMFR##Z)s_!PC)7mD)ogH;%Gt6sNt9LH&_<3!O;x_O!J?;o%$9=<-GhOLD; z_ZByv{_xYk|Mx%okAI1`j<%b#&Fy=|=`6dDK(uovzkuI*Z}^^Z`S*Z-zDwZuVEye} z-=P4AZ}a$few}x1JtzPktoPvheosn@mNM)5PH!ES&wS+J|NJ+u|L#BVk3HU(s?%z7 zww%DeJJ_sNC$Haj>d}>}mu_4?KR>(sg|7}@dcMAPTB>%lezRV#&zH04WQ_52$*Gds z?akOJJGgj=iwEs{ow6Nxeet3H`oBEBaiy+Dopsyo zu)KFVo-Jd&k@X7q&#+o^9Aa2=GVx$F)(IX$0i9@(58Oq40g1)aj>(%Pk5W2;Hg-Hj zf(Xedq>I*RCLl5WB^#y}Ql)83lLAvjlT0G;a0C*6OZxl>CB@=sB7$^XZ;qdd+(dy4 ztDrOw&<4gxYEhLYTh0)-)CMH6DBJU`ZHoZd8vl}U)M?{H**%{BMN&lr76?(vMm;D< zgqSauF~x{BWDu${LkR#l;(~r8xyKzqXi5iC{7bY@ynL3Y5J7;*q6mtl03~E)Y2j-T zpr-F?enhm-s?fQhYcmsfhq7GoqcX0UL}rMwwc7~^1_PF{I+T5S)nUY#9Y>0)~A3tu_?i|6)U zAMDgTl=E)8S`O#SVmg~x&zBxmyV4)@QNmMLnkfLvkkCH8eVWY)2!#8TuJZxW(4ex+ zy3F%ZG7u52J6cJD?y3-egM4&o6%&J7<0iaefM}%)jOf{Lx3`0ariA&sv zk#OG(b+h(yYqi$(n%mJULW0Wh@YOh-FYD&q5SVq{qVKhjsA z06~N*S;P@i6dq8d>;Z_g=m~4G;J@Y|O^xX1yN9@W{}T*99N> z&88Zm?|!#*Dt^F+;h|YQv|h}h5H3!Y4vA*J%3Pms_fixZeE({O+$e2DWehf8Whsdh_2S8g*GnYUyX)!J>ck@aWf{egA*< z+t+^Q7yF-hdVcNbprcNkTDO}RG2g#*{h?XG`JFq9#pO%ydw8I%U;NtLKmW44He4FZ z>i%Zxy}PvVahy(87@{-SY7BMly7}Io&WgiAp+!p9sYna*R1LZZAWBG#hAB`z@wuq5 zrxeM2fYjDo=ml3oG^RQ-o z>XU^Vt4FO(?CWHB^WaODziNztdA4`MBU$$V3zRx!ZC zrz(@j*674&F`CpGHDWN|48Ez9BI@C=ZH@b@I9d5Hj++5}H(TtZu#RgU9bWmRzx(K? zKXY>R((TR4yqaTqax)korMtd=Xv^Ww@-}CShi~0l-Z>VlbM43V;L}7S ztHpN0P@{$fDe!6%5bQ+BpajDW7Ww6hi01kg-q3&6I(c$v>K!u0qOpCN=7E$V!eA!) zr3(`gyZW>VwXqu=hHEFHqM8M#vWO&$j3SZ47qI!L?zmlKMiDpl`EKOs?o3ItBRALG zZOUsMr(r~`RzXNNo7XyRw&$z5HK1w`W>~E-#%#7gQB^T`+D=_4KHkE5OTW%H{_KWd4{q-l>&-j)5rX}D=feMuU#9prPY_NXr~8zeEgyP&-~cU-~Idh zf9L1Bhi}aLzN>^9YMGgp;D4RLDJ3Ar@z5{gzRL#Y5Ec7^hU@@ z6=0aBfH>S?Y4RR0r+d>0ShzU}Qo=$(I$ zpE(F>t~5J{n{j1GIRJ&CP3p>sP?3@?aI~^8Pe9GE0E(s2JDbU7ZaooM6%?UEJX|S~ zsbOeTj${ovc15W+<4<{mOOyi)G<^annzG!;(``pMO8QZ>Y}uj&DR8nWe1lARNkU$C zDskZ;6bN?);VsWgdx1#iT2M;+K$09Ij}%82O>?av!otIg3aJ3X-6M9+t&KF5<1ZjQ zLb|LDLsY^nU*Drbl8vWj-g`{3T?x3@JKS6BE!Qg{KYH~M75v#B|IzUJ-2>=mJNkBt zslq}$VjMZu3xZZEL8K@dG?O4LqE>pqf|*R0J2AOa(MC_A(qh<WYC!!I~W;6u~p_-g7ruh%^0J#J-gAK#8yz+KsZpS){S0ngUT)5(^|F^Pzb9#!`-WBR#h>rxuJbqDlD^*f{{e9N z=Jg|jXD-%vUjV$N$A56^K>_e!y-n+FKbgPb1zTejje)Hyophz!o9UxR^PhV1%5Q!4 z`tSbsL%;lUvxlzDR~z2Bd;GQAw_o_$-8;9<+-7}Q99}xwFTR?|Vt#n}^tHQZfBA*w z7hkk{(_T5KXXmTc$=;2_?qFf3D|_uUHd7~E(ayurv0uzeS4<`ogFsbP6#|Dr34Mb@ z?B-rQ=hEn0yHfft6i{T26D3^?QxWXx5g!qtw@6ilyR(@cMkM zdl>n6z?=p)o`&yw&433i1R|7%hmx*Tn*@;I3?%dq53iL0H%FEbK}I$yvl;9GqchA? z7zlSC4GtRI9YO|KLoMnwM)jCr(}c-U!zc5tVagXbMz~d5PplYDR#=bIy4Dk!pB~@) z@)zUQBL|OtBqBDe)#`M~(d>N1lVdwwayww$$W+B0;}|xHM-d6H@S3}b2r9{d1cg$_ z()c_(Pfs?*q%dXH7tMZ_hGGE`D|6l#&C0JS=S*ASzS|gn%Ndg0vP*uO6Uf1qj<{jARiBX(74I6O?IoU(&cI zJxYTvJBpHuW?hlrH)+114dpf73>z@afrVUD85c=sIg1bh+>>*|5Xo@oR6RH%XJdL1 z;|oHiu+6m^fK6k2YAc%vxVtJL(pV`Vg$AdSNfw%+<>m&$JYzrFflg^M+gTx6z?LEq z0_ik;T~&%B0F2*g~DnP40C`InfvW&=kIP!BHm189pTN&H2WP9+KCi0{rx=}nJq7f?j(c- z$?0MqFB_;)|`>_7U>%F~NS103pK@kWsD{^_S z|Jb98&wk?aZ~f|(pZ&z)BiG{o-SuC5{`fDx@Y-{KeSCV#PLAI9$l(W{)JNWbbp851 zxq0E`)$8|GORS!IasAb=jjx>)?TU2U<*9XXaO)xTIv$_k_0t$E-9^l6%s5;0+I1jZ zYo(V?MF57O21sQDC4|O0O=Jt&6f$W0<=HUoKrcg!R ztwE1)^Nj2AH0BSur&EyEDJG+3h%d4Y4|hf?Pm|jNcnA^h7n;x=b^yY|(41$I2o!drWMUWnzO68O*0~bBt@Faf|jqf9D2?*TfTQpIC z2n8a(lR&tKVpr~_5o+FkgbjhFy-X8mlalN>Fb858`qhR55kV~i6|V;7cnpyZa8Zgv z$cXT;h-N&RG$H{S%S%X5nY5eK3u{q!jDQqP0*;7!#4bb-Wp7rr^Ko2<&${{LgZ(;A zi1Crx^(&9P@BYc@*;iju=UB(7PHNClclUhf+%q0|h z>EfTfofh+u(|`;IkBosxQWasI*8~Ox8r~HAB85S47xzI%_|6L|k8Y#yL+Vu*p+i7)tGOf-*9zJsG%8iHW z+3?&S{rU5M{5kvD{cgnhcD<5uakRK}^~T<{Lv|6PpP$tA@lv&1{@6#K_@$ryz%TyH z;)(b7M~8wsF1K_BLZE(O93;CN$lC#x`Nl2jn;(1E)_VfK@mqd)(|`WZt?!`#c;Eu` zU_Dsh!}^x*!tQD-jb$oQ_GbOngZ(ETzV@k)UHR-M%QH{u%`2Oed(-Wg&R%?Z+KlDk zVD`|B%TIoI|H%*QWA8s-%+62G#;-iT{M?@$|Hr>t-&sh(m8&*63+!O+fTF0=8$-|=Ieh;|0Rd~1?i~y57 z;uf%=MbyYf11@9u75zTIB082`5;~lkmyE`2O=nw}yMT9|5EkPC} zu1!V@n#7bS2e2MyzW@Lr07*naR6>C>Ea-xa5g0AT3Dw;zi*!2g)}zki>SsTD_~eI% z;bc9mj!({g>#-i<^*eriAL~IjBew&_N!%DF?!|*v;WbsOt-wlZO&a@Xa%Cj)gNQWV ze|qF((^U`|0oK4`qnn5_wae){nK<8(;~XaOx_Qzwi=&joX3@pW?UEhtlnnlAjD_|) zvLo(LlKY(xClyC^7sFe!RU13Q{Dy7OY&6-BpddT{XiTMGN`w=LX0<12c&V8=*0vLywKuF>kgxL;=tlu&~ zB)pLaa|o4EAHQ%S&SF$Du_F2Z5-s*SjSC`!(*V#~+&Ei`LLRx2Mx`+GsScAkg~i4x zCO4~6MEjX^ol1wZ;pCM&tMzGFlv_`H@Pki&bkF@u|L4EC{lERI;qxz^eg5T>mtQ~M z4rdYLeAXXcnmu&5f4Eqm*70QQt=qeF@bE_;{;^;G)KkCmsYjoAa{t=ltaP-9sS2{* zYP7L-Gx`4NfHqC|4sh;mL3D2^m*CyUkUz@nJplB-zt{c|F905Vd)`CqTQ067p~|wD z&97ZreBj3Zvmfj~{$cyzo_|++JEfv{SSVq`^b~yBahT;m)8=*{gc%f zzq0!Gf2#lXKWzW-bDOjE{s%sASF!gd_LdP(z%;!b+OrOCN1{JGQT$h4EQve)rd%nNa;)IyO0otl+@mNav|K) zc-s6u{*ED z$qBa;hatuhwZgn-aF~jl*c7!UPRagv^U6_5*JP$7)AmSG0MK-eNy}jnqPwzAttjqmdr}AX&P~&M+ zn_c(a+=)ywUkwGrTr4U45K0s7$A#FUfNW`RiQ^IJ;0gpIJRMMlq6D0hV+{#7%s_^F zOZD;~A}qoxo08Yu3_B{CfbK0%Buf>~Nb+DNjR0Xe^$IPjfcR9W?MQ)Dk7??HbvvqA zH|zRY-*v?%uI?UhUb=&+9^AZo{X-vp?B|}{TD;LfTzy93) zKl<}~U-Y6M2lt$K$7r_ii*^5Ydjo8gityyB8>_yWY_5qk+%e>(m4GTNcC!PZ4?j)72ztH zoo;hW&P|b`lp0>$+-o-d^E9Sr3RtKr;Wd1$W^NSG5S2Fdn%rug1W6Ck2(NC$E|Ma( zsqSV@QcKA*(>)k)c&*{)%?PJsMA&38g_&tl5l&;RL-oNBbPo?NqSC2LpqdSit=Z(} z7Q(QIK?GuM36Ikeslpk8)Mv~JdqtqS(-067goyS%O5tA7qlFwEu$vc&ZazP{^3bBp z9$6ecF~4~TU&8;tKm7N9_CNpcae9t^u^9%2t8@{8a1jk=R(I+{Nb#J97=wkNN}&^g zXRdBiA48}`C^UB}s4Ar}O`jx#?E(r)QI#mtzNA{TBO0?T30FDoh$OA$EG7%Yq!R5q z3ln_2eWv~5c^F*`2~!68}a%NB_8|XD_P!|l_$iTo+#hW1?cTX>x*AD7pHotKceZl^W^NuqO0Gn#H`suoEmVRBd|a7ryjfdnNP*hV!K-6wcCEYnX0+k!R2c`I~*>`;e0wh+rD^vIvaw-UZGI5 zlf79#>q_4{Y>;hP^kvq$gk~g@cGYTRrmT`G9w=QAWhlFSfhr2LNYPm+92VhZ7x312 z11SXGYAwH!6C}@%9L7My>pWr0vMka(c4+4q%%~E>s)E=Y^V#^IS zV{En&2{60#y-Vc}2}>C+FClIdMcRt2a4sYRto79H93)-ExAOAb!LdWtMXv~&^#ytU%B zd9#Qr!vkQ~6&2Kolo?1i??nqmlx_h=DP0;EnLCiVzfn|M<2=M^1{a8;^~JM1hzO6U zZh{b%Oh>5UmNL>*6hvCtQ^CeTVOT^p8*2rj(2|J+?iDt*OkBwUOtnBnK8Heg_lRna z*m(gpS_%x|7G}{VHrbb+3=y?jc$A`U@X6~i+2ksmywV|Bs7kmw>U!klHn_P{>6KAI z!fPdn5Jyb45+v2l*eO({FCnxVa6mXQM2nUVGmS-&y?J+ZN&5x0yL9!S@Ag#XgNsK# zJij@IIsD77edYiDAOHE?&wW+cMJJ8iWk56N0YxD|gcR|dmGsG|mwY@BqS9!PC4V)N z^~Aye*cWtJQYs4p*S<6jbrIJcKPxp4q^Niv>XOb*iRP(VkUXz8CrU;ZJ5f+WBf^Uo z5TXhZm{Y_8gr`_S0YXaGM@02uswPfQeX`a0dRVTCh-kMxU!&H&qf64OL_Ih-oL@SG z#%f)Mmru{1dvS3%fBeUvx$)G8)**Y2SN`OS|L(v2@8b`@R=8j37zc_LO5g9vOk9Qg zMfSSIL;L0O!TiRh!=o$xyenPb&*y~HG3N9A@%}j7Kii(%zw;Mgdi|>}jO!uV{N%e{ z-vI4)nCqRuz2D7izUT1vdv1e%Q0tpM_*(_~@6LYv`geU$06bXlnUyEzOh9N?M2mJE zM3N*Nk$#jBZt&CrIw*Owj4*US5uth*uOIAx=I0MT^^x=AGrfPjdHGnjbu$_E%i*;v z2mRt~GxnEfb-md>e+R44M0Br2M48WJ)^H4KRAV7sjoyMe&XjfDz zr4wq1b0(Yty=3H2ddP%3DFFg5Y<}l1i10x6gjvu9-D29gd^B`PRVE=j(w9)daH|4M zcpDK`4Hh{tKy^1uVL-C*-96)j0FZRAs1f1JbkS*|Asp_mDLM6sDcyw<)A`P82w@(U zqkocOlZAT<3IK1C0rOOs5(oiGl&=zsumTtohb^2iU_qR8^I@H*7e!Tt{z1DusbuVW>eBU zS0WX&$vCHKB1(#?xf`58WpmUiT8fHS4`8wiqANuxikt@duKomomady#K_YN-pt?`B zHZHxWN`dQJY_lBg7J2um_*d@?SzqaG$=yglQG0A%uFExn7dV{NaR#Ylzrj6goTezswtZC zpzApAuvake_KuEXHt!dESC6hxbuY?e{k8X%E6`Co`TSR3{Of=7ukL>CbDORBD5aEe zgSjWr?i4_T6Y!*fH8FbIQOL6qJM#mg0qxMtFqT<|u6?!JuIZEt5Rd{TBveQ#so~BE z)sFlxloqipsuaLMl@<;Xwfmu*P9~YX!iY z-mP-IqY$onHGSjzvOV|>`0>36!r$sPy%#^jTUi%n;`dn?yyNTQjeo>70PpmAz>Rw= zD{-R*D`E^EYF%&ZYOwX-+rhV^kLES)E3tF92#5oLE)b;A8TygOuK&g_EI#~XJ-xI3 z+ABC-bL-L1adozT<>mrBy?bA;91ym7`5u-dr08Dn@P59h^B#h3*1^MKDtp~*Z_a`# zMtBq{(=_@pdG#QKETvQFL>S^IfCN;Ert;A|s(UaS_-<*i7v9=sd4CT$BlB#W?G~>T zftw1<4dG$U&nIcn>1_ftlHqSKvM)F!9-hbwL?otfRYfxhhodEjQBu|`K-vpN^$hq7 zGln&9a%&o5LI`9pSduAUO%nqs60t z@DIBWe=w%;bUEBVJDJb=SPk3P?!=wfW3`ISs9~H8wU!7DLm4J911v`i?j|Iap#YmE z8v&|70^u!eK|<2vlRK_|Myrt&1=?gr1Sle%Qd6*vX7K6ZDX++$y=umXWhPek7n8`I z(PLrlRenib(OUN*?nR z<|fS_((FeC0gDidQqbu64S2RX50Mo6LQTS(03q}ii(t**wK)QbYC2tB6cH47gs3*hqfYY6 z65*+Yh@IjY;i5uOFoU-tRG0w_=J-Xdn(R{k5GD~Q8YFC@B3V!UY#vH>FpZb*z4n)1 z`lE%EhaZ2o%;bg7e?g3LF&N5h15a7O1^*F%!>wsZ(!MjtKosO)Ddo!u6iSz~04Nm~ z3YPE-S}=hu(Hrlb`Fs&2g<2GXw1SPy1a6yY)JW-3yLY22)4vw6|7*=_+@%=hV2cWHld`O&9~Mn?LcfpZu-QoUT@P|N1L)%)+~lAKQt50s#y*g^p z1xEVY0R8zUKfROdyWH6K8rJwu+w*p9>pT5A--A*l-t39*qz2&oTMr6=2kX7LcDF_V zZwOt|nGP2^#5A4+36>&7ukY{w<|nTG`lsdEj-qSo@76t%dDF)mZBhSsO)sMn3tIhzBQ}8&SDIkCRCS5X0&u& z2XPZ~RF>BGS9C3a2caQK1)CEIB19AVWoVCU#%w0{IE_2t85-ry%k*qXn5 zT7`x?9Y_Ql?vbIi36~faVIfRKZlZk;b4D7Q1U)2j3>c(h*N08Z(RR!9fLR~}$;|*g znZQY84hEU5ZEq>Ud0A-~C-ONdkA<6gI1R9Lh;g_Sus`Y_`ltWk;Mu1zO{b^J`>XR= zr!m-c?|!{|&(Al$8MHdgVjQ#vY?L~2G6`pUFK6VA4QP7%fDDK@J$)F_dZk2ZJyt3i zppzmAbR)ee$xuKkMTrOvD0K%D-%wYoWGFSNG)@w7iV5rN)dKs6uF49Yh{@Up!dcIDGi}^7Nj# zySbw|_5?F8nLs23C}BT$R#ZFVrJW)?^)|Z?D2Ke6TZ=A(~oqUz!7$A9V5 zkN?aQx4-%oyLYa-2vQglY?MgPUV=qr?Vpz|>;QkXujTi%zW$zs_X2)~d+Wq{d)GHyX&W@SxUXf))Im@Q*TRDLU%K-5KGQ$-)beCG ze(ja+-g;bOh_J)5_t=eoZ@<2Jd;RKbSDt))apR%0S8h*tjEqx>`ru4pvRriHLSZi@JD}yor_~N`yyk{G>1jI_aDDo(Ced6t*U5 z-k!6wM|#!=fIEQj$os#lQXBX|dawW_)1@0|o$F@ZT`&9uBaJD;TWAyV_pR~%vqdet zj&!pfY36=;>-0thP6|*308|G&qitb^c83k59w2tm-%ae-`=9uUQpdCVtK;QzanMi8?R0$Nuif+0b8JQpV5~k)GMTv1CK;#Vp1vsD zC29cyO*Tbi*n1*5$=w%JBwxRsQi)Kr5=cML*3t$$Woghf+TmbGxJPTYE416S|5bP7 z;6z&~Du8rJ;b~=2%g9aEQT}#=_?ia$X`KmV@Ys&Ut-EyLaFQyTICa-dhU^jy0@18< zK~3z{5W6(8PNs=v)184f4v|!9H;r(fl5DP}va7^Bj2D&MZg*#ike^#pUhdv(joOqW z$d+#_l9hlUZCac5qHVvoUUiy{>h8nAv#Fm+7;LGp>1>%(6hzP|S}gOCBRoPR{gFac zBZ5L#COa<2X;RuL5@?=CS)IFAQBpPB8l^1>{IyB|=^_QA8wH{iElyfgikQ`ky6EJ| zThGYe{44+Re_5a2+YHk0_sjeDZL~CPWwz3j$&^Xesh)2axpebHlGzv@V5UYu+o%W8 zit8Mq1dtA@InYY4UP_@jge=;uQL_XlD4I@W5?~W_?2bkjAX>D$h?ACpweK}r+8_d= z;#T2?04+qsta`0c9U>I$lFL2DP?uTu)RozV-1p-81k0B;-K>@UE5OX4)Z0Nce^Ppe`lKM1W=_Y>yfNA&A=*2(c4^W8 z5`d8=RWNzBx}1(LCihTtI-6zrJLjliIkY^j2gbdi>_B+WvJWaa^T1RbPO zD20-=?AEzgq$I-uv`LPrMDpm{0c}0g*6S8XNoU7M3VRZ5O1TK*Y9=hAxy6xIQP}nV z^L@1S$R#2~TK+21np0Zg(A2od*$-7!i#pM|%tFae)qWIG0^a1$Y&)vcl7*2+0H!LL z5!NV7bmY!#We((G27s*8!wCl%Y_)5)jd$ZG$s^K+6+o7(dE#W07+PpUZlXu*#(#l^ zjhnYWvQiZxFHY{P^E^dE@W@2sEC-kc3Niq$O-ZP`jyVJ3+6f9N3^S(?G`El_MNl%+ zz#CO6JK+`{K^4_5oD}IxzxHwT${vqB@?)2u`shoa|C8;@uW;ht!MBB^?6>WM7bT2YooLu432S_bXh#EMYhsUfCB?7r;jlur_( zrPEHNhzLq2ng_lmvxSIC7L?g>usjqXL;$x82vnH6*Qg#sbOlyX0gGXn1az>P7ZIY= zZJm}&uQ5AV0IWmp=ku%kSEkLDs{8N18HbB{vN`+9uRix5|7?`*eNTL#-!CWI;o37# zKl4kUUETi5=?kwFnT2S$c{?j|(8?6syd$@!cWeC+K!zWJO?)fsd!PWl=@Y*lHR$bH z4+?;HdHpRr@88|^COh*DE)W@v(7f9fbtwmX-OoNf`{_?kv7H~^_m}Q1@10m*=8xQ% z-8$OL%J9OS`Ild{$K3^2V>FxV85Tw zd^E4_+cAa!GzmN`a53qWWDu&!f(iNm*?ZGwOS0=aY^}Xd-h1DBHFb4WbvJsz&;S8Y z-~@uANCKh;Tc#OyNMXqe%aZ-gf6Jd7{^oEvEZHH+a)=_$nIZuZGeGo+9;$14(;YI; z-u|$5-fCiQvWF_54-nneuil+9Pu^U6?=_$yiO7&qcULwi3S+^mGk+gO%THVEd^0-d z)_Lh}g~NMV3gSeyt0C7#M)W!?qU2^sU#Z4CgXkTJoaX#5ab$Y8OfweKcOL*8`{#nu zl8i{sV6c8i&Oece$js~=K=h~>D2WHf`xp5bqSmJ}fFr`#VhS?TJ;Qv&v^~D~8^8SI zKl&%jzP1jV*vj-yjIC7edz&N((6(lShejVpt{al%x z6i-YjN~vDT>bj4|McIcUr2uL$;qJ+#mq?(r73L(PG_c8LY_`Yf-V`tf?ydI~rq)~x ze!UeCY-WVJiDe`#Z?hD*T!QX5or@kL;(k=W(_6EY!kbj9TiDo*BGkLPl_r_WSJ2z` zis^;fAilU{05l79qsJz^hi6f;e(HT{OH-okDq(- z{M)xJhcTy2csL?j1c^e z7&AJjjEIOnw4sk(B*xu(0({4< zxJvJrp|Bo~}8Z+CI zj~xAj&t3S;&-Bw<`TWiCmE(>!eBkQh1K0D)Be6X{`Qq1y=U(^o^@U&hnc)-9_Vd%t ztG70{kC79`@V00dS6sA`NV;P)S-Kw#?ZP6m_h}l>HhmmhHV|M>B;DH3j06KBla#L_ zB70~W9#&**-6u1OzMu}i(mW+_ENH~aVPcWXfg;xwAQWS_$1EHaonj`E6l;Yu8n{n( z6?Tyl06ojgamo-`--XOm2y}10F4A#jf$63iGYbnM3kRXJ+uU=9ka{dKgNX>aI~G1c zaWi0!&feI(V{tGY{pPQI>Yx6L&Ar$D;t&34b?t^7dOCKDzuzQ{M#@r8PzMa2M}Y(__-(i#BJixE^Y zv~KdCoGAxp+5+m=>d1qX#fmhwVkS6E+;q)dpaMYP$z|KxM8Th_Hv8d$COT3POMOt{ z+L`Q+tO85mydr@U1Y|m?dO*V=?0Nfyq3I#g^;)%#VQTXt69^TYMWiYoNK`y=R)Jz^ zu=&ZAq;Q{mA#){jo96>4OCrr-XtrEK{&KC}2WsZ))Q;*~Xeb8q_`3KZ??O(Pwea!OSv!ld;YoU?rBaaE!524 z`>{nf-2a>3`SbS%LHxwxmLKthzthL(t)2K8UA0tTH4c@qJi-YYeFWvjyKVQ6h>KO;0`RRktd~&;6Z(e}1BgJDRxpZMU1TMC=g~Y>r*{l+WbMyNVqV1LRGEU9_cKKdGX}QO!b2c zR`nXW&_Geg42^Ut!U55!0yDR#I?o9;l;_mBsLG6%v)&Yd2>lu-WDlhjX9C8IERZT+ ztC{&D2dX0fRL(}aIbeO3)h3cQ9|u(zGpB8Kv{X!lwgHOY=0^93Zbk%=9iXA#LmqddNMkggO1~Ss=gT=kCy}Ee${N^8hMAIrTP>jzMM-qOB%Z#U`t1;Bf@ z-k$*cRo0CE-4;H4{Fz68`Ij)n^uqJ~-nk!M~8qc?{-8p;t4cm^n-JypqhsEU!wqR!Z!pGeX>&akPHeWRh#->l39eT93 z$P5~qJBy;9q4dj!f*`0yn8W0+SWQ;}eGM%rxIH5n$f(6D@3%}9)g)It*zVK%Igbm- z5`WUN$IQurAiEx2jl4+om~R-I>AFTu9n|K7g=~eoTJ^$enK3s40rbo%OsTc%8mlzv z)eHypx@rlk1`v(`6Ve!{BU|djDH7AzPM3f0AN=&M|He!I;a}bSy+673%b$x6fAIM9 z?)K)*-R--+-QoBw*BeYdr_mxjVZCQe?421HN2U;J=9CslOE|2}15TSijTq^c<`UJS z<|91`W}CIYYS62F==4GIiV``{6k`k$=Cg~FF%rsoz(Bd+NZ=`G$XV|>t20TWn$Vg7 z(A=#Nv#%9h$5W3p8TOks*F>pDfRG*-vH#7Y3GFdD6EySIoTOpOUj1WIJ6NwP$R<~M zw|x#l@tVXX6OD~F=fFZz0i9j|$dolIYa0u1hiV7gGwo*`fSgMKFpKwAaGNTKO}V@! zrO29Rq{atGq#5$QRT7AtRkX>JGNMP9QbOB6oqm~E>e>kL(};+gWYk6`X?p)z`&N@5 z1Hp*M0359iidG;<_j;4**~8q377?Vk)|xj2Hsc1d{=mhni>Dub;VWOkdK#K}YZ448 zfvccCv-S*7L$=EBN|p~#BGb)LvJK7T2uoiKbVDNtvxS@21)j69zq$oRE1|cp%bZOE zuoobqHawN~27u`GeL*=y)ptRX$SEqZPUSR#$e03=IYn+KOc7%+EVn&+y89fMtuTt5 z*1KkHLio^p7}#yxp6;g2>geL5S8hz_=VzPE(T!^tpMB!>S6;%Jj>*0R)Kd=D!o_p^H z{Qlefe%|t<-Y)>&ll8D)0PoEbe!%Khl%6a&b=)z9QnZz8D_}Le!j`w zWUHYaEotBsVQCFu6v~HOHR1GRX3vy0KI?uPIe{UgPmy?G z%s!QQi%2$1Gc#{iWFClj0e~WSBorGY?Es;^zEN04cjhislm&G|M1M&pkqYXn{wWhd zqxCe+DIyqwDG-s;4=62IUD=ddX=b1#n#i0wv3Cc_=m8L!Mo5WBWg9E?49qbs`p(Bo zpZ(h(`^>NZ`Tz1i$2Y$I!TzL^Mm94C$@0(fRt_`( z-K@1b@@8Fa>A`4}C8hfPD%o@jtXb$}8HMKsTS=G~Cm$$ZmlBTR%C&6a|#V-Rd-dl08R(lvjbVR zDoys9P7_zm%_5T~P6`IInUg^=Zr~=-NCF7B8EByqikZd>AR|IfLm5fQ66VUS6v!1p z#bj2^poneC)i!iaGrBlP$aI&kfCM9_?q>NS7*@!^Rl`#9~s^wsTe{ps}8moUb`<%>Dx@a*-c z{+qwQJ+$?$+xK4lcAjijvFxX7Ou+@0myg0Ck=Cqlw>aC1(h~&-+Zz_X2?j!7LJBQl5TBKOKxV48}@le>Q+|U86d1cVVUqi z_VhC^N3i~W7;Baqk3?qADAsXuQ)-f~>x-I#)DoFxjFc}S?~z)VI0 za_>xvz4=ddxTA&-)O$!q;kW4L<5@iZn~Kj~qLD2qhz7DQ_Rj?1(Ok{M>9-@7+ZZ50v4lAT8J` z150HX4c=UOfa0Tq-oO-DYs`Oz(D&ac*^DsbTfwZEJ&%asrL5hEpeqtX#fSif<<$PLf ze{+`;5r|T_qh1OXIAA*9IX94drW)v_84&+o!z;-_vaTk&-EV;-jg0#u0z<;~RZN8g zaK39*$(In`pH&W!z#g*q5f@GsYLzm&fGRLHL)3OvZ76rGWHOUr^Z?7b-)Kbku1E>W zX+}z76&_5d5sN`}Q-H%_SPkv+^1>Up@13phKKsE>yma%GvzK1$V?<7hL(o`=UUhrU zo>ZBk0#T_2q6Q_IC_jKwwAZjJt<4Uh^X6xbqKaKM|1Ij!C`wKVB>Gx0kr|U3T9rXn zO$2lZbDlh9CVI;DwGyY*`v(!pNRT;pjN)hk0FyZdKpzaQ-Rw+^yIr4had6l%CdYPg zuv%Q2PEU_-A0MtREEX4!j@sQ@Z>%1<_{@i&JbC4{yt6grxZTncuo#h3kB(BHytNz4 z`|Dj^Kdw0E;lOooemCzI0PpDYyj>gO!P-D?+j>hM%h4XaeDKUutyb}`1Q?i|MBk6pUboEM@v6gjy>B`m!JCY{{H61rT)glB1Qv2%sH*X@d^n>W_HyThz!x20EmWzJ^D+CLySc9 zKxXy~1d=(WP3YbaKKR&`8&BW){Fg5H;_83@5BZs&K0mp6dh^!#&ATfg?j7Ug6q{}I z(K6X5Oe1?Yq-SRzF^=dROi11U!hvQK5gjwu73?#c@tk_UtX{9d1{1%cej!nas)>0r zbEf3-#98WF4N;38lz3V$D!K{C{he}WAC*kz(+ zUV0(DUt3reoXELglenC9NF&@^5#Z%~<<2HUpwx9?&8UjK>h|2nDg)&#S`iItNl8sW zw@4tvX!R`tiGPxrF-{Sgy(|K9iVXBI5Xf;tk1EBOL{4KjcxDn|gTdXMJtlE1x9hbh zmq!=I*p9hdEDtVTzj1o&_VK-27cV|~c=^(`D;I9ReDl)d*RFl!@tZHb=;vX??sTm# zNCK(Ei=SLP^PsICQ{3{71O0avAMU#+c)tL6;Ggivdi@ATs~D*Rjvl+dc>LP5Y{R9) z!^dw-k6a02{KgCG-~Ph(OW*3pJGlFS4{e!aw#TnL@sIyb|KMZe@#*PvH@C0f8+L&) zwkM~(pK-NZUbzI1Ob&~opKs!1ZLqdlhGkCE5H{_0hBWiBZ$UIV-2>t744An!27-ho zEiDnzyTQwX)6L9$;cN_cv)0Vj3MJWEOBN@m7(P~1A!f3?yOY&&n`!oQkF88U#a)ha z11c(XBznrxQU)Al@E<8^ftsa7;SE%05VNyquPT6kCU@JInDD8 zrZ6P?gmKE=Nf-jtXuAnxg$gS*DUobte=>u16zxPkQ@6b*xQ@1jjA+H0=R9%YpyUSb zr#j`wSuQ>0bVFxk6tkRW%`~}DV_p4P07fsEtH5A01w-t&y^-FGbN;Ug;roXJX)ZoK z6bF{o^k-{cU05?9obI*{v(332KyxKjpbVI-a)l{h8S~=LWHp}E9wx0>QN47sYvjRw z$3Oeb0=GS5J!cL*08o<_AVoFTd$1ZsReK+4QC>+JK1e8n%%Fs=S*}18b5K>VXi1oD zF^Ni?gDYMlgP@^t_UxxKWtcm^+lVU91nuR)in=~^R0YN%eUBR>W8pIfXsPQ8yinT^d~IX|Y0 zKhhoZVOT#VALF~$5B*&Hf&cj~ExzgBKj8(yJH6hkitxv3*>X6zeytxK@MyKXa+wz| zOyhR^=5x~@d};f|7xLaN<^0&JEruAAj~-n6Cx5s7{6}%RK6~!w_SHK+j?->8oo~m@ z84L%PA8pGaBU)?B9Pi!5F1T3Oa@h6V(wDItqfY~uFzcKQW`hZ363l5bQ98h8o*kJz zEKKrWF@FZ98=K_}{mea)=|Uc+G%jm|GEm)+F{AOBudkflN)DJW(-{>>V<7JxdEtu% zq4MDVj_FbD*eO`3jsS8BBg*s>oCUhG)2PCwBKPczy_Jx-J#q~~%9k7f3KIva$KHdP z(G$^~hK@0(9kTVsc=OIC?p^yYKX>t0f8o}h*Y3UY3X^?1_Ph6Ra?bTG$DK_hqvvj7 z4<{@p_Q`fTP8vA`MeG*;p*b9ZC}$cSBT=6v@O2U?Q-2a{HC3SjZfW<8eKZP?5#*kr z0uYvzOF95aEYyXyG94l->$mJCAv7LFNAdOPUVQ0@khIs$X$9K%@L5Bi;{PbAR_%WR zWNTiS8YLpmNCa~Wx5%dCYMkZQkirnqS=rL0|#q+=8SqpZif0? z1T8WHSyco~@?STfx%(DH&>;Y?iA1T)v)rN5d=^;i@U=GMNX+{AqWkN04VLD`IwPwJ z6=%*E*%N7L5h@EL4?sjpud**$2vkv9H4#MUnWYk$5)Y`ZHjC&4NLQp?NnyRlEYVe) zI1HKHr16Pp3x^DOhNjZyU}b|Z4=x{m`73|)_(wkRv!DL#m;dm$`^lMOF-`#wBbmbS zz?^_aKn7N-;LOXZ$0Xk+Spsa8F2(;UD_2Q9w4di%pR;8!5{A<1I9YKJw5sX`(~C>3 zE{|Yjl|9ffB-0XIynm7bH%{ZkDF~C56MCwg%1EQ-laFmA}!a?b(a3-~0CMfxf+a z8k-5*ZI0t|b+9-(#E@g3tg)}x{p6f5Uo8To!GRn%XPLRMW%LOaMxzY%!01y>vilL+ zo4t3zzQ&g9iEcD&7B(fa*@93)RVK2W55?L=kx4ScX8U>d`IMI=><46`XJm~Bdd2RP zTb~+Vb1|TnKju6o8HDSes3axU@P9U)QSd+qdRW2FnmI@SkfA;w5E*)jKt!Z;&wDXI zsP_uxD;4270X@kSk$qyc5!`O#`r)(x?4Mrvxu1FYjaQCu-dggrnX8*Z-+Yk~)_hpB)#{R4`<;LLe?0!!hd%xbzw*sL`@^=|5JR83x(fTj znYi|J7T$-^Et}{}b563{Y(a=MBn5{yB#9~#fH$jC7;~N&L?X(0N&^~tx;r!`l7Kt+ zyn0fJ%aX$wk=c9K)q{>cO}U#eB|6Z-W@*V_OvOH&`UqK(G6Q2<95nYdAea%$grei)o-1C`CHp5FFk$j$tR!Lot;1O)FY2RbK}-4FYWHE zd+!+&dd}zhAzb`%>hb?%uJ3p9-s;cauJyg2|NR2sJzwwDhIq)(GSD40J9H?`zNG}5d**mgwLzT=_+fHnQsP~&00)N=wnBK%G0_F(e%66KQ!eVA3 z&2s>aBLc{6__(!?KKZeK`p+)^^hdt=wLh`N(b1Jh&ra97+qZCXZl@cYwz1t>c1aGH z;S9vY-45e~KpNO6vvCmC42{JhDOLmE27rxl9j|i52eSSwRiv2oOc>pa&b{NLiAR*; ze3J%im2)L^14+6mkz-9qr6^eku$4qBI za|X>SN}4T^vCBGAooqQf6=cU;*J#FrS(H3$20Wl(CUzteLmLQoM1va`*)h)v;w-=FOXC-NjN%_`91*gq)AgU`5kP={k zo*{xz71JemQHq%cH}ma{3eT+)u)b@>@;9^U{u^ayscVS)yQ6stW5pp-ULLCsqgnQ{ zqEcd)^3XD)Er+tAGRq#r&<HUi7)Tdt zPBTw~S`V2pBaE4H;}=OPL6+T5YhZd)xnm$&YPk`n~==%-C{lMU) z0*OOv?g~~*eZhzah5ace`Z)IOE_aCua7KibGf1#mL|1Y}pGKP+=skC|*4jcle7!wy z%VC%jv0KkRi?0Q_XFw>q+Ut55q-e)BxI z%WZphyu10v?#@X++X(m1_w#}@FlP4UrAu~rTqdy&ffUOs|V@R`F4AH z+S1~DgK^uIt9I#doYuw`B&Xhw&j_N)$? zjv~b9kVB<-&?-EXAfzVlGDwf{6Ce82KmQ+gw)@(b{_yfsA6s01;`rXZ^SgKA?rEN! z*)-+(hS3d)Y0Q`yQ|pP{h~30ql^!Xd6wqJ;>1q+9%Co{`)g~7y0<2Cj07Wn9;1%Ir z){2F$SZ7V9@@C1T&xi+^U1SRuam!G^+V&%zxpkk_b|qlVy}4C;7y!{|bh@mb@_zk* zYLxG-_vNoCLWL@mkc|;*Oi*veW(s%N2gw`M04SY0XMjhgeR1~ZF&$MjHbi#zW`h}n zqM*sVw74zWwwcIeGWHckLLs{hA?V1MRlfz4>v!~)ic1tq@lDJUZHP~#0aNY=RVp!A z?0kR-M&`b~EeS;pv@i!@pbE1j17*n?*)x-Hqaj&Fuc}b0qyZ@qFNE#2jYWUo8|los z9C{U01~zNf&{O9vNVk?G;oI%DBe}4HgTqUgKNu%xU;MxR*Rx%G^s}G4cjt}W$%$A> z?yf|UH{1KvXQ@pHxYh8goO^26r7k_plca@47(kgs(Gd0b^WGvbQxNC;2ky^?js+A> z2q2?m1L}BS7J5gdyppEO(GcJuZGh%U+88Rfz4u9MxWs797mJ0p7MbHX#;zM}w;s{S z!MIwOS!)NEhQl`4ji37HpZ%NvXSmtd-@f?IKl3+Ej^DWR z!Ycz>AhPdU6c6j-_xRKI`;7-`{fPML0e$y>?e%a>0Or5gPXK=W*Sq#1|C$%TPsGW1 z3v23?GV{F_i^&pLEiYYrbo|%{F8$`OEFOEboo((s_lixu?_zy?54+AW#`84>TV1(4 zVa)B+7DLCx?HHRa>20|XvE8PO-o@h&27xv-`krfy*pA5b)gldwDK(}wgIl2oV_H`H zrVzm%_9&Ff2v+akP1fOQgBPy=HcKS43`4UVRO(|#2eU_K_2IHi8#81dUiUvSn4<)k znAtc4>pmXy&tpy%?rK(6yg*K2R(ADbArZ(P*#mQ{T~&nmKQ+PD3zcA?@LX=Dl3zHjynsWPnbG0ncez?sYCYO zGusCQjNV$x8xsuKBGp_7bk_kZ!GkJC(wuOw3PI|}$_JP^<(%gRHp@LJ77d_qW%-E>w%Pt0m(af5BHA{p#x1|e8f}}SGIMa~Q6%(Dk{&P-k zQrNC2-lCIH6nQIy(U>_KiJaezW(%?-nw!LcyHD(Lx2=k)C|8=4FvJu$%M6)FW{83> zYeI9&Rn7*y_={s!U=wtCTywd8T3%)aK`YssKx! zqbxTndD;t~=f3s7KT6PLreDe;Ol3(6=CUJT<|5$DnF{kuLXMWmkospvy=HXp(cLW~ z5aDKMOvAQ!wqa$f!`15G`Uf|6&tCqme|>iM{L=Ls<22?Nt#OJ8NTWmMvzU!~W&_XK zb*AzPq(s2bHNUr4n%xCv4KT}Ym4r8kivs5`xVwf<=AI04o;#UL2*OMu6J-=eGIG+O zBQsMLpDMt1VN=Kh*?<^(8$4BGn)j(2kYh053lGC$F<@4f?>0L&hvhh}0qcpQi%T08 zt6{keKe%$(9yxmP|Ni|WUXDBK7k}>yyR*|9&pf>a{qXREpZxfk+uJYP%rRo>^BmwI zzn{+Yryup_@7a2%an8eX9^Wgx_g0>tf9W5tN%xQYdapLn+qEGcmJ8rrTfBcv8ZTad z?Bvmv{}-RR{OpHU=i8g#dUe3CIp0nvXZ?I_G1+!v-`T;c9W49i+&a1RK+p3X$4P#9 z5z%~z-Xpp^8irH~fYq7`vmBqGwBEt!FynKlfZiO%i~ zk~tcXa5>e#$r4>ApyE#(yucsk(A7jSFES+{&X7LRvNDoFg42vp2U9Mk&^-3Rit;DdD|=p6Fiuq(>KWtmLMg zM+-Aasfq%Fx#Z!2gjpi#t~X!I91U^gK%@L!anUGEZ3Kkz3yD@xlsM|TE;Ojg8WSix z0Hr|{Vl65;NTptx3*1|FCbO&JqDAFnm^L`~qY@cT1#I>N3d}n7c}sFXqtHGORjrbK z=Zrj^8wYa!E6lDkoQDK+*IdEMSp|K}`v)}$Q+YXa{$Pgm1lWAa+--M@)ne)Nqv7DO zr(-kS`{S=2zx2lT^dvJ{Yh4*$-sT>^Q4wI7Z%VAF(i&yzWRQ`N+>?;jnmP_9gDlL- zoD$DeLxISBFdYD?9BiLW7)i0?6doS;oxbi*S-OJEWc;K&Hw$$Hhzur^-VzB%1~5fT z85y}?g5KQPV96fYcT>U_M@O68IlD!-uvi`*9vmGmb2!Mpy79j6!n8(RFn11QvxI4#Yw>&r)IUzF6w+7l`vCp#a<7hOuris7PVRQ4)GpC64j-u+S zqn7C+|MaQuxPxotZOw%=%-ETMFmt%MQ^>iZc=ifrGZ-VHZoRnABFRW7^){+e$xN0; zH`N&oQJIUYP;-DRHZ>8cDBJs9qUwjn?2y?-F^UZ7DRWLAD~Oo$uOU$#cgZm;TOenH z$C#0Z#0=g^d*a%`lh4F!FRov``P4uBN5@yMZ0@~&a_8pe?(IC;4%^ALn^>=HlCyYc zq%qjL?I!j}!UlsSn3m;=W39U1k~y1MhydiA?MtD7M>!L<8D~2S1UH8pArBlN6OJ^v z8w%Y6;`sIkK7<-S|myc89HtbIxepl6y7 z3Z%^KP{f3KxK(IK22gp`!hlw1gJhy}_AsiK)XPO!G5BRKgAiRu&H0*X1osVpuqbg- z;HsLSNdr*i6r`Hg;<9tzIg0-R8CkQ4H+}s{w~VToMR&>+SxVxZoJVv3C~7`RKQk*% z>U?DH^kxf7z}+&tH6vm@u9ruL3pPJ&!?i2-UU{wGIm>YxPj}dlj0CA)6&c1>h@IUP|C)YnT!Xg4OoRwlGGMrn;iT#|UB#5@7hvEp12lNK7m759$F zKtz3$==Wp>(7Tz<3EjboWc28+YBPd?*k&SVVN}jFht?u7?shp%?dTB9>14B9tkSzP zE?&8Eba26XW?No;^6BUP?QiwlW1n`b;Jt6X{?hM%{*9Nu{m4^KUHs6K*FXC4qa#0k z`R4lWc|`(0JnOFriXVtuH^1u-KjQDd)#rb}*L(H3{%X&o3BdaWz(cU!oeSWv|0?g^ zg{oKLa&hp1>)TH~@yKU>?n=9O?}2nX4}_mOx=2*kIZfkAUI7Hr6LG+A}WXoiB=q*ItprC&RD$I zDntn&h$68&X6GLig^>m!oEOYuS^~|*U{qy;N&&ODvvq@TZ(Dh-UCz4mC(@J;CnT!LVf_quR zgQ_Z(f52wmFmp&)Q=%C(in@ItkteffR!l5s46Ypq>}<}T z-m*s|(0gT1aFzmxK21G66YPj`aH>*QKQ9u9KBO-2@*}N~xQe|-RVLUQ{nrcS92;C> z=i1})pPkhPStF**N?40znnr38=a{mp zu}8B8Bb6g;ERbJnwu+iALUdi>?*zyAOJ z!P%=fFW$Iz@tJ3F;N#8L<816xuQT{VZzS*j;+y{e9qap%GQRtl9~AKRUNtTswDoWm z0KoeNz%P!m`nvsoUvO{Vy72g8`OG!{%-^_j@Hag=kmA$qS4A^-z=RMW1jc^ZXli@MXZ zce17gg8PMBy12MuW=W*tdtn(J8sH1bMEL?`biL|c_*jL3LV|mBXRCb}`iir$W~kI+#VVC+ zC!A%igFQ*S@Q=-wn8~D>+W@4hr8|nCuk>IIkkCA{|t> zlx#U^#;ijPH}td!I()ehY9HD#j+^x~UVh}t%2vD8>eef-jxXFoz?TN`pa4xOvSt@n zrBti<3kc7%)GcO?3{-~NEP}5`!zjueO>Kyz%Q8Tve}src;_n$-IS@p0FP{JrVdVzg zO#{UU{o3piT+*Q4Gc(;RdXzmenUQYn+0i2-%nh)%7)%M-mKlht$JB>|Rbt$poc740 zOIOy@_QIvr<%?Hx+}^x*^XZTM;&XrUN7JkKvV-io-Q^g0yt(;jUwirY|8TcEf9hv{ zes$x~m;d4${dn#4nNaz?)_Vv<_qdDqe*pZYY1G5N9+m>&y{!V@mgo5^JmCEaz}vRo z`%3Pw_}2#YW97N>I5}p(4;FAkcFV*xiLM~mmgrN7H#3-&Bc2Zilt8wxx0v9I zAx++$qL0r|sFf9c0%p#p0HZ`tOh}};o11v^A-Xtiwr-RY{Jsn$GJ2HpxTS@fn8C!;H4nE1R}1>`r=B|e^l0bjEG9hNMy5iKRXI@EHsjU+sr)5q-%2plwYB(X+~t} z%T4We)MM%QrRq|H7nJ5~C_@@Fg__&-+MuevYAa`ECpKRmkqwdZi>6cmr%0%7j*AR>uDMILIW z%AmF(kmcpCPb!KUkw$ASsfM+t0aR5EI&*IOvPUPz%ErJ*2TL73Us^zg=^>&^_L0#e zGI|)Aa!5+`5wonw3_Qm2cqEp&&UpPp*yW4Iv<}RW~J2+^|)wn(%&!^>T z3Ck;&E*vhFZMg8p%P$S|N1y%p^Z(!P@_6c-t-dq$HMu?6-1)*czV&B+))P;D>QlK{ zZ|)sqHZ1rdx045az4?9G_xjt1<%0n4e5d?jITmjh_~u{w*LVH=2R!Qi0^mJa56p(< zeiObuFG0S;`)Pigi-Xm0`6z$>=}Uj>mwEg2|o3lOY>5fj*Hje#sTZ81_JOpVOm0om`U5DDboSR)ZpaXuMxsRKRQ>0E0#Kd6uGbRz=YCxwH7GJIX4k(gC@QaWBzjE6kQU0C z4_}WMcN05?$1flK#^2dJvKsFlpWQu=dz-eK;`}_%*Bm<&*+*nIIee0!hy^ZK*E})-=ibt#2l!>iR1C%&N z(a)3u2G$vXnG!vnFWr0b#c{pqA1x`^j^9bp?(D-l(Kl$*PNH{}bG0J6`Hqczc&CHxF=az1#o zaYshafNVrGI*81Y761ZhYVFU$zKEXcQda>G!b6#}M7S6xvShh06CfgaX|?+Gzlje& zhV#wtY_~f(wat#_n>angcC-%kDW^&OZuH5fiQ`mV*RoV05K5XY(|QHOQg$ol=wE#3 zQfMZP5(Hy!2sB$F&PRm==?aYXI-xWO0?siU8e1csMvwoeJ1B~N|2s0X}l1Tl)=&bsQJ?-$qWTQ%s<@E zGLaq4Y~woWrL21uKS#a8`3Xe8X0u9$uJx&hHQPBPR>tOzPP+P zvZLYdtvd%9K#sAqdJfhwNu+$Q4_$I>U35Jyos@f0DS)*Lb**Z9rxOHS_MhtgYlCX! zg)G%CA;(<(8WNI}VD2sS;tghYQwgX^oi4mdx}PdY5?#`x`IHgK+(t~j=vKqP#*DOP zz4snD?M54b#lZ0KWIb*A@?c?Z7mh9-ELIo}zWJ3e;B5Qs&;RO6fBgH?Yv*%+|2uCq zeKSt$out6u<0kZ;L(1<&^55$w{9S$hw{ejEuB`9(ZXdKy@lNBkcltQIUjRIy>+RSO z@5aUQHnsov`|9o3QQ!6LcmMg@YI*S!Ppv-t={Q*W@BQia8!sMw>f`56Uh8*mE!KhP z>Al}Q_sujR4z6Dv&rf-B-df9r!LluuF-_R+K;yy@!Ax`+koQ2iRCXl+V1bX!zS$Ve z28Tgp9;;Yd&e@*@787A+O!VjwrG9`=9V4ww#iKA};fO8Ru3kNkbq1%eQ0!$ zWFn?8ScoH>Q_^gLUtV4K+|M6<`e*uX7n^Bw@3fzsVLQh99Gj6Hh$;Jogj=GI-0f`A z8_0t7RalS|rYQ?TQP0(yWl`7@CETn&J)Hp5*@nr7ha#EG)SCKr_4sS-qoAttwkMqM z@FoTg)%91=2Nb1h@4w_`NIBu?b&atXoK@%0YcraO+Y<=}w3Y`-3whOcq!GjG0;PRC}Virb_cZ z%#4^u67G$lh;+kpnTFOJi$!8QU!OHUc;w>Mqem~j^7U^lu-tBUJ))tRTO!P40;@=e zU~}3m`p-Ht!C(_>Q9FT{8BuPY2s+G~PBb&4hig8f-R>@RhDmPGQ{l*}=^}$VbaVNK z9x-+1{C!nEVWC&e-i?d^nniX@U2dsrO!RT;V<<<0%`LLQjRRXCImRw_lW!)=z;cy_X?==dxJ7nI<~ojQTq(sSqGxoqB86-OUW8+i zVKn8;muX36@4cMn2vA-+g@-a>Vs!UFQ2vlKB14{{(R=hskIl%O)Z}E(h=`CKNmfry zUVEWNs8Y8i22BX1sL8m$ePkkf7kp9Q9@PX-#h{{Wy-}>4|DU}#|FtZ=uEW;e=f0ta zo|{dPqBw|?EGe>u*oX`yk&(a&Jp2$K)1T_Gy6 z1ltZ5IL0Mj;>EOM#e29N939St=~3Os}=p`d6v2EeeX@k0Yfv|Z_^4Aw8M=SxSwk<;?>m? zEco!FwHh|b4CmjiS%KC?(|YPwNgsl8YuBc}YtbI5Jt$Bati~S;QuElzol<1juod|f z;Wo-If!4)_5d9dPNmG6|_E%c7t3unsmi(J)dGe-Wdn%HSEr9Z4+e->LN6pj73<2|W znon-|WIvvcIK;*E{TcdKfAD)heD`~w{lXXD{IxGWe)7(}?Ggzdh)qf{6%m|N$)LdM zt=#+vz!ZR7*nCJ(N##*ogf^&W5=0e=1`3>*_H>}&E*gR5B9J)y;LwR0j@&aAKpJ@BQCD-|cKj96)d)@f=x}bmRLiq@a-&LG_a<(3a#vi+yE@N`r z9=!7K;a~X1_WE0=fBet)zyD8=X4d(UX;zH z4^m`x@g;I$1?P)S*y=L27>NnK=&l2=pxP@g%M3|_F#Yp zn<=nXxs}K*{CPr>3}VS*p$=zFGDOp)GcrJJj80jSf>5!lgt)%EpO;tpjW4|PpZ>?S zk$0z?cOLQX6l3D{E{?|-gEaxQ?M_u3N9;3ZNz?(#r{cay#MYE{6jWz5Lp1pV!A6^f zrJsc}ytc%K2;?~t3=8h>`o#sXdG=kXcayV1opRfdHnB-kQRbx3zV%(frju4(iP{te zZP?@aN!W(6d0%Di1x$1i$TO44jCi=e5e1Z8EF2~c808X)G^n>O`CLO{Rp1LH6Efa_?izA(^W?~pZ zu`L@TkzB;KEffV*&jYuJi^N9b<(%y4$rgFoJa*XfV3Yj{pG*-+m~N_i>O0f?lsw8Qq5|EVB%F|BGXp>3Mhpwf(n-J zeyD;(C^nB4ZTM?Z4W0r9OU%>0$DJZfdjYFTRVs-}M4X)fdUA$TuqIe`4v3Zr6PZG6 z8K}yDBReuwHG&8xiWo|!W^D>ia!gSf7jw)FIgfi#*AE}25PQZE-~aaatBTsUtHWF0 z{KmiehyVNI)6FmcxBnHce!gCgdXFMv-LLZx&Ta7vu$_YKmo`UX!efvK5GHOmbF6s2EhPNF~xkgIFEHO8jXnMUVwn z(3b2ZiAfYV{gO^B!cQz#Mu%|5*&c%p{S%Ghg@d4seXD=cyJM_L{X+i%fYt*nRX{`o z%POa$+5nw^a@)4pRI0aL!>@ni;+2P2k*DK6Ps~}kJ?VDW-HzRUMj$K1m=$wGap{ly z9eGr<+iPnPV@j|m9nOMw8hF&b_FQJigJJ5El_Xl1 zq?fZMi5|${yh>&7ruMK}Gn}>azAxF$RkV0_9q+Q?517nCca;_tuT$wsmw+&}g=cy` zoFCT@QFc;Tr9<<$w3xfuSeLib~$4TKbB?Mn9|9^_E0 znllh3#kNt*49!B3RZ{iW6N_^iI2fZ;oVBx9r%`uPh|7of>ol(NaQgn+x9`4N5@J03 zm0!Kmy+8VUfA_0@{x=`|*`a%j zrGzaL5+)O&QK)lf&{L_dRk2_<3do>JRgf0Ao?YK#W%Pzv6JoBaBavy9s4Wk3lNe_3 z7rD>cr&u6W;wV)~v{$LfJdcLm$(UGf;ayQeMlygjrpIilrFkU;Iuk&U*0jR)sW|LA zMP)6fwyvBnWMlD%Hym@Bg{4-}}PrSv=m{?YDP29d&od+q>9zR(q6c z%p~J<%F~{+#Oa9%i_QO5GekhSda!J>?ctX+pal$Va%xwU#FkbpkfM*clZ$cT2K zanq8msO`Z~=PuI-v+LwVHv=5r?CvSf8LlCR$(hydf|lLhtfqm8C8}7bB~?H&jlR2; zq$e5~%2bBZx>wtHx~c@BS>*y;h?Q3riaf`i%8u0Vgo@=O2n zpZw#?d)L4ASN<9vT(G82F5oi?L83}Uk(%`KGiD!BMnsE#VBwAets;pzNQ zWDhgDh4f4YB8NCdsCw#RzEK2%1h{49MIs7M^)OYnLqRGu#d64r#BGa!<}3gbBc)(* zXBEj}Nhns0fhlPm@AhEM(~QLBgUix>x;;MJ4nO$r4>)SudG+S?gRg!05C7KxdGnpO zf90?KWnC{g{3m!*{sb?k=khl81NZh1-5+ zXZI0Nfb{czgZ`8)5r>G&SMI&^r7xYn_3d~6?mvz@<>i(SuOIxv=iYwj(Y@E+c(UIe zA3wUti*fVhaLBmcxIuTP*k^7TsV%p(vCBSKU3jfBP@VM(Kx(7ODj-;{ZS1ybs;hzI zhGSL5DkvJ6WAjiXscd>)QYAo=MIdUDlnRj3ISDrF000ys=d|PiA}9y^7D{1CfF+c4 zR+zvR2QNDtmm?7w3`K_7(173f>?%PE#kACSdFIe$m+fMqr0p}qqNvoyGTgg>W4ke$ z$*m~LOEqkRogL;kLT&Bi39QDk00wRkxv})47iEyw@ZdbtJd)!|l8II~jL>U@iIdSHC z^%BXz24qrU>OTZTu<$HXFb~nLqXa2zKvoR;`3L{&#VarW`Tz01$3v=4xFcs#Dtql_lggZ5=q7};YQ@c1 z4}%gJQqom=4;VqD>>DPug1g65YuE!QU_De#O$9pvFe9jPaD#Uu-=4GbH`INlu&+v)Ch%Zqt^y@m3)LlGBSxC(~-D<(js4B=*&8C$Ah-KlFcE=&vsb75oo z>Lj9Oan2~B*pvq2@eRHA|FO)g_PbR<1Z}KR)FdfXmmQmD^(8A3T=7y-(4^IJAixX+ zi>tsh?TC@9xXczW1Wf~=suBTv(KLw|%d;I(!lXriCUY+hL+6245+Zz zVpGc9eQDwMixkr}_|j|Isy={dtT$Ys8xgo z%mB%@hBO^2H#b+&MQg9#=qAmR3K!gVU+t2A)mt=sq6L<#w8w(#u>#mc5deokc)tr% z_c4-PS)RH3jkc3oBb29!ueU03K6NQZ3}Ek6Vl+g z4@reBu%(JT4nqUHdomwC;@s!$v2Kp!c=*LHz53Rx|KuP1oj1Sqwcq?Je?6kYtq8`J zs4+Dw6_^kZTRZcnLM6>QuZK`Kos^aii9P6A&3k0ZoS;GgD%qDztc+BFHb^jsaEu!B zj8IsSk3ia4!EzF!#^jjR;)~Lx6iO({d_l6@9;m1yD9w> zjN>?c|DAa}US3^oTR!;m>sMd+%;VqrXaDB^`1@b~D}Q;v7(e~1=lyQa&vz)E{nPWT zD&J={@^fFbyttM9``q&9{~iJFeV;xo#?FuVoDZ~%AN9*0`!)7~w`Z56i`7aVUVUZU zo__F8{{@aw83}Dy2fX&?es}Zm{!5bP&2fwCQ%SS7tE<|!9CbJq@(_n;ARTkgf!Rb6 z>6wB_YS$Uh1X$TD0%eG*1u1rTDZ0Q=6CuvRI>}i7O5RB<>-XkHDD7|)2wMuO&X8-% zbeo@2WGxS9dCaYQ}V25-8vygIgSshLI*}^Ho2!)zdVDL_9 zUk{pB6A@{PeUjB-Kxfpoid#sUhwH1?zy78D)q77c5jtSgsCj$GyCZJyaC5@s%Bmua z{ZCRN*&~%k+}VY0Nv1|8VQKiem(j zeJQ)j2fB=E*C3n$&v;6zH=3+OJK*=o!g)t1K&D=`Nlm!2&FG|A4@1W6PFS zoe9=wm1oS^0}ZzX6{_x45QxOq(hdMZiS7;14NEX$LguOfEr1Cj-G=t{2rEg^y59)2 z=$C%vU`V_B3ZLp4JA}t3zV3%`gn~Oe*apZUXJr2_R!7t*SXmp#nt> zhBL)WR~PpVhiz8PC-ceo-kIRV)%Df&<<;lkjCt>0|Bb(S(7iW*^J|?{@o|4${@BW1 z@A)q;=Js*F8P99`*&Q*FC&m}D=3R56sc$k5bg3bkv#rfHBU$0TVk{aq1dSh0yF05u2a zbOEg@P4__oC^l!NMiu)d9cuqXDov`ciVUgNd!MnW*`V=1fei0dj=`$-uo>)1(ya7i z3NE`j1#%bb-o5*u`}~v3yt{vR@Yb7B*1nIMTi)$?JjLCP>T*o8u#X)3R(3j~ea#&m zM9*I`Baj;EWH+tM(TwM4%ySQB+A+dbSa7m0mM#XUm100qVf~qx<=La^o(4y$W z&NuGgxN2tzVFp*j*Zp7LI133jP_(zY+!KOG<(YfUmaVdN9bK?W9kAeaV$1n0(&=pI zmgwBWICdb;lqt8h<^dJU%p7$;BLv8~9784O?6!LDb4;UWEytyH%5ve}oN>0Idh6h7 zpcfzEw$@Cc03!3u8}dyo&&?dvAaR!aOvx!3>xwV+0%#(TCCrM*$OBntEwwwqWfB@d z$7*{xhDZT0S|7-RSvsB}F|FBs#`lwCtcYBRoLu2;N-bwFM8qa)rS*#Lx+_TKmfE0A zu|d)yB8nO_W6LcyPC6cg5^Ef9#_=w<9CxQWo#Nu+mw)TGuP^wI{&-{hI6feIv zPfsw5M{cqCT6Kg-Mi7|&77rmIO>$P1+`@(DM6)D&ZuU?q0F7diH2a?+Xi`;LyHHtr z)M^Wnm_mtTHkD2lox#?2Y8cX-BK^H)N}JV9=Ru-(FHY9%wNz9UXC;D5o;1ghI48%f zhp)aeY8(z1kKTU!Zr?92FSp!Y`Pwh#D-RCu*1z~$fBP%H@fUC}^OI7~yl7>`dljrN z)=|Fq_M$Z^Kbz|&^w9j%?fq`gV?nP$`zLQ}FeEcjJih&dn4u`=(i3^{;HCNahuh^L z?mx`ajyboBgO11C4s~(BBu=x)8I&Ru_QZCH)$YkHHRPfeL)k=ML^GNVrMa>px^Q<% znWo9O_;3W#8Et2xQl)6HtL7N$@|;0n8Zx9KZdKOk(tb4d5^Ye=RmEPI&;+MY;Wu~D z&ZEYBL#m)S89>HnDt{ww<$t1QC#Dz$3+d(`_YoySzjgvBJ));<2awuJ2B(><5YfdyuHETnvOE8X0A zzdd_t<-(d|sQ3S%QvZcVv*$$7d0iz zuAOWf>+5GbB(_DdqLj)*Dx#*dI}m}~!s9+?fooapyOR+Zhm0*Kp#r2-k%!2Qqj(%f z6ZR*!`|(cV-J{3jbQ9OxH~-RKyB#^T#|#HoKUhOS$N5!xKaR2Dl|XJ4`3*}2d`P8gtS7qtz5eu0Ei%} zdPm+VCsE2cv<#r#(bkZR4Ae>(21_JWv&Q7C07l*E@^E?oJ_E-`Pv)3c*N2?j^=DqW zc>Sdv@%BIaS3ms2?>zY2XVs*<_wizRVYjCc#K&m+(2w{Nnx%ikf5j6*^Ts66*h%>_*HqM7sHHNBc_9S5ey{5W! zf^Y4^uhu-KfDShrFXAs8oQZ8k{rEY6jbqTA)nhltmzNV>6fn;Zk6w z;R2!TbRkx&0p}8nrZH&R`kn3e9bKfAY&3-J;*j^}Y3>(#^FR9fzGaQm?ai%D75l`^ z9ZrL@lqO+SR>i)@7+pyQ7JbUsw3W2x20#rA0}IQKXWfb#;ywFyx?|C_4L@Q-8$lxa zhguq6DFdAvYkSi~Zn4Vj_3dapTS6}KbFrnTY3@+()8|mE=AY2EMCWDV#msP}9|ljC zooI$^G>KT@U#?xtOKcVSJ&#~X z>D?|kSi!OYwszbg?&1*jgX)F6(t^R_babw@>^e6GSPYh@vAc$p(f&(-5M=_Irx}?` zBcED!TxPVh*4nY8n21Pf4cV|1%L&gKfF;UJP|W4Vq?QYZ-9SB&h%Lj4{@Fs2*dh`( z`!bfDTM`wUd=t^9OgolBiY;}po07Qi+INRuaqMu`NA2^;A{nxq=3?wakTzssvQjEDWmrHI-1T zNh&Z4a^r*^1`dWIB2nx~=A0sHPLLou2Z)*u|LWD%3!unZnk8xO!ws~k%#ih0HT->u z#by!_O3sN{U|~`tth!02$o=m2`sMpOM-JY-{Y1p|gNNYdgIDk2>afl25B|~by!!f^ zIN*JiG@rg^U-a!y+@YV>e&G2o#K0%M0eD_F;Jx31F~VGdDa2~<+^yxE<4Op za1F?USSol1UW>*p09lk~1tBJ9xWY&?H?)F|>~*6K6f^M8F>912#MBUnG=u^Qp{U6q z0ICFJl}ONboK~;ehhr`}y2%qDI9+YkDnucJZ2xUHgxaoSblO6@1wj6usP8{UIxq)CTU&3=d=^GLUTG7)zAA z2tYx{!#*7r$poQ@wn&dnTC8=^3UHU1R+-41m>0AZa{1`crN;LAmO)*o8tctiHZI-F zJawyC$)L^AnNO+VfU)V;853Xxp>o;m|Bx~(9)N-?Oe|uPFA~ck0)B3YUUe24NK0!I zQGsTrhuew{neKUxuPFh<=9KK7n`Fd>M*h(hqzEhTwU|r{$qS&nFD>xVl&CVEs9K z3jgPUX!Cpf`Su|$l3ChS7VODVAhysUS~rp1A`zIgoUEM)dx=_vZV69kBX1xHPwLXM z6|hRIh?K5Y zX%r`Gn#~UpoTgUw_Z&oIOb<=eE52G>QY{50IjJ@Rid0HgX=hNnBNTagv0Yu=-Q6Cx zi^FB)7WdzL1(%o+^PSt9?|uK`-Zj=xD_)$}+K;96f4aSo?bD9!qu81X@#*&BZy(_U z5YPPc#oW$Bi}q$TjbKZTQK9YCS7S?2ReK(`EjQ<8C9o+%ad6aUASlhrD$WU@W_bo3 z6;TmLI<|A$Oz~$KXQir4{kIi;7mt%b&{;eZYZ`uQu&n^fFGi#k<%l6nu3}Lf1C!iK zvpC5~&FMe)#K1#?Sst*PCho#db6{2!IA)E>L6vN*2>_!os7a1O$;rVQWh$_Ho3`yU z-9_uDCPAn9TAi@!TT08FGo--Q%!=G*iDMs_4}bmZ`P!TN?aesV;c&p5ym_MAqxK?& zMD3#kzxLfAwtf0r%$nAecIhvPoKi&s?LCjkeCi7nVGAeI7&Ag0z6%<5&o0A(#k*+) z8SqYp8m$J#(gz#n&Dh{-8BHm$KRudDa|XGeK0jw?7>8lS(+b26Oo4z(fmDGd0qsPo z(wSKH)F)K1L%-QI{>|tqL_qiv+Ld^Vk8)+Ri5cL@&-0P7>z1RZ18 ze(r(@746H3qU0ksXzMNjL8-?qZljkNAP7;jhNQGkfC9j>2UfQlu4W_p7-E&2JrhHC zo%BV{&5argDy0LRNmiXFD%du8us~DW`)Xw73P_VkM}l;xQ_IP;8S<;mRSzFx3x!nT z6e1GNYgWzCqq%-in1_f2Xm}nGRy2g17k0>ya*D^P8Z6eBb$91$2G!}wqj|aoaQEa! z$6LPg@)v*mw;mmj7uyzxZDJiLA$WzXw={mdGS__6=`Gu}R(0DQWwwg1Am*nYt_fE)Hs++w@;@bclysX0!k z%)`ZYp*h6~#3o?!T=HR=$LJhr4>Un12#{6L4mgCAkzKMVLMc*XaNki4_M5K_wPbDK zJkf}xC6$I$>68FhdZLtDkmcz)NjYGVRoHz103ZNKL_t(ww%d<0J~d~Jic&Dly@6y~ zzwU_q4z9D7w9KT1Rs^U=3VU&g1t8j@ThwF_!2lz)XjN)xA4?6JK*yw>2_Y7P8t8M? zJ4asX;WvNzwXc5l?$O)h(cRVcy*Y+%k9j+AH%u^KjUE;+?ZMMR3IIqU{V!6h zFtZ6KXnbA`>^9jS0q3)-^_p;;LUVzAf|=N`WZ6x&X{`lyW=5HOvs{4Oo217wK2dY) z+kxfG)1g2vz)|QELM(oRmDa#IVlTPo(^zhjKAhfbW6@{ul<~B<@4qQ8W(dodQ&{<} zP4j8#gnQB^pqacRb&4{v*ii=KJ-`6NOViw^@KyEQnW9@!?#%bG4=Rdc3D zDbTpH(O-kTo>Mi!$=>nqf|eNG6FHu3>${F`yVKZuTn96+JOuDxS*H)FR}os#>w${z zPG%t0X#h>PqubF%w?d|3YYng;lRUH>*|K{y1*@-21P~j~vl1IWc|B~IhlC>~Sb32M z)vR@g~LkMw<2_$Sa-y9t4+G3ROah;~v_J@N4*r5sg` zIf>5j_ScH1lGS?@YEFs9TvV-Ep8J?JXOV!W5}ISG;^J^`FP@J3Ra_?WbhB@H=%MPa zI*n&*s%O}60G@Zb_5Qby-^Ac!cDNte_Cd?2pQ7yg5H9(r6Mz?Pdp>pe3t#RNB+O%n!RnjBxaI{Ue=SqZyaZ;H<+YUc5fL$o7fr(MtM@+_G zlxkGNW)vndsNoksRE45d11%;VC2}#GCuiC7Mx!tpR3uPDF&NxOzuH~SPA-HO<^`EV zfbC!scU7gTc8*vIMK{g=P;D?j|+w{PBgbpOrI?6c;R+jw%s-QJqzAjFs> z#>ld>5M3w}h(p3IjAgvASk$@hTu3l73T*euGd&{%&Fg_oOsZ}zlxl@)*D#QcQyLkh z1e7Ttm6^zBE6Ic+MAe+(+Ay0z77?5ZTaIQAQo53M=}lwC4cm8TP(n4oyh919443=( zo@O(38SR2tbg_P}qpD7jYiTutW-6%4W9`)j#h-dv)p|andK-rmcMDLa}E|)^1SG6#fzRp=!!do6kzH zfZH9}QmsHw=O`>LDX1tezh5^$(J@|-SR!M^GO!r1jf^CMZY&k;N{VgUuoO6f$is#Z zQclBI7hC8-gr%I;UG)r@&^E-y-aj&`OAxd+cZaMdN-hMj=FET&( zpZwVezx>vIw;#Awm1>@SmLzem8%n`n4$kU6wM7pmVkRbQ>=rY~@IR?pnlt<Um)XHpm<(j(Q<(6qm~2~+B7piCtXWnZ+L*Gdu%c4Ugeq~U zTbE)DYSPf2nrG5jeH(_jZX2bcm{1w&mF((RSkrQoaZSaBmTRZkM}Tco1;myKD2f@v ztM_01<~QH{-nVYQ^W9fI|4XNfZQeeP$9K3J8pEV1Oy;PVBO2$MP|q$roM}crQvKrg z8=PnAhgti6&0Alq^}j5Ze`{1*YMW1a-7DH)t)DK?x=3NjHvW-~7;nuM@~`2{pFx1j z2+Di+67eA!xsE<=rN%tFmm}6=#BfpNYLM73)a+ zcr*{G;1mKMgqt{5ilo@5Xj+J#`Ls1=!y1hU0)xsP&Gc2t)y#Ao5evMB3-um56s%3P z=o(MQE9;}QuB9*7Q=Hx4z0bzrdT8Y>v{(Jv$Fv%$URluRZw)=e;%c?_`3tXMe`FW= z@=$Au&~1z5E-`F(lwsvbdp>3MJfxfw7jTb>kdzUbThCzJc_u8U0T8J~h9WL9E^+wf`bB=)`JjLB2$S9 zgsRG-7hfF_9Papnl5LAk%v1T9lBrfzyB`R7715c1rf-!{Qi+PD_ml3kW*ZfuWj__2 zOF*C(iu=OVH>xLZ1SUunEmY9magQxebH|o1eeMliLRD+EyuWwJ^TpTmJ@)=yNA<#% zzVB=Muh{^Zx@(+R)_-ahKb@kiZEeZ>2X^KO6oy?lfZs-AUs z6Al*_+~VT(m$vJBNnGTH;5;2Q$L2V%nylHr7HGsU$P_k7RUw85)&{5ZmWwvdN|%_e zJ*|kqm{G{sG%IGUlGR?@Y^ba{W43)d=9F4l8<8zboYEksI7?&JK2W4pcOu82H2`Cv zn6ch$Q_+Zifx&X%068a!C~-`UsUbT6T5;RF^GP_OD9k5`Kp0)KEouPm#pz@xHdi}@ zATcYD(WXgIF_t^2Bpvep7rzMc^xf~h{?)II2iNoValQQ{PgL3Q6Cq*WF-F!5PI1~! zG`7gxa_joNeP$*xU7Z#Z+91KohaeN3$|UD~WzdZTK-XZI_IK|z11ul^XTI&iRqtFz zoQc>H4T=Yl%Jz+k2$G2G8f$sxbSFu+^H4w(N&={gG@Ng8c|lO5B+Q=MxXM(qJwBo?A`REnp0yr|&_D1bT>55+g9Ja23+ssFDsDhVr`|Aj~xJ|wm*&a*q`~xrnqZusQ z=8RVeEJi@Zh{$bI-_CQb3;@192ge1IlB#`q++kWoT3cjhy4v&9#5JMkZ}#SN0SP2B zdm!R#MapiM-mM%v1zP|`L~gN)NSDUMN?&G~L$wkjU#@$!Ai4VfWm%iLm1DQc#Dhy2M^-Ih&aRBRVi>UKx5 zY81zI|K48nd*AtvM!o!%FMjDi|INeYOWr39@U4=M%p46vlK1|+I- zGRo0l#eHBF=R_c?l8hxo7wy67@blnY)JFtoX`k{u6Uw3nYfh1}r&g$KptHc?MsMa4>RZRszIa`2rcN#ax8k2{(x_5Ot9S?`Y;iZ=@?q7GC z?Y86nynx<+tME~5@3r?I^Yf3v_A>w}pV#OA$Q$rpzyI-T*FGT)z=w5XKI#$G^H@L& zWc?U?*(|3QAhxTk8Mu7y^#cy`c#Mdu$$e~$JRFoDr>Zg{r1tV|0j?3JCg6i-2_@1L zVc6e0gkV*P+$V2NppX$P&8Y^iRz+kYxJwo)k>PBsDprMZp`9X^zfHsPRr|zPJ{Si3 zX@^JGXPuFB=FoPnR|rOEd3w4lFz9C(ZqOc(5QEqH-B-y@#cQI;dkH3JF2ApguP-4V_?oGTga4}yG%sG z!fG4wHqf*Pppjr1nl%d9#ExDIE9T50SwS={DpO%J7>znM#@rRYjZK$vO+)Oy)D3~R zh`=Rv^I5v))*4Ur17pPAR1^O#pampyUCZXkc#Y6}^d^ZcpH6?cQDnA5JNV4VBB;RA zINwH`8zXm1&Vf)JG@{Ow~V)R6uO;&9PRYeIW>Nw_;eJjn=9)~y{k3r<+ z)&0-BlDYM_g5LMr^Z7OaKmGQ6hZxUcd(kSB7wvd{^6eAS0DROp?1MH2@AdPa?ZS$u z|L=WaC(n0N7l7eV(d|(8x2w;51~v9akFTz;>vYoH5yj24+F~k2$N-_Lt`nwW~&N?axq z13J;>JT)eEVIYVonB31XZL00?0ZnqS#fYL2V!P<5h(VnuM!N<@gjyEaAS=$a_0i;D zj)5xM`3FKC{JTGyq+^NQkd$IcKLJD`LVM184_}UVZsWn#)o*@d-^P6B-FkA`gc?Wg zV^c&G=FF;OVa%W_#xkqEbrR*WlU+x)Cbik&YdYXRc9Df8$&9weAuM%B#TK&vPX-X^ z%v1!-lrTul$i{;+e1q1kV0rf2vJY4VMLVLpZfiEwQ_2Nbxhkp@X>J^r4rCdn zMislSJvX=38>__f)vYQYnsowxik3}m*W69-DIv1eCFCrneK*k})>@UO&JhHWT*I|< zJ_aC%UC|{kux;%tk8lsAu%A!Dk>1f4-Xf4-6-kiI)G}?)dVn7FxB;-|4I&QDSOr91 z?INUYvqh<$vLd(K9GkbsJ2h893}6dJMJfZXn}eIlK&)Au$WWE`$uW?Ks|#$XIkuF> znBy3iaqo@S9>4R0x4-=#4zIuQ`fq$)sx0ewD`a-w>~Di<_uD|Zc`{Q6C?%y?niZ3r z#eLLi*x8dsAT*Wk&Gy-;BP0h!Re3Js>c7U=efO-A;l4ZNf#aytF0^fcEw$Dos6fn7 ztPU_!QKNLXkNs3dRCNGYRawO}b*Za)+`TwA2If57+>zQc?{1IdxL@ACzW3&3`$iTJ-w(w+TBm!EDiF=nx#OAu=97@gV11MTL%&v6;qhdcnWXB zETdWi0j*lDXm9UkSxf|Dj*7#U$5E$o^=E$hc<*xi;XCt>9;Y^r6OYFn<^~M($rg%H zau8{!&bsi-MN~hx7!3)B$@Pnr4PcQ%%-45yFxd|Df!vmx4zTe5waQ(g8n=%w?j8Ia z_GxK*b%m7ybSV@4QS2IiwTrnva(y67HWON9yKsJCeTEAn^iV?QOlAa{tJGNd`d80L z&fc2qDCGExh=?ZDXlaoX=XOQCBLn%8{UP~O=iR&NJ5QYjtevO2B3`Y4-j)Jkq2Gp_ z`zmx> zgvAS<{7m;XnZa4JCRKGBS<)Dhb-21jCdb?=_b0n*Z1?Wnd-c^n`u*QOe*f+3uYUdV zi*M!C!2<+nne9YE&NOD&1#1WuJlk|qC}=_oRl!EXvmLI;3b))P`AbZ)AX}3v#35PL zIe=DmOt!*#7b>c1o+eM^1Tv}$axO7K@WjB*uMU;toH~wrd>rrIW~BqPN!1*nG?faG zbsU)FY4C3HcE;4?Jh70tJDwu)^0hY(hfUPSsF3OJqo1Z5dH!|dr`w0JJ^!jJKV3{8 zWcx7}>jT}(_xhX<`tuKKd!ZM>VoC!((2Tgah%Fy{?z7idS9kCJ;PU$FZk%{~#7+ec z*H?DKFd+nhsElZ++YoyZWHLnidP86a#BznS#TKCi<~xtDPi%n=A#6DCbi$}mvI=FN z8wOx6i&b|9Yg+AU6`PH^ISd4%#?+i4M9JP7?o8@5RRp+*$UcGlo(krAWI{B+l@L~(*esj9| z#+UAY{mX}&CwJfeVK9`e(E`%2Njr zfKr&LRq`iDE91%R3YU{;4;e^3fkC6)PfKkOhrkwHrI&)pmD+V4h%H>-NIGI!S0aH( zwIQiNYLjQsRc}{|34v2iP0~~-$~KGVnx0hXOnH|~<&MZ$mO1DX;No_BN9dCwtEu@O z)VOW(Y19#1 z&+P2#m>KQdZ(mbZwc|V1-xt+g1e+mNOhm|00aZm6fZQS_fhtWQvhP&Yk`GCiOM759tlOKnMUBY4__`^BrTUR+-M@OOXr=KGIc`pPdJF))|| zC`1WPCmt})ts!l_DK~a6{15`%a?{Ze!;Ax*g%%X)ZAnYOL5iG|H zB9xjHjzpwl*Zjd_9QU3)2pmP)xpZO@vx>VXxR}fxaTK2%qZC^f3Z&OICPTWry}j6S z-%sQA7N>oq4#>JWiM-zK-@N7UQt;fwU5QhUcfpG@}2 zGs;xLn0N{^n-YQMEEg>Z4qGK^MJ};(L!Z*3C%KO@8EKHG$x)aH1J5L9H4CVUvy4om z7AvJ4MZrls{}ib~OmnLl0ZwUG6jaO#PJn2V5>X{gZrp0`s3kd%)<&gvC9>&F$qG(a zzcXea4-@y^dgb!hzFrx}Km0+R5?j`O#IZJl`w3N=Q*)vw=Cp~aLGX6=G&mim`mgxb zRR?1^Y{DworoF=mZ-d)yRldmmu4Ee3XRGLq30n~R4!+mB~k{RHR~K0oKBcU zN9j6Dz{7yXg99mr86N_wy9uqku(~WH7blAT5QKcKda$D{4Xe|aIy;V`DNnx4krp2S zuo*2PKp%aD9CKj~P%PauB0TUg;q6R4YgU+YJ1d|CAhw7VHKHYFZ^KqE)`UXNlJh;w zwAws5Ju=kk@m&nmY1G`~VnZUS)6Hq#p5ihd{L&W^_3rQe-gf=q@ajW=C@y*wIcLXr zxYreBKmQ`i9Ut8c=s2)k z!;@~N4X;e)B2lwCe@>C;NE01*YxFgC9#2xFa_vqk?sum;o=|ame8bb!jJc1)cBnB^ zhwFRSkB?6xb$IRNd!KoE1%dzAIlznaZhLW#G+FaL+QNklWiT`b zCYLM`5u0OZWE&p~WcRuM;GIw)6&VfKTmV8HcTBc%zU<@KNTJK1Ad<<^4nI}Jszl>k z9HmL@&`F)i1!_2~izsrIndVIyXQ(FUu;iD{^qmSMB2cu0sX|e}i8`_~!hA-SZ;~;| zeL5aUv@^XRCcx8x{eotFxM_U3EHZ~eP^=gHyXqH5sC zjnL_oVz4lFjH#{&g)QOmre#snH1fsbFvX)Q`g8JgEyKb9wm@^cwaf~Ei_n(c?g`Py zZiaipb!U$nx+_3>jL@RYwMcmO<2Tybz^5@_`{mg{;tw2CbNupT!WsD_Uw!;SG#-f@GM`-#Tx6b#bu~k8ql*6cPp+( z11Zw|0DE?UTIW_pNwe8EtM51;B+3Gto+Alml0CJ^NP-danrO>aaCH_-dF-)AQ-IV* zXL)wWqZBngtj2U}6rs!wt%RlreHMkQ%EO;PK8A|e3sayp{EfjxaLne~d2D$8U!4?Z zZvc^VI5(FJ6bULJGnEdm@7kF zeYFFTj#DmaP8%gf1&>FZcB3$vnVEBpDCXpRe5&IvVLM#R;|U4QS+#H1+v(;m#$8-r zKKR`0xozkU>`!}x<-q(h!FaP3p+WGJQ@U=JJoE&*PZpsM8<|aC z%IZL_K!D z7jW>nA-1z6sLu&D4KhN`E(X!%VLt698iz+mMx5R2xg6_z#Tr+B=G)b7m6m5h2)6DN z!dYY-k(8>+NV8ctivV=y+#8BsBM*j@TF|}kvdPxVjooX*D&1sHPXI;C%xdUc31rVe z(3%i*{%ccX;GtBDixKSz)SN3j-)|0d@*0=xUsC0PL3%mk|7Y*bnr6F_>#&u%PkqC^ zxBwbMvj;XwwMdCU)1sva+u=}zLr?y@esJi;eo{z&p&bfSCLIill*ne2-A#4_cld^? zvomw~Vdbt1Hh~^5a6dG#192zRS9R))nJd@eG;4{ibO2`&u_$5z_S6o#Y8IA{W#mZ& zi_1RMIMiXX%Xn8^9#*1(fd&rs$kLfoNWMOC={m$iGl&x?I#g9uDd$T%ek-`K(m*%s z@t(uB(Kb+Y%}=zWdXxh)tEvM5;JWBb>U7758O5v_`|Ul7wa++Cz*x*Z@18!-x_R`Q zU;oOtzp?-DgTu*d_^gR6jWi|Ye+fXqL`Uofut0hi>lh4lE3)*su=Zu`E;e=L%JTK=|Z`7UT@D2x}%@3dE^IIG?Zsr|6gjEPx?ZH!+87eJ^M2D=Z&Vm_I z+uKQ4@`>+294r=0B4}lz_xSLda>^^tMup7)6p~KMnViLJhH-oQm7m%q8n!1;DA+J3&yZm-dfMJ)x+MW*R<(R}!j;1XEedvvxg(1L`; z7V7z#MhcXc0z8+#2dT0E03ZNKL_t&%rE;HU=U4{70QV}2GqZY_9vbLa}!KjZKSMrxg`xK9>3?*uum|*VmxGxl2TS1xC%AM8B z;5`;ln_-mUJ$r|!e-;+deOF01Uhab^8|ItX|7E1o@gVA5$0h5w(?lXb z(*RllMMd`w^I^l?bDoeQ-usuoyM5&fr~QqqCs~0g zo2)%h@YPSjUG25UdEv5^7IGK}|a(^G!N45~1M_J79Pc z!8}b!&Qi%!@tD4Ce)QvapW&2cJbUu=FpBM*aB=ncZ~o#t=5AkoXIx&G54_}LJ%sgXK2IOkv-&5j*9G7{ zwD)wz}_zXI!HZ%7a61eJc059P**kKbiRaEvkF+l5{H({CMbjvxNt|Ce39eLr*C zgL7JhZ$2JBu{r$|swvG(%;mGp59+ zAYm1g{~$ZJ>%%2vw2DFs-)*}~7DbmfqV8wiPgLbT6*6T-rZ674nBeSZqSswu^3mFS-C0Dg=Ei9hJ?E1MRT`0?$dcCSZ?X${~fA}z58;6g^#inVHiD++gxc?Jui}wmO-$J?U{~lTi+$qcrlZkpf#Hvb!mv!hn;IwD2G!swE$NNI+1&5l-WL>-T6u2C-4MHO2`phhGMZ8WMf zx=Aw%Dh3IiExm7D%3;d8Uv+vDZL%*ds#`p7QLQf}hb2>AaUMmKT(cJ69WV*f;ni=| zsJct#V7)7{tXdf?DR42FD@<7Gh_Y@m0-Y5hA+OZ*G+45dwd}Ph?nqTFD_61s?(?yc z@sG1C)uvMWja@7HnsYS9%<+#aC5`x-gUUU_bxY=agdp}Cy*Hl zhijanSyxsSxs7eu!EMXP+;hG-&buRv`*BCow+&{e=Qr{E>D9YWe(it!=jnlCcx?Ne zoP`|>F}pNTGNo=Y&e4R$B#!B!vpbPx=F0xn9cFcgRXrh8;O^!CtL}H3bcv_QVc?v4H#bt$#R!9q3((^F z-8U}Yy50_3#!k+h88fdQUBCE?zd78Tp8Uo){Q7Wtas6Y!_$NLZe{3M|YFEpY?9**T z>f!V`TCZwr9@e+|CD!MrbNGTZ0G}B(=*QM4ozM^Kf@zfX0WUECI*9V?Z$J5+-@pCi z|1Unci8tTa_uKvEG=PlV9zVt#Z&*&Bc_vH9`H@*em5mWjGBS>+8t$2sOgDEk8uI>b z{@srN=cU_bfcMT3JH?C+w+*MXy`00#lx|~9hEN=jc=jd!H?F>G0tB8$w-y{!%X7iVO}xoG{cMiYvTlT?^;)DC~?vuFmJl@sK(J~kl`{cfB z96{CKVggjlP(=(;@vn-=fJK{HJ6p8)Cdjy|12r!Y`(NehFbu3FN9~XS=+si0qxTi7 z>t?#O#k(q5K+zI}(L4#bQA~f;>A3_7&G1JLD0(C7<(Aia__z7~`IRPE8%vHUE|$71 z$Z)nmpbN{fs7u0$tKIl(6>4aLMUQ-hxpx;O^1a08npqhZ65JyMDX2}IRe7FrG==y(hdUdlw8RM_uvpxz&Rz@i!%*@9?B{JsCT^#R_vESWQ z?sl>HwjH0}-M+Ya^zCoI^DqDDes?@fudLc*lrc-rJWBFf(j7qhU=S?q34oiWgxi&s zMOg~~oMDpZ&U84EfTGGexg5F?%+y#?6D=<=6Xo%2uN`593pZ}Z?>@DsVH~>YnRx+Y zY;t$yDUE9k3Yu?K%nEbycyz78fCR3`6=@f5T^A~EYuKn+oO648eR*|p_cz~t{Pp+W z{-^IscX3{?yZ(CMkJHa=ect!OPp#j6#=`G)pO2rsz90?2XGRVBCD&^^Q9jcT{>47b z{eaD`?C=Nw;`HoC^N;?>pS*W^{K$?+$RuabA3ZJ~IPKh3T&%|}IIs{hz=qSn<<+84 zS%;as?-@UMW-%}}8>W~^YY-6aO5>!nX6A7>_gV><@)=~O!m&q($+n9{s`Z5j_6Y_x9oNP@SdV*iXg2M+@&Nurp-Ywz_VaM<40eQ zwnAFdi4s2E%#enXRl!plXg?Lw-64@mNT*qAD^Ym{f3qcB;DG<;bbRy;~Bl(61g!S+`?rLey=QAnOKr zu}s?4rl~S;vU`Q$@~G#{I;ehjC%V%@4kp^Z4Yy z`#t;GwI#3oGAd+7jAG8<40I0wW7~sPkoW1+YTc(mlza=8`BgV##ZvfguoK?0EYCe{ zG0I!1Qb-uBvTDz~J8@AQZGbm%_g6pg6C(qbmgbbbYBI>X9obg4=B~3JnZvEmqJ-P9 z;YD0Ny4vI9k1x4-9>bHY8nvB*c=DC+{?UJWa(L&h-}@(a@G(YPfA)#}E4Rkyu3mdx zOoz|x;Qx#t^l(qc&)Es}tG-^_iNXimq-t5voKOCn-+lb{yC41^{~k|I@zz^&RLvcc zm1T#qz4LZd^8OgdJE6rk+=eq^6M)=IZ8%+?+yZV526sfv51-boIt;sX@k-6nVp0*f z32=1%aN3)w9jsYaaCvEq8=bTOg4`qLF7VR;MD0I%p0_(eA69#K6svL%N$Y521@@?! zviGj3d{bitBAdz&$UufI^FcyELXEqS)wnA_+Gxg1$yE zsw*kMaj~MPqD_N{lC2OUQZyvZr&KyC$Q58^j}CNsR~5Ts6ro;FH!?-=G5ws}Fx4() zw5yK>U+u21cc$9ZVrCUJMUuxRhz`UxC_XAugAB3#t+{y;6?3&JRl@#NZYnujThyX? z`TdK{V-7GfDzs}yQ?ksva2K8HWgkLwr+4aHWxQ;P+WHBipY!}D$yH0U1!kr8jVNHvyi z;@WqW2&G(`FL?yZy<5}y;*_^1T{LFK4SsbIAD*87=fAZ(_;#oW2QZM8xz9Q#dP+3f z+ClECQv5`k!#9{=T-xRJ^}L@XE}uNkn3kM(cL$7v;f=3<^{@ZqAHDcb|M{DL@UQaf z*rTBq)qEz;q|ap8`8ToT>|_7;>s>!D1LBLe9>hWV*!5)t@JiO}61%*@wWt^VDQ%ne zFTVgi(%`UR;5*lke)FIF{lEM7{4d|FZL=qjP*vNMh=e9)dOJ{=;Ya?C8FY!)WfmEqE6Mp-@uc%@}60>a^1gD9&h-v3y5@ zk*Qh&{YDUXWqxp%A08RZoFuKfG#7r`Tx(M_73IwBu$)y{R$jX}KT;bSGg(Rl$ckkS zDZ+e#JM)+oNtC&Ps3av$2=<4GYn1JR!;aMjQI#oTb~!C^Ao10=AOE+%fBU_^@~8Xt z`(Ha=j=DSH_Ee|6a#n-|o%BkR<+?~0=LS=b?$yY4+W>7qlD}^@u?NUTr<)2Q9yZwa z?(A2qH)v&V_22>oTmnKgbDpljf$-^mdf|1RS0^JPB+LJrkaUx|&PFl9zP*l~dda>g+ zy6XmLK`$HI{=H{U|M;)!@9&uM`-GRri=!RW3^r&kkw|KQselGAT1wVoV_ewfu$oM~ z@&5bVSjXJ&?-_xt!@KW3dh`81`nUhv+^*mK*Zq@o6(4 zewB{r!&+Z90Iy{IyeH^GJfWN8P@mGa@yCAuIe(Pn&FeRQ^S7V>>7SndqZpB+9{s@|98Y)iumASq{dexZauxS?e12bd_oI??a^Fn}3!+8u7;}`1^#!kX)HG zQ2EDwL|DmY=_no{mFk27v5j`u1geN~rlWvNy*WrTZz@__T*?D;W+sVL4hD?N%TuqY ziZeN`=SwR8S3<*L?vy<5xq1VQl~hl65njD#O!JH?7^h9dyR+s1>H!~|_oE1Sj(=4lA+{W#T;k4uA z@i?~a;>nZ!hadjkpa0o!{ZIe%;n&_|2JRIkLSTi{Elqd}LS~s62Qb_dmNe1)YBB>= z8Iuv5;Dn`V@I?Sc?8PV&$w1M|)Qk>ubIa4@-OQUEJ*(4f%zZPz96ql3qx%p3k{$8aCU=J>Tca2CUS3=VSmc6IP=7|G-5;{CU_$D{U%op+}lr+M*s zd-FGb}l)5BGb0*#Nwf^-wzT zUuJ!pbJMkdevwZw0Oc5$m&d>T?%n_MrwqepSC1cEJb5xA5b1CnE^u{~bMp4Sj{D1k z#bEAi7?M_I82XE$VosGl`QbA>JsB>rSt|&Q*v?Z6r!~l{;@{dF#V`w@nxhO3xGV8e zevrkAbeb8eD(~j~_ddXVx(yQo=q4HkL(GRX#d$@b(vmgH*|m@GV-}GniaF4!ucGS! zE5%F@=5Q5tNaoCnl@2V6k^yiHVA_~o$rElZ)n;`x85-l)k5?|iHC*r6%&)Wl0f;_hVCX#Ko@Yp*gYGJu z3-!{&2cR-K3^XVq&_JX?@qElA0Urips9R+0FwHNlX zf|`~!Wx;vi(l-jWAFi&1tYD?J_gEiur#0g%s=(+Of2z2IiTDKuMRht0v@V;kEAw?moD{kGuOZ@-mU#&McE zqbhju>tBEL)vx{FfB!$f^Y8w{xBkT+(DQ-WS^qrCKCX`V*nj-V@9-M-z(YgsU;9b$ zGxD@ve`D}v1MuZq>!pR+D__6NiLPhAou0lpe&+|7#KlI`;mMmZE^D6#BL=sFUtiaL z@ApTK091}E)5lqI?eO)EJ}qm&hsB&c9^=`4*{BQHrQy(>&{-pF=yXuXBj4ceFxY0u z%G-OX0vKV*;e%P$$x7V`m^q`4-~XUq9L>m$Ah`!8-8V0AR_;5BH4B8f$tK*)GD1~~ znPC_sElKfo3Gon&^6{XRHHaE1qrIHz7?{Q5j+rRaPsu+NMb20msAXVP8;eStE1rrI z@#y06{rBc~zjLrl{MK*Ah2i#Cw@0r;X6+}>a;$6;L3EZ2C3miZuMvC$3Jyc15|qhmHcu@E5!x-Z+IxOUN)468rVL?KcFJTZIZQ4E zGoW^kH0dRR4R#9iP8D=wFu3gPnPoJ2*#O39eXjV~rPyN=JLe<4M5CZ35(vAcG`YFk z^69P8Orzxt1dJS2WU!_HdowU=LEhbctcI^~R_HpwyCNtECqIl zMWDkdRFR_>2n`yu z>)6=C;j-s{smT@rO|e{=RV9+1yJ@f|pVx}2qAW3O41FCIDiUp;L})f+KvwP{Dxf*c zjFE^0K{GhV!2v4s*vzcwIBm$ZxtoJ5a~j=1WV+cHUO6k$$`vJ6G2vy~V9fpbO`P`O z+dLhu=6Lk@o8SD_U;N=8(ykuA`}W-LEDH%tFreFl!na&P-nM}S5};zzk{Jg0aAsMN zdm;;9MjAG_=S272Aa2tnVZya#A7x(hWBKhLpv##HNA6p&n=fOeqyxq6A z2HtyPT)g>%Km3zt-}&3^;=l(`{r!ULV;_>g0LK3ss>;`2$i7_v5XAV|f993Go;)~OT_rV%rtr{}E1=jeVUC@2WXl7ltKQB=?j}osvdoJh;c#<`G>j4cN;?NX zh>4CSJS|sFF1Y}kr6w#^J&@{6F|SyKvg!b6QEDVu(bpGBG@}=l z5aQ-6&*Pl8cO)@u-}kb8yz|cE_uu==KmXIO{nkGV?Z!|7My=@@bPqw$M0^Y=Be~=y zHRlLG6~aM0N>Uf>QNH8vao^TS9tTpn<} z`Pj^d`=%$8ySMG2!0`YG7iU~H`R+b8+gUe9FV7jvE`ycgCc)j1-2d)reE&lO>Htbp zI0nc(?jUV5k{+y6vVK*kAhSJcW=n*{H-Th^GnmUKGzfrhOKIyBS^JFR zL^jrsQuI?*R_?)2rYVN|(!ZjLX3jC(Jbv^nt{>O0y_<2`pWfB+o{YS|-$2f3Rq{FE zX7J^#!nT=|52r0Nz`iG{JwU=~gQBa54Gget@48rS55;yvB42WY)+3+iy3oDaeo;zb zo>?X+B2Z;Chdav^Sxev2yrlKi2XV_k!-!Apk7cg_xP2B{bcY`@f z{1<=;N>9?;O_-Hg3x1~za0=qtuYFhms~iyZ1VvkF1!k#rumYkZvno@Rc@bDvZMd9g zWX%c#ysBaK%3NyOj&aRS)W!1Y*M6|7u4`M0KuO6{hDTV8j+E06jA{?jISnm_rdKf| zv9$IuBulMSWtW4Zi)9vIHoQ$fE6r(}chSoys)qQ?ziu6KYaGVkDfevXWvhjYHJNq zIisySxl}F&DX=xR5|2)zZ-e(3r?M&+4~drK49r=3BD3Lxpv%$g6#I+YZCuWMT2dhi z$+VvT6j|o(hv8S?HElRP)ud;D>ydS<5vJMUh%B&VS@bkO=Q4jENi^rGib#3Dp?Q>uK`mzD|RM)T0iShu} zs<+(e8^*9GQ^~s-h9S%LtU29>4=3n02#oFE=1`DoSES471F*7TLx5l-cW$eAIHR+j zD9{Bd4ODMXbkL0ZWYo)?uR)Sd8*T>Ar?>foXCp7<5a?zsqmgddCxQ)Ew*5AlX=QDy zBH*^!C{?k_Gj{Bh3aQMZP`X^eZL($};Z&>-KvbYzgVYBROjK6aullAyk#3&ijDvw$ z1V4Cj*v8eje`_9W-rmM>M+Eni!)i{;=uKVB3dOE6B>)9l#(EwF5HCr_t8!l%uv|T# zYQ_HBxu84yaZ=`;Fc`7}p3Fd$5v{;XN<=J(wn$Wd6v9#U-*X1Nru~&8pl&Nt-!}|&~OBMS<$fXO;GYhGC*lK~gNh48_v&md%@S6ofan5X5UNw9}?(8}f zD?~SW?hpvTnCdYMhG^~dI{3sQ0xi-M)*Pe2LPqG8z$U$SyucbF5iNjKei&tS+QDG9 zS_(Kjn!7lT001BWNkl~q9tU{(2+D4XTR%WOf^Tpk~yBp&o_Dt^f*40;j`>;o#ebjehAEA|nkmH>Zh}MPrnu0rYL#4jT@hr|Ekf%z1mX z`}z3&cX_qx{xM+pA08iFU7l|5IvU~?K5bs_`bgoxkNd|@dhr)*y)J6aCw=axJxO@= z>&phb>8pJis1S*OT-a;7WXfIaayi76vZF|bu`E-TW=>1BC$Jbdjvy!S3=jW~d0 zyEFA@;GFZOSJIa@y4fBM+cZ#82rU;BU*fB@d8pA0NLA0pN;7f#TZIatzJxeeW-uoP zNIGQCm}EOkay1)^q>jb5NqI!J{6Lwp0Emo6)>*I`R^EC|n8BgUrov);8J2yExp9q3 zc%E87B$28-+xJ&hb895jmd1ch!V%p_QP6y86T199mzmGVl-G^5Po|_7P49P>noKHi zTIF&k*+~v$abfrXBn=;xz1f+mOy82{6{+No88WMH(pW`ir1wCEeK2)>DB~fcGSv&2 zIZ!K}O2N`iii6SyTzykkrA|kkfbNbWqBzwk8M9zzj>su30SONJ@gmsh8mhc_ReVnRcn&+~d&w0;tk=cksPcK+ov?g~Edb~k=vuIU=27kIkO@UhpU)#GY70Ho68~Jn+~t> z_TvUcCjhu?Gi4qxhBPG@5LVtlP+eU?^z>R*-+SOx%yJqhD>5SH)0@+G{yv}Y7{lF$ z-bh3uv1Q!ibYhe~mT5dDlOj=MR<=cF8)&L*%&3ZTT1K{gkSvc_3?f;x5FH3B4D+mj zRT^sw5!5%PP-?lMSW3~zR0se`_c0QOci-|~|8-;3&3)e7b3b`h9Vhp_?J`+rvNsjb zm^@p`uHy1c))B$ourPg<#X(FU?H{swg#Ljgg zAhU35Kbh|F`jbQrdKQyH;XLISl1 zeQ3C*Hx2bK1JmBP2C9XXD^RT&`iqQ|tft)qO&cpav-oTIhSEByP2ZQP_Nwh@s(OZj zHn7*{YZpl+(QY#Bp3j$E8B0c)D>YBM5^F%wUvO3CCT9Mc7wE%F0m+npRtgOr0kw^b z#8Lzzvy$Fg3~f!Bi*Tfk#yT{jE%mM<>89*xn3|%f=DVN0r_`>tI++HPlni5ZFDJUw zaLI;;d8WE4TFYBS_@SE@QIVCIOf!R*In13{T72o9%d$N7%6(dqQP_(!d~6t_?q)u} znfvK*`Dosrw(G;y@4P?Wyhg4mVJaxY`f4Z*mFx_T8;Ook^8O zF6)>jD~h)}Pk@a&CGG=e9A_8l0#?EVI_tyBybNaU!`+e6VH<`))jz$pLQy8}rTaJ- zX}miPgI(E-9k=&;eEKYoq9xg6y7&Eu+Q2Vq!5Q%LVhH(rZ zkf;7|u1EO5aJrQ{H?tv9dOxmN5FV|<5nX1=JJS+pHLa?;R{L#+%@9>L_dG^LiRN2( zcl&pLz?&(xoBJ>y=A8x#r4;+XDeC6PM9IcG3#W-aq%35VtkA2HlroR*0=UJ&C`Pso zsPw9$h7=HF(QJUQGJ_3;)m9-Qr*cCfTJu8Ki%~hFb+JD7fQzM)H z2r7ojG}Az?RDqO_zG~~Ds}EG|OZ6TqtebMA+DNa_`IBy8$kjIROqWuktp*0wROw35 zNaU~5X{=T4EoDkB@WsvO@rLxI2HE7xtZDi9sBW!EhPoZW)qu-cN5ieE66)SwIwY6E za!`p3YuASS!Bvfhd?4{01leF%O|vgfQ@l579wkp(s!w9 zYg$lRY#$)ft&GgmUJ-IaLROqk81QkKw~;Sy>|lp+nKK`K>#KP0k(>LZCREb(o}(Xn z`q$K{(m1itL%;|`ittuNHOq*!kdUbqV8@X384knDd3)Rrhh*Y7tzet^FdN=UeDWfw zKFmU;+wgZ-r?dBkriRoBRt08ppN67aIXFBM^V#ji#kE}y?sI?kVjhpz*N^6$h}>+8 z<9*H=+jt1I)GJ#**H`&yX;ZIjH$1fZ>eJo}uWADD`qvkv0eH9v;LG*1FI9-5yC0uD z#eVX`b%mL2MdsWs(lN|NRSX+2=bRPOe2^&7>&-wzTKn}ImjB1Xl4SYctZn&$v=o(9 z@-6v@G}Tc6oaUU2+xvD{zd7c2pWz0uVb(U6DHz!skT;syAU*C*m@E*(SeD1geF6aF zt69yF6((&9ziZqbKtr?33O`WgZdsw{Ju7SG5)+%`3Q7+UuS5HUG7ASSiAe*h63M|~ z?|cPc{VL`jw>$PkloOSK47DO=fR$Ne`&fy+XO_0PpKU*|IQLzZ+oDsa9Ge906Gpfo zcZlnuz1Cnh09$Fl=jy(P9p}QMJw8`=bmo!+YfTYp9ODUk; zJiQwJ7H3Ep@+vr~7>e_}NkuW%grWi4g|(}#L!RCAgOLab;BA(()Eb#p6_F`-e&N2o z16~G|sSjy7&AEza$J|O!0D7~2njC}%e5+_oFe+!I=22^u0Z6jl_`P(bECO`7h4QQ_ zsVXu}fn^0!PaQ@wm4_g8tXNFewz08Vi8ISz#!?`v!)U8WL%D_`B$)}31Sn5E`l z6u@XUd;5)|DMgx>v6f*`u~X`51%yGkK=y5-svS(d=r$^71us+mZ4i%N%rL@$bnM`4 zr(J?Xf|fJedlcAw(*f|Z+N0!;1VdWfoiHpb-m?+<_zVYtFa^G6sNoI=v z)p@QdhU$1AluLaM0WMTkv}PTJN~^R?xkaIGL9)Th%HwY7wz*~1-NZ8eGE%U9w-#-- z3Rc-lDM8WPs~g+Z*qg%$Hxf0=$_&JYa^&5yp1ruZxMpI@s{3Q!9}kxoVToaGqvo!@ z@}a~3=k-Yd|HuA&hm`-s^>T3V58vvSs>vT(LH}v5SM_~Ae=3bHNCWV?4#2}b@Soph z?kL2~&0!ocHe()!*PPgQMEV$ZaUd1817jxv~=o3}S;(=b#b47cI9pLutL z8I5DxEWtf$4+@+!K+GbrQqO5h|H%}D4VvM9B zW@l+&B7n9cDvOhLb@}LbzMY2yj(gnR*M4#k5!`pn0^u?i%@h~t^#Ot28fD1K z9Sc=?_&IO-OS;IE`!zPb`YCJ4V~OoP%D0 zwiuG7PJe;cNwX&oEEpT;mFl+{B-V5r@ts85kbO;2Nl!h=u%#-oDpPg=&qEkeeDsb( zmR`1rPdEd*%vzzZ>X4fM9WSU6lDRf;swC#9xwp0X{NJ6}$b4G79EsZs^ zFC2Nq>TqbEYgYh7xtm#=2*IuYU1O6)gVMJb+vKD8kdcVOET^HAu%)q;ICAi9m@4RE z%{0136c|=4WGd6mfFXrLCI~ag^M*4mkuz)!poSYGk;R!txR0tt)|LauS#t+-*kIJ0 zw>Ib;rw?w9r`tE*dOM;nzx7qVb6v_L6NX{N%28&t%9Lf96_u)B1|}_=so4Co^V}oK zstmL&&Ro_43gdRn%KOuHdBFh$>$uy&aTucfD;*hC)pFXJbpk^IQ7A9?Z5 z23{^U+fR7^pJRPt0n?xHp#JLYpD#!Q@T+sczUa$9qhD;8$Ae!8^&Q)YdE!19Aw@3; zx|LwRIc>YNi-RrOb2N&o3Qp;&2KSEkY5l26)b_D#sz9#$td`V{@+Y@~yVb~Nr})85 z-KP15v2Nnx48hD?^KYoKkc!kS?@kENhWB2-1#kN3u@e8y) zD%Q*73uHxzkVA+9)0Rr2mjEhPP|ciXF{4iwD!)q{bgF4I8rax0Nf?aPge3qKx$GC) z=N-+B>U|DmlOu?0H6iWb$p4-$>RH^D|$`pxIgYLiwK z`RKg17SzhPUK6UWCkRupK(PS1@-(NYt6%onC09@hZAq6dxs}{&oI*-x!0cwN1Y1W| zlJ3;ZL`s4cM^I4}Y>=J{KZX~;)--qI7fIJVtB^@CUUXUaA*z?tGJoaz|&B}25HRn zvYhHD@~~a99GjVqx`}*tL(j)=ULUWwH~z`j;mk-A(e6BsslMH*wNrVmFAflu7P`U7 zFv}DLEk%OZR_eh>q^`9+436O_)ZOGv4!2=vbJrF8Y)ONNTS=s%Q_y?cbh8CPX{;d8 z#vVv^G?mjKkP+W*uX`IC|Q8I6Rp3^J4IyP@(*Mg$qCO zhtIU+d1#cIUwHl47x{$|{Rea={^a$*8-UNve*A>>W#jScm%G>fecj)|ec33oj`zN2 zRob?Rm2AVf!D)8C`xx8BK_zcLwe}O5E)O!w!yNFd$IJxnOmD(QGr#qGtU@P~ig& z@!I01+qwFKiXIWCM1FyiYZoV2{D+n-%I>BzlE5fz7nc{`{MyW&$5}VG17rqICy(qn zq|IqC)uwDQu`2$iqI5Of%IEg*QKh`5GL@?S0h|SjFGnl}w9Xh_A>>w_fN93iAJ_aJ z2*rmR8yiNOPx=DvKO)gx8%Z-OStSnRu(o;xD(YNXq6Y;H>cf@`z6vY%X7i)74ywBM zT7FGj#3uup(a5^W!Nq*Sd7fY-GMlB8BJt=w4eo5@RG@zL5F-;BklC|_vtd$eD%1>z~gAt`mi;5rSFk52UeX0gTL4%7oridzcHx7On`Sf`6!w(-nc`^>;?SJ-ldvs~Vk{@ujH_k~@$8=noHlZ4Y}>AN0n3xjyA(AA16jv>C`W zSk8#7ZKf3wBsi|GGUk9|jIwgy>iz^8hRbsU11Mj13G7FCdecp$`W<2!UFtT zm9f0WKO$X)`yk93@>ZA*h6v@p?kwGI7+X9lnvA2&aCf}CI`Ck&dCfu4IOaG@muLx)@CNf&JtmjiMs zsn#gypmoLik@p9BZjc68k6#Sg`eVL)JFE)coBm}b=MwgjQsqR`WW(2Z!OV+9wW^iX zO^rTCdryWnmFcdiC;()c)99#5qggp!$IHrKTkB)J{cEaK9qxrHrv&LnBau~Z1l-z7HOm}l9CZaCM~5PWSxy{!LDkxm(vdw>I8fojMmOPO({dB6f`R+ zmtfS88N9Z|;AbXOYjB(fP?B-M&nDYYgH;yT9i2KB(V12*byw-Vg@Wx--BejOD`h|# z%_O>kbzpDFT&T)`VaDQENy%y>rT$mZ%Jq$O{KPUBI}m^m&j@fC`4jSjf?J8O7iD9Z zS$5y%V6Aip<17rb0k8%J7I3NmBKmX(c(s$^hEt9LCeMo8x%&$PSyE z)m}BLqGv`fZ@2r2`zcf4^DW@PtaFnC;Nv##Bh@C)fBU2t|AgiL;Pnq|C%lsN@s+^C zTc5O@4|n4q_ypiptzX5i_zST<=D_i^^Av3=R|{jD$p~1D$B!$*qHMF;;IO=Xfw|k& z1&4RpE#_~*f)0o1B~&`StBj!HxopVb67G;XcCRM7aPd;iFaS5WaTqq^!R#<3JZK`Y zycOFGqZuwR(BN>}3>sNNHvf&b8&%Xuil7t5U?{$j%sR;xmCm9gK1pRKSaCeR3{m&6Ndm$RUNa6 zmU|E_YGkvgsz(-mP)z16AS$G-bS{-`W!QNx;VzKxoI+Ki=}4-fAwVsgpwVrbnVS!q z1`CTG6Qz`^F6hPG2T>`4J0p8g(+rx-mEB7#RKW^Pu}X9(G|f^^R_qk2KIfKkPi2_| z@ddRO5`_quU*n);vN9{;rJJW4i{@Re639xEaJKR8(An6D4Gn{{W|bn(^d)9BOU=Nl zoC#5hh*H!sn#+_b%3Gzwj5;Txb^q5B24#Mst8E>9TD3;Dk9h90S_KZ<97glDz-s>~ z75|EJHWR@}ucxCx8?<56oo8~n0|Vf&fh;6A(_zC0o0bEZ6$(y+*Pcz$kuw-e&54}& z0a!6-=xEgOIG^6+jEn8!cz@)^@%ZsP@RnZaK*;_`)&VFC16Xq^N(1f&k7xIF6XW8-ZNsDm4Xg+=LzPGIG;w$GS@uBd zEB=Gu|8c4Hs)#zT&HU$AZ}BJo^{4*(dHrGnk=L-kAPvBm*T?5^eZmcr94r)k3`3!^ zX0+d;xgV|&Vd6-*mpNwT@g9c@zZ@b4G`!e_=_(9`iwoB$+=l3}hKAQ$rrOhPYyTh< zf~JzztzV?-*4(`*3{4#oREQ?qXmlVuQFEHl0+9k(vB#D}NWOV}|DN35r%+Btd+5i8q?w7lFj>M4&0_d*F40pR9 z%p)5k5fnuMB%nnc99B_ZRVGA6Y`^__ z|Kf%A+y47MUUZinm>EYQt-s=6SqsTioi@psidtcmPH<0uZ}3VPAKyh?PcpiISt{A7 z%thVCdo0uoVbD(lL$q)zOSTnDyxfhBJ}TFSa)H^gvYeNJEv2fby(h`0Wv(ME3X8>Yz>>Z(-!YyHk^y9yPuX%mi}`$lwF>rV^0lGa9!t@&4JoQEsJstt*MJJC zRbNaCX^Um75`GD*roC3+JpZc9tXbKl-Ig`NO6>A@xdbG8??S94 z*Po@DC(_7M-#vj!S#i`NQC>hVDixUqG;zJnn%mC3fd$-I;d*6Rzfy*roQv&_Me_|( z5ayFlg+@dJgqrUG0LV5kR#y8_57bqx{t@PTg5libZ&Z%;5ZDyfBaKxyRGNX|D zR0^lr!t7Aa4HLjqM7-i=2xgv#}OJa417{OJVuO8sGTD%p@75>s^Ss|mNefyFu}VKcLRZW9Ga!$h;17nYR+TVoV5MIM?Um} zHU;d&ykGf~qm^Gip8Q0|yJq|+Pv|ETaQ;n5I(UCKp5526 z1EFnbP$QzSfA@;xE-u>&G$|lJ&D2ag1OX8Y2>y4;rB`G9VU&E;oNC-QjVZD=XP5EF z7y}U;fgoaV3k*`kerQ$)rm!UDNgxKdfo;?=kJk^UHe}ol1d(h}peFXoc$iaw-7+nJ zox>7(QkjIR=K1{RVnUiF&4yqgyVhPd+LJ%eys$lIGttehk zn0urHPp5M|;V8k;z)5F|13II?^PFGkb#ELJ4H#adK_lb8wWFax#3WKRH7@so zIv)1py2nxbcMng=_a{2^*1v!2csoY@#irq1f%4~!L0W#s$csRhP0L6GWfNr0n?0(kP9>&yEPr zdNQ;srxwzP&IiP@j%bI1g3hY%G9M6NCoBX-=JlpIHHe|2IEE-qY0uiz!KKAZtcc=T z?TJF6*nY5Gn6{uz*UHaIbGzHN$_IcagoXqvAk{YV+4DH+`29EI%P;3;;MlQGB2VjI zTL@Qnl*zLuTZ_#KoVXhomfIj;1;K2G-Ng%Rb1LUA4HE9M_bUH)^jw|G!J?gB7ddM3 z>VRnU5p?j?c^3Kk<(2ann&RLB$hXXiY6pdWjBl6001BW zNkl@J2R%k9H{$^K(I!9q$`wyU0&wW07;N;GZ2C=p!CL$7BOqtVxOC?a3wP9MH)LU}b|_0k*R5OIN+(gi%ic~|3W~nFWpGT1 zz7T2HFuOZK2K3hoEA8;5^e#3WtS?2@P}tto?jmtpRtE_aMCzrX?h@ulvc-~Gow*9JG=dINd?WvjBv zlQ?~Qw!+d9eX{K_VX0Qv3Pc7`g)Btq0)znN<1I8=&n&>`?6@b3tx|>TW{XQC3-KSF zeg_4!LkIwK;+6p%RS`qisfUAO#1`9St65RdEY2F^f?e}^4RU|^rd~fHf1tYliHx6I zgZ{HW{J6roUn;=;$qC7yz2HMbeSZ1X`rHBdJU-UJ?-Mr=AmlLz2FZDSlnm8nq2kLI zvoiN7%H_e}AOhDtA09Y13=`uBuu3dyKP&_!3GHnp3Sp+^bP%UTZv!l8fn^1n$!7!t z85}`?Rv?AAjj|O~gi@H<$%X(_L@?-pmmn_W7&tKB+)#+I5m88Ng1`wLm2;vM`Yne7 z6t9N5fzaNXXXM>F^o=hjsK_4xSFsPzvKsj+Cfh{VMLiJpS_=-RAiEtE?S+ z1&B(iqF6IEi)Y~MAk0wp=r!vCxxe63=Ccwj0j~t%uUEZK9=6P86tl-8_ zUtCEI)^To9-4V)?P%~*lZ>1YB=C)R59W!RNGlr+j?g5#~%GrLyHLH0z5Da54WoG6~ z7ww}k=uKWmb-SZCL>}E*O}3|LYh0;`#o6 zet-A!;r@UA8-I1LnSo`>Ay%d<5X@w^DTJBfENM=kv%8`?rjB%b&fX&s{AsXVE(WgU zk$K$_5%+D!;p#ik`~qY$wCkvntB+e88b8N%f9#+kA(7$|5`C}OGnuYfc^?BE!e*6J{ z;eGl!0r)(Amht!P^FO<*Dl1}#7` z*aIt2v+|Fte5UpatGH__rxo7?%%bDfcC_sXt=*?u;Gd+y0O7b+U}b|FfEI)7&H+&Y z204g`eB5=L2*ik18Vwg~fho(RbivtSzxDpJ!QUMpxsoX@0nG)%Ju&y}Y{!M{j;Vqu z4S<)yUwx_9ulUV>-Tn4oj%~xfa~7344pzen&@$ncnOXgPSEA8D{E1-c|1A5XR+dC7 z6jGAr>TDF2k(au-?%?tSerGp8pq7}#0*No-rtWgI^^oJgt`N)8t?N{K37oF5>*9-; zAk=Kn;6(*~lF4_3sdSy)=3f>9YM)t$^$y}&0@eKtCD5+Jy_(qz(K4Mn(dwa28~N7K zyXMLep;(EXI!rfiry5Q!v44eb@`{?=GY$p>ks+j`S!2B0fn^H zm)l&uXH8O|`vcXmH1}H;Tf9_-WK*|Ol~wy>fg>2i6bkux@CJ|-%eK$z1XipvzdJJw zbu%bVP^uw!eWRhW)@Pt7)Unfdbsv~Pp&J;O#_xSSl?ad9x zoNpem>@5C~#-Beh`4`{*469u-7$Wz?acXi~eF$WG;z(i)URcWG z&13Cpz&ynu7+`8BtaoI26Uy?7TJRaiQEfd?sb)Jn8g6C!ZgliqT8>evoE?CfQTO7x z6#1Xua9rx2zSexe)v(|R1HbFw*?Fgd77d>%o)Pqk|7nsei4|9s)2a@a>NLY52~^WR zVUbkpof^=!gk}1AjVE601uz;Nw6NlAZAS=IGaC$J8-bM4liOGeYrSh`lnbwks><1s zX)TJk11eK)TS`gI?l_hbQA$R-3kYj)x?r8MCs!(DJr=k?DajP87Cr)6GI&QAc*Lpe9AfC?Ee55KvcFJl+0bS_D zCQT4tG-9p{r2kT?VjEApB_?E90F)u3MX1Q`MaZBa+i+Nqm6oxR3QV+(fx$o4zJKcA zxr1T+y(DYKx?3YKMl`@_pWSo<8mAHjLunn+C~5R=luQKQkIEtxl&XRpu|bmg30?s& zy*qOjJF>}FmVxrJZGyFbj_G!ZTUBbFF>_WOj-H7OC3H+xj>{H^{O;lQ>eUw8@o?pa zXTSN?<2T>v)$!GT{JZXKVXXw#b2*QGOtXhR`qy5&|nuP9&hBvjobZY+}-Ex z&`cc3BLQ75ci;Zun<~uPjw8R9U;D{~U~dh+^}jzqfBnU`-+P50SH$tLEyCXl0s6@Y z{PD;6g{SCq0`Nh{$94mJV&nS}d4`o{>DZAOsyt?t;JYcA>xR%HkLt z1D0{`Ucrrbz|n^YY32#l}0JY$*mng?2?6UGF^W z1#D?-D1aE+Q;)k9x;uoYr8DgAv?Mi`6>gmc+Ou#i!(e3i4OW>FUGJtnmDuGtmuV#w z2n}v`7k>Lne|mZO-M`KO-j2M^#i=Z1DH|q_vsP-wq;M3_PqAhrSY$oUW$aT~pr8f2 zUh<^QPFzx6;cl0&{g(@szkuPZ6m2D!20AC!=bKvXW`W-q;tMGBh1)#tM1q~o83+3? z93_l5!`cOPo{mJE)(Wlw_c9o5*1eaf{kw|ss#-AqwRD0p@mZZ&tSIbGhHve)yH{6dZ9Ve^Kmcsjpv-Q3 zC}gk_ufzxbd3rmydzY9W3( zJ?@PX-Wol9OANPjVyT3nP=@9xr;c))he3!K(2=-Jks3lkPMLyc0+>3aDl6wY?`+pU z=i=+V0GI~Qmzls(L}ZGjt}`DVw{6P}_g}t<%P>$gj{_wIC0?(OBTz>o^B?9h`*a=O z8$f<<)j!_)q2B$sp9k>ncuaro1wZ+K?{~a^F2Uyn;Pd!MhyMG0skgr@>BxNZP{*M; znU(XnJb!_0t9?g_mZmoVBqF%!HuKE`VstethB4*@Y91(&A&dDqB67%1R*U{ncHkmd zsQp;rY_yQMAK$ZMc5)a)j0kXGP@@3An)EAjv_4BKk&Y0-L5@IazPaXgCnD@GOM3%J zRh-1EIu1j;k?OYxI-;rSda9b?*K?N^Nn9F~%Q4KDB@2qef*ljbAOFN>&+*T{;qh44 z3J2)(z>zq^ zDTFjVAc=m1#%%clXS!-`9Cq80mgpLPb#&jMkWN?@s-xr;@kcB;&LX+a#L^l?RuovM znK~Lr0iRE{4k@bWlzuOp;BZ8oas8nUw;~}}=?5ezJvJ2}) zp{ci22lk=E#ju$`(GpTObsY*G6&=fM~ZP#F2g8Om%({~W+c5L?ms6bc0 zm#_cV|G52s|CxV!c=6A_oi(G3R3vHovQ7?zX0cdV)+xiRsHj6vxs#}wI0`8aa*NK8 zQ}r-0$-4oIn+9vOMtbv*AH_? zV8s24FCvhSJ5)InQ*|6Ha-#zd@9GSE*zvBn z{6|`nk72z3rTv@$d>)_RAm;6c>rJz)H7QKo-Bq!!dzCanf!n|!HzLSX?Kel-5@I=i zL4bYlPeV}$^d(?mMQ&Z`W#f_@@gUX_s8;!I6Ws0zG{nU}cd`9QHXE~92g+hq7sAaI zAQ4endA(60BJ6VIxO5UJlS<2&BY^3a1x!Iuogf+GkDmZSMrEA1;HvCrhB4xqGiS}J z*VpZ~-~G$)=H0+;)_yRcOdLr^(uUs(uMa9NE96o^*;4brMw|y1_#?BZ-6B zD6?kw+sY>qAh~GR%3S;tr_A=@>V{9h$}NSOi9{ad+JEnWO1mG_p(>fwUaE31-BPD& z#z-X-EK&$Y&E%{Q^GMs(HP^frGh2|DHG2c6avozFAnWzx_4VU^EzJG?x4+qb^Q+e{ z|Les+|5I+lN`_nVpcW7dC;H1}h;H7Qc3elgGEgN89dQICr8uGlT@NxaNZ-GsC9-_W zm|msKAZr$~?9T2)6sIIW7aW>%BUN5^911ce7VCOorhv<{`^)p^!8jgoDCyXBOw}CQ zc=htT- zz~}J^4zczFk2gWc!k$uX&tF7P`(eyGBjWy^+h!qumgbvBFe0`X(F{9=B+SxoqGkJb zAzZnxFvv#@sDQiS1XI{&i(RIuu!EM|NkEIhh`I*^PFpa z!FJMR6rP|hPW6xtlC#^a)%10w#qZtLP$kp=L1$mh?w(clc4&>JY(Eey2r-;ayS%SV z>!mKVJ=0!Wv-65RHE&x63M_>ankP%|>ckR=ux6J6WX`YF@x8qCz^vj1+fA9EfSMWV?Abd3VpMm~$0J$h&P^2EYjb!u5uk z!6-J?r7Ok)=HzxK(6K1mlt}~qoI^h#DM=wrXo>|4!bE^oJGOzXa{|?hBLy)kL`A6N^*vMoN9*e?aQmuG_o1Wpx7iH=11wI3 zes=f=DoF7tsK(d7*4;B+AM=*mC2H>6lW9)qiWyXNFgMopz}0#oh!Rc>q+(|g(fyQ; znFH&FitaPC+7PJDWhG}V0}Dk|Ww)|wREFDDDx036s10Zb z470F}vS)dZ*J?4HnWS$KganGdwcIqL+iU3C3aRZ9rR)`KF)fzDZVnR63}CZd_+(R1 z%79xdz1K^E;5JSq|+f*r->(jW- z#u{x6fSyux8_WqT5ed3JZlGGp3%kN|HDn*Kz*2xn6g2LdCOyFC0q-(0k%ZlQ2d0#VE2KrG+9(Wh-Orz0y6V|(g`Wnp7glEY23 zJW6kFk;LfhY|p=Pbw5C9wh3`}Zf40$=yn?lttx%NTZ6$+LAN7HGb^}ZO4lPeDsXxJ zY?S15e0L;^FzdC;8u>{5pR~{E)Tq&pqA> zWc6o1e=k%2Nvz*5IDYy(f9?Q$9-rnA=F9KuwnuAm6WfKhrt*U=6WfOEqU|Dr2<x>&1|qmnN>;aQK5iY;>V8;A>$dx)-UoTK$2fC$X4R&1tVRW=#futDgR0K7mWFAw4uUoZkkA^VXHqHws9P2){Fv*)0A+;);>_(RjseJgr{hA&)N4Z zUFo0c`&C(VyQr`fFeCSD(OYNSu8XH?E?bG9*ov8!;<5kk0CJygYsbYyFr(wT3)-GP z8>;#3>*IPm=1tEpU;W2_mhTbg9DIABd% z1(m7WK?b)t<#f>>F#=OrItp`TlC#3=J_v{r+=#L~Kr9@4Ac&c`O>2RYL`wT)5CeD5 zpKaTHKHRjMriHxS2B70OzWr{0_2{lwZ`z+9=Mx|JP2UyQeJoY~x5v9q^rIT@I-j3> zp6^=zkL|+WIX))jcp)C?R&JgWY)~sF0qA55djuw;&wxT z0}Mo<6(EhwsjicC#WIK`2@i%P4UNn&vK&>KiAKO;1;S^y0I+IGu-r%RQxC)xvbrj) zCE*Dn!Vm%f`QmsHN9A>AdwG$|ICcSeEbWg4d+ETJyv)*qQ3kpZ}XUQ+;4Rpam{w@QkvU}6jSI*JMAsn4#`WNs4?^o9Er|$WK0o_+i*nO z03?_5ApwN}!^a7eSrbpn6yKEnVgkbnDAYj0TJ~sD`!; zX`GSTDK94qo$aJFwKRogaGGyTw$PUmdaY?IV1PBV)qy}1>X^!@S#`S!iMZ55&Og1W zede)mzx(y&w_o8mfUZ%d=Lgrr0(+6v01bpYqhy9~=Cf*_63nW=}JM{mgfh*ANI zi(0ZPNCO}x9EqdQ9j^w(t-@@;YuOKmMS13Hbj`wrhWRUZ-b}GT!F(X zWjk(iYrtac*Im=4R@dVlWD@9lHXbW0+t}(6Bgz0%Np?Bo%2T61i1wU!KmAj z*PS8~VWe)eTU%_r3&Yu9nN{L1SX{i(@FE~<1f5#oR-AMn-%382RXM)?3K6+qyR`ZE zXj&eEIklAYlWs+X;{XGkEFNF}5v`)4?nubdk9i02*<{q16DgVW0mGlVuMwBh)^SBaQ0-d{#kH; zHTBj%Uy7w%N~1D2)K*rOy*H1wwM5G;k^bvSRMjynXZKiWYH2}bAFFT@ab(S=%(e>z zBxvqOBSr{K>6lnjSDpyV=eL(ab;%r(ZK_Otwxp7>39SA(1XxqckgQ^5)sc?EK}eai zR0wZf6|(BE+k-k$&9)@&bfx{30e~Tv>PSt~sS83`NI z(2?q73+vPE%t=Zr2AvfnqN+@gV6;se2^|x+q?s5^*S8OVkRk)YvX^#9QI&}!4e~Jz zNxi{cSb@@(+k%h}JCjuj4ivGcp;C--xqpUnH{U$qNJw?eDB;LAe|lAW9$b z^T)*X_k>=5KKMOfy*2*gjlMs|{0B*cKG1kytHrI=H%$2gd!zFTYS7b3ZPZd)*G)_7y>BVa4aF zhL##5VzteVz#ZtK>#u}DwXjmDXbNCAE$Cvo{O2^F=YD%bmCs>JcRko0VJ&7IK+I)Z|ST(X%UAe7ZTbDDf9%J+BR`#ix zjo5+~Z&(cnLesd-LU!)4R>P31RJUAc;&(%>s!GN^1qi!L6{jIQv;Y7g07*naRAD*M z>KCgaKy5>NSSxjwx9arIOVtudwubPU1%QGX(fe%Pg3TkM4X<)mr3i#*3oaNI=w?t! zv->BNB_tUtAlqBR>*Z+G8vreG)i$*}m(K=mI7Mlj3++{9@jUT`j_V8PGC>O6I|2nw zb*=r#D6O7aXTRZup8^dKLZ!G1Q`jQz@9J^x-@K|fhqCVe?O*Z5y))ypMTovrvrpxz z>4dfa)fgVhI$bLlqTmcI3#c$)ObBU20Z<6gEY9LtGXZoImdiwpK*$QqUJEqvFi@yl zq9{~&F)}Fzg9CTZ??D{jy{coDT7L{uULQAMfBAadQ~wBKeQdrvdepnY_8;kZU+Kt4 znAZI8DgEVdKiDPwef!{Z0`Pgf*AYMKp2}~(t$mLz7W5=r9A>KCTmfQSaCc#ZBk_8? zS|&)Q>cy}U2TC}S$4MyFa;7p@&v@eHts@whiK20N_zE zmyVNqyI9tWTFJL z&SMwa1Cm%!w?|feK8;Nh3t^{KL(;P&ZzpGUOjvknHZ}rF193XgCfW`*p;;1UN$Mts zGp6n*jgTy~FWcpcmQsZb>aYfj3XROJJW{i-vFFN*ZtdD^1ixEGX{3$LdNb2PKk2iE zRo*o_fX7}ltnyCw)7&P!(8GxbHBE0@Jcy1J*tj%6Ya&~5-uvu3dHhALv+#F3m0Zc z6QP3C25GIJZ$Q@+G6#rh8xm@ox{%Q?uCU=iWhaGfv|*b_s1@`vu)G4+2TRHN{i@W% zEpKz&KNq83@7HhNuyXs=i?9CI@9LP#3Jt2K+ox}ZlKCn~k+5bI{P_uKi-byL5;3Z9 zq$CY-a9Mj48}?R}6GtMeBn%4DG2uc?5QDAOYidxw6|xg`9JuY43qu4_ItmCxV2r!x z&nfILU(!6a43$SNe;G$bZub{NiW(QJlix zKECgy7q0GaKM?Ub0r))L^Z1!Ri}xd6en*7{IHy7RnG=<|-J)_lzaRHo*SRE3>DUn? zqKUZz3(|06*tSWOU{H7E82ev~ncXcBW8GI9Nmc2f+&M`}uGMGe@;z8R3%4|?lu|YkrfOE% zAj$wC?_0?)VZlDW)>o>MErZdn4?f}ElQU6^*IahfbC<@a*1&ii3#0FlQ7BCawdcI0 zvaD_|Vwp`j2{PU6Q|+ni1Z(@^UYW-?FZp;Y z;_hGnNdy#6_4rlMz`8rUX+o&kA5`i-hS7Yh_gJO9^mzF5si_IE_~-q7Yf|~c1S#AS zQwrBXxX?KTEvGYpKxO9RjaNb4kyMqCZoJ00Y|rlJF|XggHgXJeLfWslhd;i=Esw{m z7v^Vu;_!pTj_;1eR@?HgjZY@1{acsp11$0PAM(e~;18eDyMFuLNeDl2oagsB0r))L z>(I|CPo^pT@!JSiP36uq?m$S@n@8M^5f_eufFZ3+9S_%F1jCsAu?>YlgjmDjDYEM!r)GZHAx zY!p_ic_1cv8vG{b7^TW;B$YckO%_L@Q;|}86ckv6!~Ou8s>(v9+ZMHG!OJtDE9EvE zt5RjVRtXipE+=vT%UCOBW-H;8P^GE7rc%5NG%#yXpEFyv#!P&(WlObBl}LictdY`f zVo%L#txb#4ES)ZEDAhi-r=@SqSK$C?4g1t|`i7eJPH;q9rt0Q?nB=phs9JIqtG`2Y zAu?4~Pm%USe0lNsotuV2|6o^whzsly;!NJuxppMkTQTsl=c*f^< z_1(kqU$1h`yMO+Ad~wmtswdxHITW|UF>t0mEt%CBam3ijhzJUKOi7icS(I4;y)^7B zzP!)lK&WL4C}2;*AGzMepxIS8FGm~!C~pU>N$gLrDNIC!Qrq3#*e-e8_LmQkl%~<} z^`BnW<1P0c(>59ZZG5=+{DY4l8|J_3kN+x-;O8IjD{%ivF6YM#zV+{qfBlb`sCQNR zKSO{%g7L8=cK_E6v0lI7p0QmtZylJX+yT(_8rx-T8xZYACDm~yA~+Z>I|VI*V-y4! zr0a$YXTn9av6Q}Cc!XR~YyKjej;pqGq6L1~m3pj4Q)~V}DJ%tR2&h$c2GC(N&`6k7 z2zSIi>&f|RDe`L#EI52WxpDGt4crAfGULJpulG6&^Mo#20o!(|iAL}%$1cK0iLx)obTHo zhq5Uz+AoYfC&k4IXhe<$9t$g~)_Fb+?{2iTg_8t3L@U$~>U0*-GDK;BN(a-H(W$jM zrsB3*O`2O;hOb!m(Mm7UTid0CLi=8=rU!d<-D^6VBs~jCmCMm}b)qZcdV<{aZ|%?Kxmr{e?`=5$ZOjrpGrbWh6l$-<)<8{FC3`O|2tCk! zr|#3KSIHuzHnKC|Z9-1HQn0IWH|tJ~uxSxsc-}|rX3v$por{fS%-1c30*P6+ z_i%#mu3A-R0uXdGK^y?=JaA<7$B7dGTFutg0d|L{kfq0iZcIhnr+|tWQayY2oZJ2J z>dpN2p&7*br5vZ|3Op1SW-@L(t10|sg72yp`@b~Hm#MTN|mZI=XCiW&WNpH zpN6}zPs#pGSZykl0sjupx*GvNs!YohnKVKSWw~M1Q~^(ZrDoC|Qcxa;F(-ZBq*)w^ zIi20XI5tCNr~;}gj{*Z5O$fp4{XiigRHE!=0J%d_RmbdTa+apk%v4QxPMn+&!eN4k z`7=^w062u%mJTwN|0!ulpQl6lOo|;wYi+z)QhJwoS2PS!KxP2JzBdD60Y@QG?!1wW z02ElPy^z$LdUMU|#AO?o`}+1;-#pBS`+xbh)C6$vXjU1(1~etWKqj}z3=TtJA;)}~ z#9q2tGL4L-(bVE&tv`k;K^hdH6t3yk3I;Kp+AR0+fGD?73#xtca1Yc59ZvEMk<7a< zp7GiJeEs_P_6=D&4oDc|c>QL7{ZMl`B>ctrun*mj=#kDp`~Nz(kLVQN)A-m5sXuT& zKkZb1u?zg{1pG;ik0+=9spEZL!PCLA^1D}h{xwu=2!xTmseMNTFB`XQ72|-JIBs}x z$2oGJu3$SiK&tj5E}L8Wp@^0mIiq9Dy z6`{DluQ6uM4VQYj#>9v*XVNMj=^8r)YZZ1N-bTC4ZglsX~%CG6n3LQvw8PoF(swyH@6%Z3RZj?#eG!g#Gt z;$%fnKw=P_m63sMOQ!~*envd#tUQm>l=hDkJVNuvY>M;B$WEL5xA zZlwx_WZ=cR5Givq25j>@t1SXjhNJcVSq-bI31?)L&eE(4KRmC-W7roAQkF=iOnF7x zw^@Z%T`p-etS19f(U}jbpkU*%K%hd=DnRW7>w-F`x&t81qhbV{`u2Vo6!w%Ar1X14 zpJ6yGeOsky28JTe-I7@>)Y*{p+y=eGB!FOqP)Ij=8si7BW?9tBe{8Ido(ipkSS!8< zp44?*!Z$OUitfS`S*XL=>|5Q8@LkU|O$ALRAqN$OiWoL@h!_w`8QqL5N_m@O&++1p zUpH@~WZk-$(BD$JrW@c|1rxb}yu2rgi)TgN349I?*Pz?Yip6 z>UwTlggqE6YTq?3yc=C#91c-<9zViR8HZsN3wlJ*#<3`r1xrcek}WB@J$tdwxjnno zob`Cyu;oAj5kvcrjCCEz1MLeK?4s>2X$Kt`p5jj4KPD-i*K*>CTa1|e_ z2LGHGqL7gUj?poM_3n&?LNOV@C=Rsz$U+vCCYPaDhayW_Qj4qI3nTy`A|m?|oMRLc zV8mD=vW-r0++3cSekeF8khUnZu*vQQL{ui72(HeLVK(qu#Bgx0tgN>5(&qk+0HmQj zh$7q>P}&yF0z2m>0~iZA1tiY{Y(FSanS~I-76t^7YOzk+S_P!i?0ic~Ow_TO2-fovR7j?KbZnJ;s62ZDz+la? zE{{&7&Lg{v0_)NS(kxnf0!s;s!77a`D}ABBU{wu7Wum}AH;GyflL073R8yA2=5pmO zme~ziXpcEd`VNCPLeo?D;)I}M&Tn7MH*Yi=IfIl+y4?iuau@d-M=Hflq@|u+Xv3GmLhAz&jb0#@_>LqrI@m`&i>ZRja3Ig9Wb{;* zGJR38mQ6BBnQ)-VYMGa=%X->3m8o2XSJ|OpHUJ1}O0-Mb)hkm$BF*_O82#1F8;gp%ztXay-3MzN zmzYwcdY0~|Ipx{%LEa~^3jE46XS#CX-z<03ZL987_%py_|IK6v5Gb@;Sr_2RB9z7T z31EVF6vG8|yL^;tP9|BXnkIp`Kv6UEwzDuFW-u;aeL;@>)vFuF-9P;bM%47}DAzbV zm0inU44HMt4P-Kgs%GhyJWCnjYm}$Ld1+Nz384E7d(p1G2B>iQ545faEqrkQm$L^85?kD!+Nfy&BdSK;rs;ey{9}|H};ieUG;H`&j%qqksIeC-wvH z{ll~WBiH8pj?W){$$MG#^Pq1Xjqf<0A9u9R3Bc#^!N$+?5g;j3k2k%1UDpFEh7mO1 zJTkRiHf%!?42#l*9&R-!V^|iCu{Ag~h^oBZSfWXW7UnD&e!ZTX4W1VzONi{rJv%l^ znb7I-a(4(bX&z~iBNV_OO4=u8QUQ?~O}B0I2~~xNwho9V1)E_~!JM@pY$IAV^o0g2 z0RS;LV$pDyAf^F9eu=vcvsHxb1u&jJj~F$xZU@EM$jio(69B6xwYGZ0ILdT_08rWH zTm_5zpwTr=|7CS#*Tx9Kc^ZUT9jNo26ENe*-w~TN{(&=TTbeLnJHk3;I@c!}8HIj* z$K}u|F60J;vNDw&J~s&=3%0j1YcqvezG}m~8sdS5h|zfHnE>5^a4*OZBTM*D&`i@m zl*L>kk76V#+8he7zLTH^_O!{gu}|5jVK{rv?DfnfEqiHhu2gE%S(CQaT#Ksm@;s$I zCFNo!WI6P#Oebe;oZQ-!Xld8X%GN9rgqoRCY8|jz=n*&&xX# zvutUH%$lueq+{k@SQ<^;p<&o?z`~$*rGQjNx4vBO*OdLO$fyZ|CCnqM@Z<@g0-;JB ziGt4%l8%HY%eqo?qGy|phKG>tPVKwiPPjVeV#$L5X0nxJ?H$p?7^H^Clv^K_BEy!z zYDqAEmNglJCFQY*6I2AV=9`D(dJVDmjQg=Y+w#9&@$q{3`YWj-0aeH0CYF@(Y=F^C zBqA&pYwgjRQ`4xPIfu-LA6Z>YEB$g z$3#d28o|pZG9sLaT%O(CefdH;AAkR{*(pgx=ytn)_qucQUux*@c(e&Pc?<6)Cj2NM zKz}tL{+Z8`fAPhS|C68k^B=h-e!{7J?|4t!|1$*W^Z3Mvqs(L0%UAK6-xO4H_VfF8 z)3Nh%8PA_38EiXEX5n_=i+c`gAx|2_3Sy?Ru$`E40?0yxvw_CAi&QMPB-Na(fo^%% z30$d#vtCs;00^+p+YF$PJMqOzc1tLOyTfDi}7ql@@m9_y+# zTn4Q4lb`Aev#u579@Vj$B^By{h|Xc6@?FKN^!MT42?m`i4= zw87j;DJx}o_cmHnKb=k)#6lQ}E)-fUb*-&M-L3N|SeY zDz*TW)3QMf0jb&?I8u`!{f02WhEP?w88xe>7)VXmiXF0fg)6fqn}9CT7p3Axf!zly z$vF5H)f5B_I1?6xu-~88*fnP=3uA2$xq2O#C=}fgXhcm&6;gJ(8+B9%*-HtN#_)tB zF@~uKINU=(Ssm5Al|4gTR(fUvfd4;xZ`x!>k{k)Dn#X$ypsKs8N%rjQ`TzgU%F2rD zj_h0<4#{cuQ3rs`eBoxgAJjZDQOLwqKoz=NSfd$uUBikbtP`n|Xa*+gYc9ZD0%mh~QMdykQK+h%wrfpu<+= zUUj=W@6R$nEv&BZYgt&sA2}Yr!)0$h&@%0>76!=rB=$2FdWM5zXaE5B#BI91j~WG} zwt}OD+~KPbsw$UaHAqR7`>n+$2}XotneL}QoW>kE>ECE_Yrq#8pIg~Vh^g)TBnM+7 zZ&P#PGLFvX;2MC1hH%QE5X>Vd2hf`fVHa zbTGK!ny;>wR)>FO!yD~N*n^uDb_%3Jn1vv!w4mW#-pfdCu&l+@0rM`F)vyUINhMp< zj=#^DSrS!Lg>~^8ZsfXH=`cJT&hKIdj+m+eHV3iW_-3D^dbp_EF*9mlpvDO-7o{s=XHdS#s@Rt9GU;t#fH0%}S3+ zxlh;hb=nOL4$8_oXD+CfP^DEs;K&1HPE~f5I!aY_*)0hSqqFVWI8VHRiO!x3bkYDvcXxMxzK{*2$>U!;MS=XlhnIT zS^~lVRjguG-A=ev#VT!Po~cJAqHE3>1N+w zUR#Ij>epWZy#2!;&rhH5G`y7iA&EtzkPUsO0)(2wG>J<3zRa55lZbF0lVQ}PEPrGH zU9-|tDAi2dOlH?s7Qh&YkiCy1YS6w!>1H|_z}R98M8s*c&qV3`)w4LC_iw+w{qu97 z*bBB!@!XJJ#l@*(`lT~^R&g-Dt2KK(B&G8 z*ha-}EzOD*Vh6Bacy;0kWyWBm85ZQEJH2o;2CQI;m%>Snlyy9v(J^LV;awcGt~Oo? z&Eh_JI%C!CQWXJ2pj`WA8YVF7h2faQo7L2kt$nDyI~0B}tfK;OmPd7MA#7(S&k zfZK={wsGA?3+=gBUD31U@vf2rG|!qC?!G6;p>zwRpkOSxX_}H$VZ3}XqR(rW1t3_J zBsBqmgRo1Js;mZJxCiXygh*DzV5OPi&~%}RlFDT);HYpwSd3~dMSS~pwb0&4h1bJ4 z;8YZN`pR`$dOWmiA;3g{3yY%*u31A7vjtw12y%cr;qC-(W2>B>(^*07*naR54wBtf*2kV?@nxO|*Z72)_^!c8)TKqcuShKnphhF}~~iO@~bj^(z+HWE0UHrJIK90aa@^|fOA;lKZ$H?w_d z0DD#wO+K6U)(_n4tv<#@){FcXa)o~M*?#L+{N4`l8~Oea3*n<%v#;OpJ74WBM*DkZ zLf)Dgc<=Tl1MsDN%JyLP@69`W=!JB9spl`K#gGGP&wTzO0iL!PTWlLf3*G7Z7PmRL z35Wpy~oh`q44CS@zc#D*Z9DDVTJklNLsq8dMrhP!$@t%DL zb&UdQn!4l^muAvMucjbWC5?H@3g?cpX)BJtqg0o<0)>VWm6wVoO=}t-znIfiEGe@t z6NStKfB{pvYfmS3@-|^@S=Yhv_&}cD0IlM$1Yj<*!X>(9_T)~Xpgk!dsA>9dgL4Q~ zPG`4Ng&`8iYB2!IWH}eq0diNB=+8I{W#$G!C`4Um`_HX+2_^$!sZHl_Tup*N?Ym1g zM+}lROP77_x0K?9(>G6Rzuf-uyD>IbWd<4)7^^xjBf1huAr#1}oLaOE1~hk6DX{1| z3JV9AurUjny&y@0ku+04d--euCCC}km4_gZrI*t%BoK;-s}q7`)$L{Dz~I$4zY_Js zKm8BwiFN?=ImZ3pH`e?SZ(v=TM@;4C%LTmeZRm}^e(prWz>nSNf#HRKc)se zxcRzM*wcl{Y!wQ?Tn4bE?3ehcrsKr^p7RiMZI1pNx9b9t)M83iB zjgUhK+Hs@#A}WQ*2n^$90!}nEI)#+;3Q}Q06b5>Mc`lvsP(dy`09s_oc?Lvd%||$( z-p4@N`WHblGh*{tu}~<+IAZxI*0`revnZfukY|=Bs9NSmFtXkb02C3eyCl?9M0EB< zP)=Z|wO&^iOU+q_DCBzLOHxjAMPT+vs+z4!*U$9|fJF8pl~)BfHg_cpKuwFf_Skv+ zNSN&#BaGX(a+H_9O2jDF$8eUwdA9<{DyKxsva3g30=9cv>!AQ#v>>d@DK7zz5DO~J zCWvFrs}^OzRx@>1k-+pDU{)3tfl#fg0c!9PRx+&<4vxKBi`Lh6?vEocIZnZCa1cpM z%$GNVaoVopd>Y$HH9RkjE2TlarlksYNFp_zF;}24cTBh>bAL=%PCyr4h$B@~P z#rfhT3L{3AA_f_7o;nzyZg*XdVv__w^XF{uE@X&0QcMI{PQ+K6Qdp3cLq14r07yi% z1}2FVZj-Z31nH3BYvD*iU7cspG`^B5ix$p6%Wt;^WiA|pX#fj4V?(4d%NDz23;7t6 zjR;+xSjv}|QsEpD>iUKl+|G@nK`Z<5;AxtIi9R7(XdkVa*dh6Q;-Tt}9pT0nP#3NG z;@knO%F=;qAG-qll~%c78zsKMB3pL_=nAJ&`(F0p*mZ(ET!n0aJ`{$|8>@{eOfrc{ zrW(8>(ds%3a&*!oRR_$yb?cXc=YnxNE5cG#SD)=w0U4&>;d4jT%iB|#UEG(X6qK@Z zpUTuMk6}4{eq7AK{@wWU>>D@5!fut9Y&CMd@$?HHR#iEtY`It2*t7tSuhDnr(q5Hn znaZVbDrlxsbttR4nzHXP%}jj1c)=3^Y_rzW-QcyJ6kD<@5Gw=Vj|1=A%~ zUr?o8NG!P?x=$DSiB z;$z;+TEJFk3Jf&#D21uefF)U-l$h;)RA$ZD6y1XMtWrcM6PYa3J_*GZB)W*T34o`|jqCKU5`VwRQWM zv{yRwOLps(Yrk(U;^STU)lU5AkNoledc)x#s~d0K_l*Yq0~_AqLO)s(Z$09tx(y$v zeaQg4S9{+{`{iBo&#`sX_0^5EJH%x-9=mVwyvhIXG?9=ir_6EHi)*oBP>$*zpqU_8lB(ChzJ&u;wGsDUv28 zI$+uR+nv%Hiq0UwC8(ms#nq}n%Qf?&YJRCFlw_o!!X|{X1dj5CarOrK_mF~ARMo0x zR8=5dp1GDf6Hsw?gC1ugh6CHH5HdHwzG5AJ2)9w$0LS9r2>Icl|^I2nFucDnn4_h zxRg?{xk$EJAiw0oXze0;iPE(AC7RU88L#DjeC-9H6vg)1x5$1cDGKxUHl9A2w--q9 z^s25e$WCn$1VggSu(M6khWX)OLz#;vG}p_k#4#XQlGt*oo`pLejD*|5-g%@JJ##{@ zB!K~r=vklA%ZZXg+)g~5wkN0UtE+msGl3DMIzM~H)78yC{xffK?gu>VojwpBx&8Q4 z_4)^0c*7q*anp~k`}1A>@j<}-&gkJC-oE|KmwTV>?=(2?J9D}_)h{?QI*B0;GI#%^#jK&g02bF=87C=fRoRw{>$% z4iP0Wq69=}z!X5%i zK?Yf<+Z|O(g?YsOv^%=YkSLOyKikm;W0&TER5hpku}rZ`qjC+m4N!kT!dna=097OJ?dkv^#f}R?zyddS zOxHnu64u3ET{rLsi)U)ROdJ)d98#XsFZd$y>XLJ#Z)xs)*}p;FR7^o2d$tZzTlX@9rCd`?K4GGueQ_{4OpnT{)9~iKI62Ft1}>SbM^)TgRL>;2imNprBYR?+i6Ps>3l9Io?dNFp6oX-BVs#kLY{v0^!)YH@BisvIcvY% z{=Bj6SKE&-tv7wYK2|3`7S7-kHokKwe~}Eyj}i_acxe%4_x=buDn-n zi?99gQ`?sez$b0L)O+i5Z0{7Lcvn~a(VaB2_C2`*A)Y7_ot^P{FLc zT=VHSu|@54zz-rU9|I{1mp!(D0nJu|LBIUC?Eh#z!CX=_Gb2k84sw?zWz?XF0vMYzLN=Z0DwS$zkLAowJe=Qw^KJ$eC+_op&-*HnF@}z+vx%l$lBatl3dzSy>XRl!em~MG$3u&p_&^ zHg z->nx_Wtr=IS75CSmSeea-r>`(b!UaHCa(i3qvxHUs}{3@fFg!z`n0rbsU?^Sf?^A0 zajGt}W>TE<^5lHJ8HDRsIIBQ$8*&8SMeI5}K%jN2YN~E`?%hi-3ijh*-h_Zg^aZIZ z+)mt`Tie#afa{|E2(rrn?DdN243ES0qUQ(ADr<1@zot}L?f4I5+$3I}I z_S<*)9{g>cI61^j)0}v=h>GE%XI{)wgM~o9dk+F@cVvsZP1GE{8X(BQB@$1DzxtGrtskNQ zW1!_!E_(|;CWO}AkPhi+fzVj+dq9iATLc)$YyrzbODnqil|@n6AxJi^+EI6tS`9sq zF`&A=AvnO>o0r?OUyH1{hth@>oJa8Ow@YUr~2#5u85CYO{4HMgVbUkM0Lrl8p0OykK zTmytbwVW$@x02|=2ZV~kQ% z4BFI21Vy1JAy}&D1qn!+U<}9g?f;Q!PeZd1Y}CsgsA>j>{ID4A-=SI>Pp^x%wz z$YKCR?P=dtsn&oegv{&(X43ES{Zr^^gL47v04Zc@a+Dy?RCT(!nmqJ8`3umkp= z9k>g?1>&j{UKnVQlD%b8yXQKCqtwD#uw5@$Gqq+}Ma-B$Ec@9-`%qA(Ml=_QOF}@( z%N{NqHoP`Qi2Huo#walZJdMK6=4vOgkcU{$F~opav`VjCnoFUC6B;eZ=$dqX;0u1I zxyvf)HjP>&*E49A)Z*uur!EzRdU?x~0vH=Gc-pqFp4QFG>pgyTn)_`GeD%Nl?(+S& zFMt2%x?JpxeMTR~Pxlpj{94`g$o8MF9T$H0%db5@xX*7o@KX=Oy^|l8YKLe2_2EeW zgFXOXInSG}skiU!Ha`06kA3X>?Sq%${fBWq8>-68{O)^P z_VMJ|cydBSY+LU@;N^8x#Wpws=C`>*XP<_grQ01~j8@3#%ecyv#WgKcLkrQMO4@S; z`;9Pxod;jEqr(wxoJL!W5^6759MD#mAsI`m$BBnV3=0GouC|G`9sgEqnrOL0cllaJ zM{O%xvF7fqZI|P6iE*lVfe@z+DmBDHSh6%Uq`@)K<%S#|Mjfn&TKy;pZ5G&AKlD2N zS2nt%_v=0{C6t55*ps5;_SOlFXm3WZeZRXCTD;$uv0AF19X;&?$hzPHHuQf7^2Fkm zCh5xM2jnofFY0h@Uzz|4>B_Qe9X8_`5Lj^OF)FNYC|O{#MJl1Ah}+?C( zuIXEDIiH0`f~Jg(S{bcAgIT*PHr-R!%Ax9dTTaWgew2u^(o`vzswozjC7~)zA)A`g z7WGkEq9Fgt6o7R{?XaaP?WV|M>*>&J?l=-_@WFx#U4_64IW3ouwK{rP^n?n2cB*MM zTp6JQ!D7%?=bgfjeKz0+ch9e-cbwQEgz`!NYc_Gb3;ns&xRze+%DEly3`j^eaoi;1 z6l*Eg3aw`AvI%Cd2q*-jyTvv1@=}QgUrCIZdqqKe=4FmxL~K_ljrB@hVg`s@O6##TWJR#i`ggiG?GuHDgc!{(51i6z)61cY&U=bj_BD4fqv;`H?SKmCh-xY=)Bex8O2*Z6d3<|Cc-{`p_uc-KCwF?p;reC3~? z_+|fqzdh7`!c@KGHedUlkL;jFw*UAfuU(IK2S0S^2j1Ej4!}og?>qYMx|;qv+QPj& z^tX@lF6&S1`ogUU4FE{QUKPVKYYi@Iu4-+a98AdWL8| zB8}iDqq^KtyGE*otSAME7Q1QtTbZl!!18CTv+gl3SCT3bN2oAc1Qn|9+$%%Y8IrGe(k*>I6PLsSV<$dU!}D}hwbRYE}&kV*SF znc@1fSzSC#ZIw2BKLuIUzTmRK1?1Y*ma08ZvCD3G3|8^r+LLL|r`iXy$?JLuvp7pL zYi3BST+))MOwH+IJM3+QNxP{YD^+VNC35jhJciRZYmF}ih^$7r&4jIIKM`fbIhcjL z7WyAiL?J*`)zY-3fGMUVAr?|UQI}N89#0go7u*d2aA+S`^$nDA(c-FF%y9Nd`dZvZIBG9g`UL6qXzk!bJ)* zkhizC$3e|x7MzFWZbfxR9(P_O;^UL*CG760m)AM*j;+!;Nz z@eN(j-7hP2^Z3c%>6FA54!|dFpVh1D=WV+Gyd)SvU{fV|wslV>=eRf$YOrD_GhT=p=Iz#bcR#e=tSgowMQYT(~TCqcS4+gLIUt8!NLd~?LafE7nCIq2@GdPF`E4*pkKiaj?^2HkL(5MU;yI+lj z4%0TlF$k8y3=J1=*-CNMl(w>tar9f*>QcKm;@HX%paf$H+Bj!I2U9KRV>hD93@eDi znzbq_s3Y`%9R>th zld5xHo$%|-R|yEq;;-a|tO`{w9S~F^4&I1y^%Qd9{(2RYl(`pnDebzN|2<+vY>*i00A^lVsMsGzhCO#fxxI6&5Ve*@Qw75r~>o zN@n0?Lc|F;jWJF-4}Sf$ZZG-bHh%R)!udQ-zy8(y{`$p#{e9i;&FA@>`ilDfr3VAR zYbXvo_So&`9-;?lWp(lGk-iU@RIc0BYa8FIeXNms+oko|5ASiF54frK7>fIg=1T_P zqqM)NSJaQ&gAVU2Q2?jkKW!Iy7clXaoR!Y1ea=7qIdhJyrwky1BQT)B0PFb+Otl1L z6@syCu6q>`9E4EUH;3j)u!GDvN+}gA8ich6fS83#vB2XQQ^zKeU5s~yl?O&BsL{o7 zYoDAT8QT!V&5^s30S#!VeGzRJA2D@?QN_qqrc|0o9ROQ&wrC4!9weHdpv9fY((SDT zUOf>bXVTF}5L=_hx!@l6LJ}Rz6j7>{fIRetOjevLAJQIE?*=S+9~Z1`x^0U;Il%=jm>jJvPYVwK0{WVi9v0s>}X$gO4T(D&O4N0QUhD6=feij7G z!mKhJ#kiyrW`&?Gf?J)}Dpckn0##_?&YanEYFtwaRXHnj!nd%e2hEB)%1W0|C>lam zxmNPLbFl$dl^uH>xcmcgw3lYKsu6$$c45LQmP6s90h+2b+t`^UXVMC@=-fh1Ecg*i z+KUZuTTW+Dm+VmmOxJ;AstOShnMMHi^WwBjnD^`K1n4A&QY;0dssiLpndB4f^%fYL zZuc9G(Uf{?{$xRPF2Yc#Rwd3A1-K;k#Z*`|4bZAq?m!p!Xt&`evCAb*z@+|3m`^}?OZB8J)f_h?tgxP$*ZrP%**ZRYJ2(*e}DTwet-G5?^?6#N1xWW z#)5zL_DCN-v}-Hgah&5vCtB&HR}cC5n(qGhzE5)h`};|Hb?>?#e&#n%%6m=O8(gn@ zgY&NKwMjnySNFH+kt06*Ir!oLd{*r*&yje?+}q=8_Y<@gh3xR~KKJi`$lIIq)315n zFaj}xn+oEpo!p>%UfYw6U$3m{Ec5Sg{?c&KpGH-VkTUASnE_R@8h`mp|OZmxKyWrh5iN>#=fyb$7ddZ zbbG;N=NK4alssob5Nt*)MHt*U0S*RsD%@qZ945s=`IigQnm2KX=75&EZ*s&D%iag0UY%A3?k6CiPxz2wra8$0^b~yUHh%B>gSiAI> zj$BYlvuaKxYL;fZNo+E=Z>Msm_EK7r66gsj)hdCpswfbKMID~Qx@bec%7w#N@6Db1!zB0O;qm;HQ?bnlRMD z0kR|KXS;2W&V?;k+|vxwTB71!D=FG96%4{93w!CirZjY!dT~Js8*v_S+IZehzkbS! z%XiP?e8Op~nx`kvYJ2wbzx*CE&v^g9igZF;@`I{d71-SdBeDE26_WfUXH{WoMV->#RrS#y|H+D>~ z{l>@s_(AK9_~HP3X+N>O{h?8tX1c%Sqqf%>23nt80F@IjZuf7$i*X*~q)GxKP8d`m zg!$qn!m#=D?XPaBkBz8@OKen0ALVM+ijX`i*HW_8}6u?mj2#Uf~S zTpEpl2S-4v%dRrrvryD*fy@Kr0@5YS(2^|HS?`yG=2EOT-*GxkL0e8na))sS{YP6Ji^hwi_%ZxD>(LGpR!-TU-`-+l8>eVvRO!8@Un9j<7bl zts_`DaV?#4q~9X_zV{`jbf?8c;)t>@$auAT)+AUfpB%=$kh0{zq4~XTWZ|7;SRGWX zf^puJSb%I>**1<JwC2 z)mt4^^}V?65|@Qdp)BrRB%)TTBy&M)=B&&v8tsRtrDkRM_SzFdnWl=y->cy5xi9&b z6iRuSHT$M|J*-{{p*`1A2vB3<%lPwbJskL@_U^y^!|d9O+*U z+)N}&ZLrEWc<(8oJ?8B4$@Xu1z3p9=xIKi~H33S(r0eyn=P}WFEf@r)byOHdV?lkH zJZ+&^Vzkwc3C*PFya5ambMb-^u$NWT-ZdEx=vpYKE|x48jwncCurg~V1yzo*NtKri zpco-l*Mg8srri?G)03Sc9*Q($n5)7QVA-@nKoZgJiQMgUj;@Y`+R`~UdQ+NW|p97j50uaAID zK6!hC75Dm|__&Suu2*~gq~=g@KY9!gQ(2jHeVs3Us9%2*PoBp0b!NuEg3nUdFGGK) z(^i*}Gb02NTc852tOQ}IZZC0qf&pujtH>6{47C6tOHFMQfHEUP06QB_66z^V^mII##T zA+}VaN-dJ;a!ro?J91eaj}6;u>oj&qS`V}d(C#!Fi#3)$dTUoorAA%Aae7BD4{W;x zrp>!xW4ueSG?Ub27J3}g?t@xzowl+XnkLpP1_??DMU?_QxSa(l0Hv~Y=z_;kq@dt1 z(A?tjti7De(SlSdtE2WpMGG;qs!g+*R-0u@z1tgrD?R6&et0|{Wm%ryDeS#rZ)%Fc z>@Fq%pt1FYDus$^^0qXW(7tOs8)}6vPG@Z;ElR_#~ zip`Y{phC24rpXf&$Xei^(N4OQm4cW)*I;Kws(r}DIHC23nzPe+89Tu$KNsbx6e4UO zqn5|+W3tFWM6YZbFN89z$iaYR(pE0&4%=IXMaoL-Ckug7c7DpvmAs3TRT&H>@{%i? z0TR{An`R|JQXmjrg6&qE;i2l#a!`F+YPa8sJ4VgqO=0$Hc8Qj@t8F4OvqfEbA%55) zVhapzr&DZW`|3PSr9U`G_xHI4RlMw)GscENrU@7-Bq&8jwo*{|TEOD?9-d!VK&o8l%O@m5m8R0G%#2l+ z^UCzabP&O#qqaLEyz7Rmy zChHk6w9L^n4D@2H2Y$Tk=j$hVL*QQz?eNw_!H+ude&ch0R<?Ejel=5&$uT)V#hqJ-yNv*h87I1!Ay< zFl~Jo6e-;7IGt8F*mg*pYc>ly!al;F9VIY}BQRXv^R8nn=8P_M1zTF6XzxQ+k;QF* zbnMqI0njym160_OZ}H7L;Tjv1)oOjV=~hfp4)|FibeVlX1#c4q$5vxN#S&)vTdkM4ygoNp*UWR2=A~4PMFsq*7Cj;df zB^i}sLMm)gLX1{xy(@g-`L%l~D5V4x7|kfKgDI~W*C+^rs#UQ;SUHwPm#r*^y^26t zTy=S5C_*BcM_9d`CIL8*e(m;`y1sS6ilUlfJXX8dMz{$rrDQ?degvTvi&qt$PAE~M zX%C*yzIx}EB3;w1YZ_!#jKM9C6PEM3QpBjDs)`)vQ`IfOtc)!rF<@PwenIl^*41LE z>~Ro;L7`mkMAa`LySP<_y=eFK?)}hWWa!m25U3(wUh4S;44#KhF-|8{Y**Xq*;o4? zzQ^^{v+Xp_S=Zz1ulDbszx+?X&&z!8j|<*s|DU0Ka9s8Nfk$Yix9CT^J%kSWq+{{s zyL#}_{T_UE`y0;l+Wva|ukU`I*W3GAA#b=u$Kl@Z`Va5-4!geP*56~n-QVvE2jEM4 z_qN0}bS&D(UC$r=X4@$e648rUSZ} z$a3(q1Cw;Tu=#jqk>W1Y4JMp8=|T@CpF)MNl6jL4@@ z9p{#2TG<`0`xfbZ$n02Bt>s@&Ol|@$*a4JK62}mt(Z~HaukrhjtDf1b? z=2|3z)U3*KXup9uS*=bvXXtn$7I(a)napPHQm(95{dy?{8x!v?DIMUSLJR5xi~k+g zq*5(*z6S$F?E_Mo2i(jMGlQ{iwrhT^U!6d(%9f++<_6f+E!6CFVt#NlN=lwem1vh;Mv=%Mpi=nK1R3+ui(-`}Hi3k+u^(9K%*p>#DWa!s&tcb2U zA``XuCZE_zKHf*|c@H#~+~XCJsxEtD|C-rPsIhj+3_9wNoLS#Jmx||bJ@vqbEymYh zot{0t`L}OtpWCmlwhgzqod52(H~;ugw}1S8NecWWw~x~OyYKA!t)HmJ&i|=Bp*NiQ zUSD77g&sQa&F6W;{`b!RmiA$T@Loq9lkxVwK0p~-D7icu0ygPMC{c5=%*NH-lY-_5ght;{LYa&4fMba^ zCDvk1%K>O($j}|j?wTgs{x)4X;O_lk6cCz0fuhxl9A-D=myP!6lb{e-1$EgFAbY3L z=dr0=ZT&mHkZ?A{m$SF&${3!!@C;#jrv^1ORkzoxj=A-sjW9Bc5glR}?PhXp#oETH zsyKqDQP;Qm-AnFH_XVTzX@~UaD!;AR!72Az0)ixu7a#&aEMOP_@G@~Lq>LA$qZ-?w zJH;4`E_n9s4PIt!15dXKY)_t!?SykY{r7*5%l`a7{tFn-{^!3t$JK4^PrrKh{Qvwf z^AFEmhWu~5{TSYZ*S?ip|NZjq4ffL8?&6)!{j=TVds!`7AC_a=j}PY~+wcATwT1rJ z{;zE3t3&>N>)`&beS>#NgwfBvE_6GCOW z(pD5oIllfn0PShJDq@%!+7_y9$4qPy+r|~~TC`LEhbSc4?p)zaw$W~bSWCCEaRN3S z+p44OBf^@1vgpug-0w;sgfG2pR!6+M8m_o(-<5`F0~7=zxR5wKPA1e)I@Jzz&edkGtD$%4MD_PBIhsZ}9 zebaJGYYlMwN~Xhvvl&8=F@UOy)8-i@W5lTI+x+wMrG-TV3KbFdym&ksyJ%&n!G0zq zIJgRtHy@zE0u-xoTbyQ?10rK!V+0vSa}D^_bUpQ_YclZUgwq&L&STpUv3>K^)8GBW z^Z)+)`G*&Hx;_2-Zvc#EPw?IK%m4SE^X5jGe}f;?yT-?VTQ58wAG%LsynSkW>3!SB z>d(h&uR1)BH~_@M+h5(~y}J4a2jKnI!O`z84#1c8&g~A+ zU^iDp(|*H<@!Q|jX~Qh+7xQ9OlUXc_K?)K_wAo!+IYjr=$~R<_WucN9jUzPQ`EW|u z^=bQcc3{lCg)6LYD`W91mMH=@Q4+)wLlsiZsaScA9r#K+kbNBi!;{|iYlF)XYROIlUHBOthA%4#^bu3XVEO?%*9n`4<>vcjU1Cx3!+a2W^|GIZzI<()A9%Mx} znRv*N>?h5hJ4dv;i@O5V9Lvk!i@SL;t7^&?jkpVNaQFe-OMiU9B+h{+8&BJI zIw8jP(6@Q;mHxSX)jw93 zUOf*FbI%+9wjdjyzP-snf73BP{_XBOdhLYwwm;w_yngj}vxFXTJl@*w@BKI80Q^$z zb9+U7So@GiThtSq5O{{l44&xL_SD}_6>c`v0P zDcY0pX}Ur%I#j+s;w_oq==?iOO{wi!J1QWgcyh(xej@^Vq7vI6QFGo^G_%;{oypSh z5ylds1REXE>Nkey^gtcve`5?f#tf^;fF~VcmA|)C_&#|>t*6R`0RucF-D9n80X)9{p8>M?)qQ;aQn~SVv8sL@ay{O z$?55{@#pXU^nd>+&Z^6Nm$&`jj@`n?Ey~ZS{USa3X@>jVwhulJU-|7pgPw2Q{)Y3v z!}dPUd)xx={rfjk2jBzV79ZsWqess3m-Y5?TudgSwO?Pylc#*L z@sf<#rHF`#O5$eMOl>2^&|V5fgcLbS3Tk5(SW4*j5?33x=udkbk0Gnnlwn)K9m}Wo z3JT}2d`TVKUE^!T(H-8?qA0+YZB=XLzzkr3#t<)~1Who#GyLzpm2CYu=658HiB{1J zgjfJVC zry43PdCFygW}~rKZKW?euwftMP^XFv;}@fpG>eyAJb?rY6^yDx0TC`uFoF&jOEQO4 zmN9iID2*%c5(Ld@o<@a*LdL!Tju@j*h(@8Y>_*to@94Lrh+*x}70*W7;#M^UN)`47 z6AZ6;dz*_aOEQF#qDVSd(1Jz|9WTy6x_H_~z*asq0d=trl9f%0wR$Fq9^pz<*x8Vl zQdL371{YO(=j}>($z?0+xGFFl1y9{uxb4n0)+^NrE~hUT6iex>0;HjksxAA@*oBD) zYfckuXrWF=+@)5!z=akkUj6J!;iEcHuxj2N{0Fb|utx#}wI}z)d294sR^?nXNN$wM z>)aAj%Hl;-sFRs$aj1ak#gCPFYe@Z zIDYk&YQFrJKM|vbPFGiP-s;86=l|co@Ol-xd&k%P@fH2c`kcPG-M^O=$5^Z7HY+DCPSD+dFPe+$~ z#MJS7NZ7)H_*{XV{y&?k_8O#SA_hAIZU$a7uaRA$$_9EJ_;@yK1_X~FuDjO?pc!K67&EbbKvN1#~0CY zi%ysGXp;wYUxK68N%?mpYu>!T18fSdbwnETYBG+0jcy zPuvm@)pB2Ch*MaNk)w5l7GfG)UxT&gsu3H?9(9LcWz-Oh)xMm?_HNzwaCVuj;GLSO2{i zJHG!o;eDgNpS*oeeR=Qpn0>fD(hqEZZU4t2`aI6_*#3H_^E}q&d%wJP*u&?!zr_B= zWk4Sr==WXCA8Y^5_$m5y;edNLt%o-L#CAkaDigQ2`R#X*PG5Z;rvSkb3NbbjBK6{$ zdtn<0mY5B603t+akR#gGyxC(eWC_|DZH&(f{w~PRiguT9NlY;oTBV`JSUc+&h3+;R zoKQ32nyDThn`+gHA?zw)Yk%|>hHZ(~1yWh!aJd9_Y4Y%4fU#9pU1kVW(1{1fEUQ)( zu<8Tarnnp718yN0_ut6})?%90niJPIYUI^2w)*40EU@gVOh57)mZf;)UF4AVo%1E$ zWvr@0t#|P>trSFenbwZ%U}u?o4LC}@Z0kPOw~%EScrC@7)+8;fRmyw=rC5X`#9|BM zG&lq(y6`O$NJ`Ud4Xvwbh6Ochkk{Z`62rJPa%smewrwbG{{_$kd|QK`zLkhA)(rzKE@0*gqK z37j18QQb){30KGUy_T1^Ae1WQn!b%^3ltKNQkY=*oJ4VDg&go^huE8M-|PF(ljSNR z;gpZ_dq87#)@t*TY~flz=9QhYsJ$>-GT)xO4r!%@&^vNgtDwVjKdKXC{lIsvH9tyJ zsVYq~P-ct`ft<7Lco69DL4%7VD*DyobjpMzgsp%)9^EdReLTq?D-d3;OKnV!YlOW9 zN*Je*P%kdng^f53j)*b1jo{eM+v%&PoVxk%f5Pny^2ya{Ty40%T>jf1>-thTU(suN zus!%)(A#$S|Ji%@{z#7FO7xt_>IN_`a!84?t-W5aufP8%*~i_r*4}Hay(`IDUzrju zk%L!PWyJj>PGmKJ1_l^n$QcqT{f3R|>Z+{Ftf~_cCr*6#?VY<3*ZzCuyL>ER?_u_S z+B@|AqX-wg)e#&w{(PI`^T3e~?tjOS-ru)+^&mHX^!5!m+K*fY^bX7K+Z^_t_y0bh zUblJ_>I;4-57&2T#ct+iYX0JQ`~0P!pDh=cMvLyO+FP7^G}`eodc@L2UU#{Z!@Y9- z0Ph63nevry&EWZ-M5O09Z>v`Cexe-AU~5paC*f{MC6|{Sa8yR-5Hkt5)2tY}gXUfm zqH^nJiCn6P>>e3r7$O;%$Xc*@MiQC0$!7NHYq#LsVZ_vcK<|n8!WSkbCa+&oC;2`y z0Pw`SU>2r=nb!aSAOJ~3K~&})8NJF1&W>Bg9yC&=geZ=@xcEv2H7Gt%2?jZ;p5{|k zw94ED01#iijCsIZ*kaGFKtpdy%FYD1P+tqFB`-IEC8(CS(`sLPK4Gb6UV)HN+(C{E7MXB|T% zH6(y(OJvjlw5&3g`4uUQQ-)J?s2W;g;vgfsTP7dKN$*BR&)RcU132+9m7^?hoFm`e zQ%NZ@y;_IQ3y`K(fXRLG$bb#H(=1Yorw6m}7qtj;8MsGu-4yIHGMOpV0eV#uNFTaK zhBC4YmI}Fr2xsU{lPajvT^vaJYwm-xuiGkey;W2S>88&}4J!Z$X0QoBWZjHRJRmE? zm5&bvnsQtuS{ej0`$LZaMKEZvFizX8FH2nRtWB1BJ{R-y^{E~v%(8CEuAv7OClHgN zSwO`)Xhb8LS!Sl^9cWbM_u{xzID_d5A=xYoIJefq5Oer97bIibI2lQsnoSMC>BY&j zfA_Qg_1T~P@wWzGYA0WOf>$s6U%z_u%is7sqY{1|-ag_N)4dwUKl*Tg%=qr-`w_J} zaNvz$zT$1ar{{hghgVN^4;*;N(|@DOEbrg#`ybd!{AiMZ-)i|iwYyDJEiJxv*LQB; zK`e@hh|Q)g+v(`jPkgtF-KBRoqq`f;dygJ)pI$zP!M!tS<0k6K>Pt)4BLY)HGs-C# zSe@Ro845L)W_fOJ%#cF*{Ser@2?bv%3PJLKGR#i4HoV@0_JeXA3v#723d&87z>5FZ z6x8)h3GAGlCVTbL-MIv(mar?9uFQwT?3900|DqrWUY>DucsKJ_*k#i<_A%0G`@g8t?e)kx%e`eHwz>txdjod+~ysqqsO<&UdX% z-n*Fvfu*$1K%uqgyom0MHy|WRdWt{=R zwS3Q)lNyCbgu0RSYn?&1rk5r6G7IH#Oc)eg5#-1X7z!tWa+qhpJyQq_hT|i=cow^v zOK{V=(Sp$zOZ*qTz#&n3F@;{rD~Uknrz#MTdnWdhgIUF?l53*!y3`Dru_#x=9!U5* zswP3{s`=-NhOlxoEER|vil`v-V&o&}54rP!K?)KZ8<~fYAYbS`x)VrWa$dbP3!t!C z=^;_Tlmh;&62eJIhoRM!raljimb~)-L^Nj=jS30%SWAL9P}v5N9$ffp212NJwP2(T z8tAM-R~55?Mm{{~CJ1X5@}?&v$dJV)>Q~vrG(|K=#TFqgrBG!z<>ZVp%pZ0)ZyrcL zbAsB4s!^N+SOl0%Fwx4+63b#EfvQO5Ort%Kd7iyO4Jp3aCG#(c@1^GHJ<95)2>PC| zK2HEK(6pkYqbl1gfGic$X~%7{%e#BzqiEu&JZEdV_%bb}3^kd=8O z>lLN+KMdq%Qy|*$wzbwTFX2ElYZj-M%h#tVCK_2lfQox4%raRjexf7mG^5cG#s=B0 zMDH2rArP}$^4SQRnpp$G8foTmCop@Q?r7xJ&`35r+E{A=ZeKmye(@Q0^P7M8b@N!3 z<>>R5n=fAbzkPlFkH7V<$sTTidp^@^Xnw%$E?oI22Y;W(`OxO(-2nQX`(OF#_5E)E z^T&z+95y_-fBf6_U;p?K830f1+ck~?mWS5t$KXd8_eL=2@T2MECqMCJ*DuZiM59n< z@0>mQvU&NE3||%i;s%f^aOx#bnH-qSghtZRD?U;J3e^h1K&h+wDvtnYt$gq-$^_xc zLmd>EY;=gF-6&72vR-RebQc%Fp!#4E5h)%!%PC%eAqop0ZSd>_yBPpC&5ag7?@W(q z1U>T#gf^on!a~eCWY}J)qpTAa=+%g%2{gbsM1vq!M}Jns4VICC0NNT%D58j$k&vgP zHLOt=RI#wUNIB@sxt&gQ*$@J>;vLmukMSE|1SQ7g4xeS3LoA4)-s^-2qwgQSh(wIz&G@e~9QUE9Z8 zx%l6Tw216pvOi@`7D>j_TI4#Skm;fsG0>^3+zxkGAgfTP_?3zYM5SGHcp%d=qOLf= z-lOu_Q8w-%?&lnZ_vAJ0QQc}Sd7Wlt@pz=xt?rMxa|u)tEc9Wsxdt+7WX3{~?huD5 zjtC|%PHQGLaS5n^0FYrtjGUDkv0$i3c1D$Aaf2#aD9$7k#%T?KHcgl|zT3sTw9Uj# zi_?q#%^8yUpO>3Yj9!>wYDof%#VxsJ7KGWy+R;QdHaq$0>*>Xd%isKd_b>nEz1zg&FJ9y1c>bTioxeI;vkne6et1ao zV0&mkZ`=Rsc9vu5Jq*If@aFzL+d~WnUf+J`$G7c&_ik_LPZ2bDW3SuxU;q1L06ev~ zw*eSSzk?s|ogMCN+PM4n^VgHL`SethJ3%@;dUVG;+ws1j12yo>S>N9F*e zD|9oVyx@V5=tlbQhQ@iiC_}Ig?!mBhhsy!Tiu{MlghCKqT$Ju`2SfFzgz->^MW8(K ziEjc$*-v*g0Hpr7P)qW&!{xI-I>IzzURsdb$pbBjWr@B}P&kx1;OP?{q=cd&m6Ccu zmB7rDcL>P6nq9FR6Hf)6U|p^pkHW2aEP2o~j)8JQ8vqYlvSuPvcnLS7@EI)vG%KCR z9Es#R)k80n4U9;dc`=Aoj>(hJVMX$hk_=E1`6)>{RlDmkR5zN-#H5Nu58 z7%BOP-qVEd98BW)N+U@xexQ-Ym{Ui;!bBc9mPxvRlg4nj){=kEr0Ii*P|rXbW6@XP z0W!1_C}U4$uq`RzS&l;Jwj~#z0KIpDja~kiD09bs*;F7;vJO*~@SQc#X!XwQa-F``P7YyXB_!Kb_5ApDVRJOMqK&sezwbGz&O_d4pSP&S-5? zG&74NX0Rn9df4LdKr>E_W)YB5kP#ch()}#m|BYH{ZCSqge}C&| z=k7~;c69U?U-;KwpZ@ZnQB@7t=`H); zFeDD0_wcqGU*EcDh9~OD0C;NmX{`Tn_&-0^mN*I#$D8e|SN-x7i+AswjG2hpdn`TR z)2o*rJr=h?FU~x=a1xe!>>Y@}rm?k^$pC<0PzcTB)K9uU<={>bX%fC1WP^l3gW!&& zBvd`Jq%lt|Y>&h~0^x;lb&){YEjejAKFSCge&O;~#%9CKCYFUT9v`XhfG-*7P|>2n zoJrqd=FldHPle0m@Z=MZEVXrcv96NHE4>nxCtwv57*C{1L==r=_^MHEG27!fCB`2~ z0}1Aoeq7vT4jXKoq29$_^pCQsR$db-1fj{zOz=wT6~-z`)#_o%RN71}$WTnkDgfw8 z4BydSxQEB_%P3HoB3WE}vfBu$G6E)^g~geQK_ z0bOGV;x&!z0j^6Z7fx1&cBB*$Qz|Oc10;(ySSvXWp7i&TkFB)zbcxHjOA5YAYl=jc zXK>F#2kss@6p|>_11Scq@8LIjwAG?vy=4j?&wYr%t4aPI7$2` z!))%5cDa@HUtK-^0W0vpc?U$h^{Cg6@?-!! zwfnU~LLl%_qkr|?eY2T-?(LITZQAs+Q=cOcmgVllxpZ94Jl^u;1ifQU!TT1jsz4#W zhrwN5;i(mEgVI_Fm@=E?e$Es?608bY0abJ=To%asxCp{T_fQN0h^#s4h`^Md^{Ygn zDC!6bWg@tW!J{1MsHDsCXu1c2CtFUN*zHO3v zsZ7l@4}4td8dK6SWrN5xsud^Pl$u3KPSn#%s3tryd!!^zE>GnHfJO-iL1=oNj=>cn z*cd&qH;kId@uP_p? zt8PFzykjWwBYzG9V(EFa5P6|VA*!x2WQg=Mn=5lSZ`CD4+0RLovQP{|njmr+AXCpR z=B@;dw2fUstMMzkS75C8bD6RXm<$F_$GiMaKC}33kT_s>p(oN?|Y49eFi!@+ zQ+r$EwY9aE3Glumwp>%;4u1P>PCM?9o+y7awNGC!7iZipNcM4Kc#p-|!{>SP$!o&< z(jy_Ycb z-zJ1clnt0xWfKX`hk}j-LKzk@^qoi$-E|X*ou7e(h~DAkWKnlS;`I?QTFU{X;kiUo zr;teno>O3)b6qv)9wB*SlZ+%h~_>oiB@dOwW$m>lgFye&7Fic`d4R<6->~ zwrhxySHF6w*qw?rj~DKZL17h<|CriGI<=$iq3?SD?!!CWKM*G+o;&;)w|3s4u6-709>~oEO>#6s?TB>XJzWwRv({`Fq-*^{^#b_QKJ+Rwx zyWN~Ti$43}OeCHB^78Q$>4S6-Xbt5{t)52-Q(`bxP##3uO5#Z;pX9gm>I?Qo(U*(W zAF3;4hE|?$>A0`1!BD6{rbtm&QklNwU#dS$pw_H$@@1hJkB@0Bmf5;TcO^E+dz!hP z0Yn`zc|jU!cyYQlt8O?t2hA5&;9af+%!y_1Do0yjYv^=dWE<`FC zjhKLrVg-~a7SJAXC`sD|x|NuL2-`C&AUS!9=msJQ`m&>xRUwP2X>36h-Q2TcL9GK2 zGGihSoXk10 z(1m18Ebk?L>n0vcR19Q)jgeqt+G8W&qoE zW1Fd8UU2SaoB5AlW0v|qA~OFt7%*d-6th}X07Qj2i~!7FmX}QL0armcPR$LRnwb)d z+tkp^FXuSD2ojrSQ!|@rhRK4qeSY-pFMp2ni*J7Mzn0Up@D9ZE*((}$|ML4-2C)6n zyom4j&b%@lF*cU%xW9+q7LU@oM{PgwDSfB`z(YISW&rTOF1-53_3ib>Jhc5_SAJ9m zz-?yKLl@~?R@nE^zLWC~bN7d|PLg8pZpWMU;^lntCKd<5W{zm2&oh=D-nXBA8i?LI zlfi2RqoZEcDEawjRnrLN+%C}_#R01F=w1=q$fRBoaAPehRaS(_7n?V?$QW|gN`5;p z2#VdO4thNf%B?65X>Kcuw_YlGY)sAC6h70)qZ62gJCl2z7iz;_C+lJaAj)T5T}L|H zLotK;5QF_QG7##zisipPp6V2750r5sZ?Ft1#SEetFDPtf`!g{_>oLeO={jZp%!oib ziiOfNqO59ICUMV1d=aMOE6cfT!ExzGZ*=mwO5PrMMGBVqK)qOtLls#J5oyFI=Hf^y z4jV~JDtw+Sma}FDX<%S3%G19x{;NO5finFbh>kIt8Er^UzBTg_ zrOi6P`v6$>7lOg)dH={(M3Q?(6eew+Qs>X%T20gR)`vQ$dn--CoYhP)h(N6V`_vUu zJ^;&YAM(W0%S-Q)yFrY_oD{h`TZ_ArE<|Zj#t78~x7L7r0 z>5{cICJ%=h+sWnObnnRQktIlm1sZK?s(;aFQwz{fFS7J++f2FV zo%zyjvq_jvW9(+mi)}aU#jBWiav36>te#lTJ(BSsy@S!(1}yQw;d}*3=C0f|dtT2F zDi;(k#&ZS66D^ssm@*;>07y!1#`lvEVAA_11UG@z883_~Lm@v}>;rK#rn*tv*zqyV zVwo+8J=wbe5C)=m0qN8cN2M)VnHX4&4H=IS1=T77kYBji*%497m0|%&l(SlwHoOl@`{o{l)v0dS(hCqAO-Awi1HuHm9B^c73B!YUnb?FA{dF>VpLWQb9*9UU0$+NJUoJpXj!aR?rN}@KarIR##xkFjgScwzAlC_ z{<7K@z%9|YikD>^CNXBz0F7W#m6f2U>5D@C9u*dgu3mWtE&%kPt1ehXMP2feeQ(9r zV$RfGRdWLY#sg{|r`#`V7Jc-zK!ED&Pj74QowP=q@)uLKUF1K%2Eh|7l{8?3q=~vW3!@ zFijaBvF&87`MmIAVPJW4O1CnR#TqhesU-?`&LL@0f{jG!`{wQIVJU^wJyo$VwKh$% z$w`|mh`3zhbVtyRCYV{(#?9*XZ6(6%F{H$LE9Y++KTYKObZm+yUrsJI?K=yYkaRhDW_i zdEl1e!4v!l4S@IEK9+_4cJ+yW#)?O5@T_dX& zFwl@h>Ms#s41x`SbgE~;*p-`YI=%kla3pgb>Ac)J4@==k=PY@qtUe1-Uxeb)o?(%0 zWE5qBxDu2p_c_Vr?xTD>`U#;FVcBeD-B5Y4;^FZRtw_<+YlaerU3{SvFf%#vO^#8dMco)Ao#S4%dCa0=H}#iwlHRAWpF^n1#^@ z^v=#TJd_e%`KVEqZU=$IxdZ~343mZi=)+VY-?Gu;}&RBlOS(GfoYl1(9!Pn3}{ zxbOKs2Z|yBAe@ug$umq1eZevl3@eS!WJM=`#ag0gh8Nfza{hw#QQUe`ImrXPgVF$1 zvZJ1G49}WV^cd;CBeIbvjEJz}4;KYnfs^!%38gzKDw1kQkaWNN%tYeyOl*K9KRXwO z-p(e(ERgNVi&42Q)L`Z%hX-k+go8|==uDq$CL!BcIT`KAD@uJBrEo}9D8ft;hBOPq z!!iPp?&M6lj*|3=JwJMYXo>~|=&)c!NI_qIWM*bd5?zD>DN{DroWY`ZIkKT}joi-> z6_b)hVH~2ah;WeJA&yXD?;cP_gaw3a6bbPvR2M}Y>*?xG&7wdcT$CH}=!%6OS-!2J zYLW4Wo|>Lvmc{ji(Um2<+9!zaTG-j2-U*8gILK_!I;odFq0|M$;<tFZUp*96ycM|L z1JHl_?Y#DhH@-UDpC6|IaB#vN{rKN~`&jO=hqYU;>TxeOp@%zw&8ttsmsl2r+tC&P zJgkTB7GHMTPhN4eiFqz*&QV@8!9g;HgTa`LE!owT{<{|AO%HDw<{_sfguX0>H#vsi zxS(Wpc|a~103b-S7FHhbv@N3eEk%@0dWsI^;#%9d{{zUBNJkj$_*sy#%v@AR6lAKn zn51AtFCLQQC}(#P=XIr5M_d0JX3XlO9{`%EKFV=M=)k!hqWILnl-;3VOO$uk1jJpU zpF;vj!!Dip)OE38G-&v-PXwy;R*K0}0~W9$AJkWd2}_iyItW;CXdR2UPGYY4fcx?WKkL9 zAZ78T!@h&TM7j{dmQoWPi8xa#C$;~X<5s+ULS^ZMo!wOstw?eNBYKv^R)j|vt2_Zo zo|)qS03ZNKL_t)=O|Sy=!C+DVB_m;?-AA}HLd!L6IWIt;71Kobk8u{Uq_R+)SD}VT zFF?(1RJ12Eck3R(D9fD}Bx=4D{2> zIK7NHtqmEg)-1gW$t7a12xNoY|9}!&BH3=v1xzZJksEP`+BA(jJx(ueVNA56txXMP z+;EyU1YZ967t`^xKm4cvKL7sf@XneqpO+XV8t!Nhg=8Op8%~h@Emy~(?tLDz2R_91 zp-tgif%QE*{8pfUaQn?QeGh}-$}Wct0AAhcAk=>{0IGdxEApw`t5F=;$DBXiIEl}8 zGq#)Q)u(ax2D7_a+isS{4dKhe-UEH}>1*?)FIf{ndNm2wm2eso!cXwHzTk_~EIfo5 z`ki{5N7ZtooVFv{j|eNsmB^YjB#XcX3<`j3Z9wWM*2F;4QIJ-UDB*4SYt_$eK{m7F zW00}TEelCmOJ6#>6Nu=eC0vaJMbsAlj39Wj! z5q10#1*t66@qZ7nxJ;%x%?Kbo$0Au6@_A!0g7UZB6eOYag%XK&&rxB^y3|#MjI=1u z`-mizft3~^kqA3bd8jhsWPTFv(Su0{S#5^39z_Naiq2M*q})xQ+qDX7{7Vi$Yaxe< zZKVzxu!5{QB2#{^2)1&xkCeHXIE%AH;_? z{;**Qum1N6D<6?&Y=s9`$ZhLPCebIgG=*!K zg6l%SAsYjbhnM9(AGi753stOjkv@)F$x`PASXyso$@~FnpBJQc4nviY4M-_yHFwk6Q!0fX!f6Y{N~6kesaxer#(9aL6x2H_b1w1}vI;uZTXb(WxyDdp zO~%N;%en%Cc^A19{{S`G7s3N%~6E=*` zXY$H7y#B|b<6Jw&-u?%U!&|mrKmG@306bQ^Vm9Q=8{21}N8kC`g|)_`BLjRlH-yiN_xb3PPlD{bMNW6@^(njK4(}$oTtWJL3zshy z1Jj5S68Z8kq`w+GHYg|soCnFce96MR$oN3|^+^ez3yKd9f7KLA!FdlW5A_~kcham` zY$i^IEI7q1aiDZ(MG+FsCW%7WMc5g|65mfj5(cc2XAvl~00Yg)(NDs|GRwlH_rqpFSqMJsZ5iAWjS~ST2wd; z@|rV8IC@~nAGy<%3#+mZv~X8O?0AaP@)ERjg7=YEz@R0OCb7Dw>8KIPY=>NIo?82W z_hFn+^E^;yXdL9X&XKPw1QwZ;5*o~$r!pd|07V`_^UJIpOsbl1jXw$R7Ln-|OvoPc z>odUc985qOM1!RU_*$i5E!V)LvRIXIPzmi^EnXHw8q&%(U_`Jh7>3>rRKaGZicwya z$YTW26&->M2QOPfOA!e46yLgA6cUXnFD*31K0rJ!(Ctw-$ zgEELnS;A&5!eM5c4H4nJwJnH#cHwU>axDQs6A4ahwFf+cWYbkzOU2YMYmtSByJHC; z*cuzr$f>clJR2Hf?l{}Qz@rVPiDtIl9m^eG~mk&Du021!hV~9vz zzAmr8IFv`Lk*xezfFbch#7J_;NJVg!UM+T#UwzieE18(Ttyj*#pNE>piCE_B%bzpB?&q+YqkcoX3A*cd3__L8{(K$-_(Dx z*>xdWc%=mdS4c-A=Bhs@UHd8+7olL&N^?py?8R-5%DE^7&ongT3s(n9Iy1`O?wMPi z>rx=NNKIwl*j_4jMO^e}FOxF+CQ*N|k6{Ub(JQYe?-Vbd(uhdtT9UxSnot=MnIKLU z`p;|kRa&VCSBTYmNdi=)3~Lg5az*MMDQv9d1TRy?Wz1{DTtYtSP(b7bsJu6=kqzPD z-lk13{G0PQ+bQl~QtgkBNNrk=XckBvFID|bGb6&o-DWTYK1MXMwUh>;k&O`%r#m`h z;&gOGGj5tqQ$vd>UjB!_-hK7^um7Ka>X%Ev6>WHG*@mHi=PoxkR9^WlQ0_x*-&f}z zdfvzC=OMt&{q48ye~;}q4FK+Mueayn{SU^9et-tRqg#aEvVG8Z=VLeC@z=LM-g|Eh zEaAKJ>GRjq)Rs4=AaQ(RjJTXt6KCEXefqjhZQ1RF{G!k$tuPa?IEiLc32?i$e8+pi z`tl~5s1zj2T*yd$zWZYU1>*`kVweX41Y!;#f-URy=C7-ekP<0FG^7|VdKR;}#ya7e zgVO|?uyh2_fN7H4PVWFd{uBiy4a<$Quf2)OaGT2%)UpnxgxPItVV8B{u+>&Y}e{ zgzb~Otqf6(cMVeck`vsgKFpwdX)lk|Le*I1FGnpouelo=QKuHVE(XpRs$A$%oTs#` zTxOr_m=v>0|FdcmM=Av|k_po@Gh$>g>$*G;7M;@kNy|j}*-@Fhl)h&KXwf||dPii4 z0B|XUmQ|Chlv)0aR(r~xal#39riM?*`=0u^h$>2epzO$`09AE@HTnW(mb2>~OG%ppX=YW#nN)qx)_z+v~UAu1(uf79&>D%F79DrkiwI!e{>O?>K+F{w9La-;y~MM zjPzy4AkF;z!cQ;rNg}J8$eevFo~ClvAEa5`YuQOB!6h)4SveV7lW-T!BhZN1aj_7z z<1N{2GjT(6G{nhY{>65C^3{L*Z_A&~9T`Tr%?tTsYj&z;)d&;m9O;fmLM!&_Dz^o z$Ucg>uAAHwz7R?FNJlACiaUXkV)O}z67gGk#mBJ{XXF|3tuM5_7TjXQ-l+>UB1uH) z5ib3f?AxYcGevrLn{k@pDa_&ne}_iv3wM4&lFoG{kqVGEM|72u_V`84kkvj0KpQ zZ8vOYN0ZT-vwd>%;^+VV>tFo+<*&Z#KCcp};~~qdANJd$yfQxA#si;kyX4zoq+Qt_ z`+pdo+K;E{Wm%8i@mAXV`Yu$zxBi|CfT#8z+8*9}r{9jx-`y)gK3xL1yyRvxy?W)B zXMI`P^A{eDi(O)uxVUU5C!6Olmy7fCEXu98f`%Z9{N1L2{DKi-W@1XD@Zdnb>HN<; zKk?kTVzNMyGpf>T6rRhk2E{fC!ZZ>H&!WZGIqtylReQQqb^QWr)<6erdz28IttrRX zyPTIv_-RamKY~kYh zS%0XeE^ahw#U$~+Q4E>FnlY&M!xt`lGGr2rDpx8pm0groW)(9L$t3lGGNo0~sz|Rq zn+c5O`Ur(NGFJhC@Gf)9gQ=mD>gLJGF_7$XM{75!qp0a@gqtUMqqJ5X$#%q?cw zb)x0OP%+}L4wAu@Wdt&YP^-}tUVw%;m#)#uAWe_{6?PCX$VhS}P|pnEKDNo4HPZW% zNkRSW96P63FldcdKkY8TB~|`NKB1W+3=!^_T~#I~qBS-Pg4WDh0Gteq`}u6-bbN$n zZ99<`J*KUF`qzImfA!Tj|NWnq-7Ln`)ko|dPv0Lj`u|p~mZz3IZ+!cf4NvXy+hYy# zw>u8^{=Ignb>rQ4-ID?E)V^h7mPP_lUe>s>{O-HPZ(pn5@gt2LeRn=Re?Fa@T%LY2 zwbqWG_uUyTcLBuWKJSiy`ct!5=3O${GZl4}QcnhkXY!oI{vdF`@si(T>X{6`eiomy zl=K0HldwFv$)duSiTjF+PS&G0o|j{l zhXjU25sXZ!#PIl9>B#6>(jF}9M%NPi^%(+1ys14#B&0s(Wzp)yK8}d89tnsZ1CB)@ zKRW{GfMsC>Sk{+7QREY3=RsV0{}Gj?U3nPkil}=t6@JrHLq#^gq&f+-eY+}Zb%!VBC4vY=)ti!=%$QSRqaL*TlXO^hJs8$L=kUAR)fr3ep& zM+;EZ#`MQekdGhf3f<)5^^^X|&46XGxL ze#R5RT?r+sNle`-b+M`T6Js5Qvx0PrA-Gi$W%aghQq#l%sElc78IL&CSn^)haxjx{ z5Jq#*S|i!#*?Pz9ezB9~n{rrb6fZ#p%z|NN`SbK6DA4DDISFHdsae+jZPr>#*N#ms z=FW?m&1~CjyPc+uyCdT0vsa(~{ICA-U;pdnZ~x%#t3`bE*>uNacYS3%A>C{3-UIS9 zazDn#gCFnv%O?ZiseQXvcxHdY_0R9{Wa2pT>d?LO4f4S4Z#fiwo(+Bb^0TF1F1{R{ zJnI)1SQhjp0{#4A`||np;?;6_CY&lPhEb|Xa^q%bQcz3?DEa&-XE)2kz2`1VgiJGu zlpCr(u2ehra5PqrXu9yD4y%X^R#r{YKU{lYKrvUwA%`>#$Yz*Yo_ZJ$>0pK>q7-r( zi39qlK&`GXUGuq0viw$he62zz13?#SNJh;7+$e^3;`_`Eu!gW&WB)dH01B zb9{_Irrj!myW%KXbNNq2BP4K|6_b|deM9RR1ATXCn`!&(`Ni&hvptT*<6;K@2zK<#v*VwB5k4;$JCs~W zNXDdqM!H~=aSIe~C^AVD3nIaH`jy3CPJC*qp`P;L5YDUP5asF?^-9jj0mAZ4EGP$s z8sLfvgcBtqS=5k`6UeYYaI*!%rislWoU{mMgiXp(hIc7}u3r4{FbZMiG4+M0G2{{V zhP@c}gt+)9%Scj4W4Opnd~h?1;znh{LlkR&L@DyL$}UJIj;D5hG(AYOL+OqW!TZwE zq4G`PrD`vGW91JGbF1P7iOE>OguM0`hJZ{vNIC`)p8d*X%`EYY%JEG%@JJ7j0N^qL z^IYysxGC!kW(K4Tj^MrxW)TRFmW+HwZ^)<&W6&#ocC0d8V&$={H(>#5q}h|52~YK) zO1{pT3;xfe zq_Q>;J(R{!o|SrBWbJ1*-Tu}liynb6n;7tAwx#>U4jwAvmV0_&Ddt|2q!1d+8kC>l zv3Sf{qnw)OJtOWPXvRjLJ1%FEcCu~TEzKgh*=|1l_y1uzJN?6d`MWrqQ(9@nLvDY< z-tuSNQcn%#h}iq{-tB1&-*4MPC|AJBLlMet=>G7j4)1jB<2|~tkEebl2Ec7Thj-ro z9UuRDYj5w+x|+%cy&_gnQR}xJ?do?UB)sqb|8CZ)?xG;Vm&?oTv*%{k`?5XW&S&SG zrF#;)IhvjP%6R<4ue;*Bo7gXD#yZt$#`Z%g=X@Ha`z_Z zUFflD7$igvQDrm*xoK~4)R1|3qsc3_jKqhAsg@m!L3dI_#U1%)bu5pH^88jE-yF@6 zX|cC=>`}Rj%|Z=<9QDK~BC!|V2`2Yl*Pj(?n`LA`D{3&ZptI!KR2i=dR{(*`odr;i z|6H$dF94lYg(I@Ou1u)>aTH@GJ=Lj`tL}4{%;L|~g;yj2UL-O1>L&~`3pJ3Q(x{3N zDUmm^HdBIC>yl-UL!k|hu*|w7x^C7K?#yg{9VZ z5oroGv({*qw*$-o@?v2RY-~C?qP47%{mIXMy4@W8>wo;){>QVrq1OjBuirIK?Ey^> zzRIlofZJPf(RDmtCCilp^Zk=VzN2FQ%8D zEN5p-SGpOx7jY!%1o`%D!l?+7W+NR|-cOd44XOIFqE%#FuM+POcOv1n;92RqWWR@l z6DVV#T-9=6lCk3Pp|q%nK!ku@l;o&2+5iqfAtzy_J;+}E%1945P_9$%`N;D$lFI=T z#8anDXoDzf&|p}U3w*4Ml^(BQ8RMXiG06#x2nQ{oGDds-)a4Rn7{XH-l9icI;R3P4 zrKkwVLSQ-dJxW?f09C zpR+Q<-cV@#dhDTUR_Qp%aXwkdpPlFR1kEEw0TIKnC#w)$5&Tt5DiLuc$zJIOQZ%Y{ z!LqTml)BSLnrtYP(UrOovXNp8yMCp6hjpbom02<75lVdqB4TVOuy+vMjCo~RdWtiQ z@DwveL98A~OerHyV5*D`n}h_s3>A!dtL*52;(plU3~D`B#=O$6X(&4+fGq!)tT2}< zlu;Qb84NH4uq<(QiSB7R$oi7-YsLlhR&J`g1{i@<{#iV$(E+e&GMhxYqp^`@m>s)? ztxYG#oHo`PB3^v)`SVwweff8Pzx&;vME$>p{)e^t(Y*0Kptk4v!2VO~UO#=z-QM{) z-x+ZKmhBFJ&WCTm3kTi%I1lLO-YM^OoDa|dc;D@g`8#;=@4Rr|ljZkex4p*{66)N& zqV66H-p>HI!g%f&?qITw+6 zxGmE!+l*G3j`^b1E4-2>3)2erhF;O7_E_ImwqhWaK=PY1T0{kr9Hh$CrUgL2OfF(n zY(>sd4w?yS1{n+s+ipFzjFN${xcYAva7G~I4&5bov(HgtF{I_l9i~I^__M1lviiDm zSXx5`9HWRza%KxwiBd9htIKDdAIlvpJcS_hyi~DWlg*TaLsSBRz`4tPzPiKZu;2IW zr};5ViaPNa*PK|a<)0L_$^RtA&0eQOX=Z5nC^wc>kfYXBL4I8afwiD-a$uOIhzBCP zOLA88kr!qv?1fB_Fj5ktxqYyO{jR&Y(mLoLTzCV8lJsVaG!Sf{u~Lx~n6mYCL-1!=9pEEWgC ztbvHjCC)D^2%(2c^iaY>vW{A6x*{cp!o!!&CGZ*Fvr+@b5fB)+A*T3t%;~HV*sXbQHdZ>r#W44FlliPN9-v+?@Zg-4B+++JS z93Quj&$SN+(f`3MH-B|)`%OH=+rIj^41mX469>ocHWYcsNgr!f*LtP^03ZNKL_t*l z2W|gw+y08aa!-7l>$q(}a_@}mDkknTYWG~{hh|{8+-;7xF%g>$m+qG{da9yKvD;;ux(Wt77&BDzg&^tL$SF4S7Qb`Cg2h@+fOla{{N)seoUjM}IC)`VgR ztT)H-j+TFPFno%-UDTk2M;HJY0H-O;I5h?WUP?Fy65TltHO7zJgN|7^Ru#I&Eg10h zUnZYB{h($A1k+41TRrCyRGFtBlnjlEndpNRq%IDHpjBd5kIJGpr02GBh*xVUfG~-; z(rtxz?C_u=q!nu8IqzleM%XZRY<*L!t2t3g?x)PM>{g;{(bB+yFETx=H8xfKmF{Bv)}#h55N4CFa3r_>{EN(_MxM+ zTiVC69_}yzcxdns0`Bqi+W`8(=l@e2FyOGY$a&?z&vu7sB3?Nj)m4NM6%c>OE3Jl0R zsnHiwET&LR)6m8=IfDoI4<&UF^b9UMLNY-ko3uDgLFLfKdim?w(l0j6yy7Q<$S;G;2Zv|2<{k~$HFu3cAUq< zX}oQpyHCp74?eyAf%hBfiPsW4pPEdCU5JoD=Qe zdJj6o9S;!R{A+J30R+SSayM-@_WT*#m$S1{@di4Vi_;fB`?J>U;^Hi`hfDk46LJcg zfo4sDIV>PM5=cqJ>OYpRIE5Dr=%*_+y{QB40ELp})J{USa!3mS3tb6UMwH;B@a|eK zX_zKOf8b^lB(0I~j9S(OQ^2|17L-)K#HKJap5f7JP@>Y(*VfBD)6F6NU6wc zPYD=`s$R#4f^P&EzLIUyXD=anjK0O*1asw)lOP+BfeslpqIe_+rVJ0%qoeR-tcS0R z23=ILhsIE+Y-j}NRU9ghZ&rby^vFuZluc!#DZ4}?tYnGOF&*=j9%HzC;T%r!qACqu zlfqLZP$>-=SO_3X=RzisXSp{7Bg7O^F8E-$S*^v0F8aTE>t*VI`*nQ9!_mFf#gT_X zS_HyXCRxT$(sI%~>sjnel@F>GZ_1kn(i%}Dp%8pBts(`r0Ixb+#2T^bqfA)67F3NP z6`7F18QE;^P)K190|e3}D7=ocq*tbpVlO%V#>z6URCtHU=^qSLQ_c)(Swg8*G)*v~ z1B=6eM)V%17j)$?0Ll-}GQhOPW?|(o=#B`^jwNqraB47vnSy&}o;8A}7W2Zz*=+Oj zg-5iL&1Sou|Md^&|L4nPx8#x~?ty21sO`>6`<9pf5scis^zTChop0^WK`b6ues~+_ zfn(>}j(NYKxC6l8;3;^^=kfP@Gyv}E?48@MVzGl3%xw>wySDEi>gMrpV8FL*zpbBF zzkT=PAKZT9x^T<(+xmIn&$n)WdmFuDU36Ug`ddH4xPMr?Yz(VCw(^JRU z>v}<}tiqdU*eC%XGwp9uiKSAjlf15!3Hg{kZ3du(P(d<^D^(GT;fojccdw6308fCS zsu6{-bM>0C6wFp`eU#-2BrwgHFq&42yKJiQ4xL0yFCqNB^afbXK2SdOmPhyf^)Mbt zslgA9v<|uO>%piAH3C_-FQYV>lOCy|6XNlwA3a=QCOXZsK-fs^!Ikg}O4T>k#SgFZ za!mcYegf+q8pC=ovo9ALplDT04J0##I&*|B=D3&xNtVl+WqIv^Mz$6Vnh~lvA>$zv zu#KDyt%0DK@{V!=dXJqmFr6Gtn~6u8X>)Y(yD#T2zmCNf6TH@LK8mlu6@D=7;5)Cr z;r>qb>E`?EZ6|n)k-HnW;KrQY6X)D{oQH0|a^9Q$zxLtw?Yw*YJ6zY*ldi|VdX_8a zy}A9+q1NL(v_HIh@`J~@ZP50Q$nV$ycsV^NU z9q#l>BIuwa8p4bYx(AC5!g3Rfgk$B+1(gX|h?%D3#2`}oBSjw*B6|fTqWqfaZcKa; z(%TuVp(HC%R!Al#C)^y-1Ar3zWbQ@WNvWTZ zSAVk!ggM+DMjHJ50`np(DO?2y;IKsYj%JOMiX`+NyRLEpjoh>}lZ>)=17?|&GcOF- z(PndWgw3?s9`FA6Zx_G&3QIk9#=_~fyUf7@|xZol^7;Gg%| z|8eoywQanDPOk5N^Q$|y-`IE)uX2C;!FJp6*Ry481fkV8zQQU!udFURxN?1j zF{^`R-VhjhwMl{D@UwErXd9}uE{boW{YJQkvCpF^5GH(2%}{kfYQLhV6DqLu5MJ|1i@k<)jl!It=ylYi0p0G zy2T(fnd1jnm~lpVFPI^3c~`5J z^(QI-Qp2l-Juq`wL}})FJx6MZhs=Po70RR`*_!90VD8~=W^lyWc`VWpEiF;)oCZ9} ztVb3RvGY2iabwaWgVaTX0L_}ggB>J~UOZcf>Dh7r`kUSV_f^b&^#8km+k6o1oiF=C z##-0j;|IU}_S?U&_AdRu2Z*_1Ydi$>e9Qh^wFRzhzsJarT|N*4;C(Ny_r0h;w0?dM z?aIQtV^O_Zm)E1OZ`21H0PipxSMRt)oBi^_CYzprihdc(BL683-RIrK$?Km^+s$%* zu2jq9pi>+b0!Fj8NvcWlv%?C*LSz$X!Eu3tG#E%zn(4l?JJUdIq#hQ306=*ohi5Vh zD6+tF3>SOsZC?i*fGO!P{m3oo(H8|AP@0}arcauRsN5)`38ZsUm_NjCNv#`*oG<{O z(2sDnAtFtJWHMw-g|_c=jlx(JSJ8Zxkt7eUA|b;@8o?DnQt(>5|37>0x*kcA8;P1B zlB~YXXtb;I{!e$lU5#`!x9+YYnc+A;U4jMdTJm8jo6Q9>Z_FbH1fwMu_QsT;w#wH$&FO&y!y9PoZqurO zGQEI6j*;mE1s1q$XV9`})5kW zV7Fi(_j@2?S@K~&e!bH=Sm0+=(9gvI0BXBN?pLe>1a{<3&ovmV7{Q5Hb`g=8*soxI z_~A$1-rWB1J?f4{1q7vrca zZ*-xbXKnTus6T*Sm#0I#83EwE{07U*AoYvyl@ppi(c71)XV)ag{iof{vitD;!^dA^ z_25S4{rwpG5C8uEM2z+B9!V}CQbg%KOO<%LM zLI60$YHOpjmrOWdgUgQ?y#2G@U;;vV*ac&0pJT40H0m9D9`YHbGkY0LCKOU-Bbc;w zlqKo*k5fx>jzJpK}23P+Vg@BnGQf&%cZwlyHLwS?r1AybFX zdBFlAHEGon6gwLrxj4X!da?^gZ)gZc>w0QOt&lDpY9C#8;wEhJTUGjnBqy;r6C6GC zI;p9EOs#AHs>VJg*)AOAtPp2)wqQ5JxM9S&bQa}J;CG?%w3QmW7<>gPz_Ds#5 zQ7W1$eagqcPzYBoVCTx%h63uNTMtngG@O}>Hdf60LWcv1kX^!IZR;6oDvu-%W|OU#4U&g%Yrqz4tZ(c z-{;-Fj=E)V4xrfou=JA1+^^U>>sWdH7W%Io6idvu`;jAeA8vWGTYmU(yT;vL|7ZX6 zCmqh?m*erz!5>CEcw(X_e<(j!0Qkc!?t6KzSn*VE+y0gpj_JL;muHEN2A|KBHhJ*R zJiCu%_NOuYD+;l&2sy1{|mVvcl$=VV~mK%99UNb zdGi5jvP96@?zF69RXCiN7cA{#;emn_1t1D_DY(9(o(B|T#0-txhy*|b^a-QcI>>B( zysoNYE}{xVECSbs@1yAQ*m`qn1_dFBHZrK^!aT*XvG+53Bg@A%r8cHnMLs*GEdYt4U_nX zN&G;XaZ>O;hZ)u7;h@kxR2ci>%m6vk*$X7IMCH)6AjF)&7r9nE7jwGEobl}Ct&y9E zJ#7u}0ch#3d1#ib0I)&^H6I8Y9|J`S0yq$e97BQmt@76*7^zEbKUT6lYc&d;ygG!# zJh3rsbEvTm(%BmX!If@rq$W$Se9Z;sZL;ubX}^@pwySeeHXH(*s7Kmu#{$yqIsCAW zWHMJDY?)Fzifd>Q2;1}uO9$qHl-Ea;m{9Z{M~Gs?k|1vuFflUsYgD>|kDs_tIJGbt z2yho%7RlSS=Ufg8lSg~K@=H}*y?~3~W@xT6h|F?f(j14R6 z@muj-lX$wk*2=tN{`c~zJdJyvD9ih)dM}?>zKS+tBLF0hZur@c!SefNYQRy8!TBKEIqjLstUz zSGeb^bbVzf4S&`g4-eL_TP;ms#rpB%?#J(Me*AN;_v2v*q7h2wngcoF`yVq6aVkd5 z7*@g;-M&D}AVkkK4l9N=p%5sTK(KzBOAlR_gi(mlT7x9n6HGWzWSF)Cc#^#+2`sVO zrRm+7gCPdUOlnJanPxq;Ndc`SR!#t;UTf$}MkxF%21TtE3Y(X;M1!|B9hSWymV`Cg z?f}g-FM~-NJxX2eRYs|hgGfZCb^V*gO#xU;(oNHJpry_P#PU!sUMLY3OSSEJNp&8N z$P^xG3RF8Djsz>rq_%}XgAK0>afaL&N>xuwEGEMcX}9dm5=JTvzt!+%|F8#laq$~e zXCK7WKGH7N0ikWXf{gO4Ej+7k>+pNXB|~=CnH8Ee$0z|nOEbCwGLysdq3tphDwR>d zrvsEmpG8VDp+}vgLiC7*Wx3w|8$&dAT6EFf0p>_ORnq0BEK51EGHyG_Z*Jn8hMzhg z!yeP@L5!fp!li~;k$)s46xHfx;j&=l*zXk&>i!-N<^Iqu5tY-|aj`Pjd>D$vwFGwx z_Rm-xK9&HvL?W?Yk=%WE8y{|Oe*7Mv?mzwKe~r5bo%oHN?-}JSVCbvL=GAwx?&aOT z_^RKt-1T0bQC{&$S`-)jy}Xw%R~jz8w1chGd)x`PF!k(}SJyv73@A9&>U3*46S-g4 zPoHjo`jJ2Uu-@OrnkZjSVytTpVjXc4{O-HR961mUXm3R{87b|rECZ#$oLYP+_J5lP zAQS*dXIoIo8NP;HS6k<;)f6r#%?6<*1cH-b4lcxQLD7XP-&S^Z0wb{ABBOBj=Yy3K zSHp@>PoY)n)&XZK@)08K@?dLAcUtJF@HXr}U}VRVAlfxzSUYTvi;KTttePCO zq85$NT~*^G1BKo@N+zY_H`S_Iin8KhGFlFab2UwS+ScGH7nDpber9se%R3y}rWP$F z@+IbdQ-O{w43=aC6M#U5#0db3o|T@V9vkmsGJ(;;nonbgrK;tOEhevx8-6}gkwi;^ zTJ2kJONGwP@WS-5&3Qgc5cG41!8zIKRKUKwo(G9nb>s*?m?6_2gIur>1LXsNk$L}+ z4?{;wixsg1eXvK4d;nKe1OY^OWds*R4Q4EWe2{WKh~N?*e)?hga1+V9|NQUuU!U5W z;t8;D^5w}_#n8X4!ZUwgZuwq*U*)sl3D4ATD*N^NsKw>>_&qxTf2+so)$jXTelO@m ze)bt-|1-h%EFJXa#ARRQZ1?aEFzJ5m?>>F__rL6JZ`b=zV-U#Tl0c4?BXixy_dg&4 znPaR4dIj#R!Jb+@U|R<%fX%5ff-Ww54=IPq=-EgY-jYHdrWK)ekPvpzE^ z<(87FwK&R@UQ_Km{?Blvc7~v#k|@DSO5;L>d{LTK;r^Otaan?y2%*0IvH<$VUAULU zbZvnwkt-mQiLDPscM+P0fOU-%gsAh7oXO0PY$n`Y5IGtLn{7kQ*Xhy*ppfn$B8}IX z83_zHilxO1O%J6`myAPKS=2-~O6kJ2K4ZK(5x{jGAuHO7*F435OY4?q4Gx3|Gq|M~N$|NgskX+QN-bm4vbyTlkTEzkH#=d)VBm(MQG zU2Puy@v?r-U3fu6@bB{rhTMR+=4wR-Blr8bb|>)`P8nbqQ0m%^r;Vuk=!!n*(LAmwVmROA4u?@?4^ zqdzO&J=C;%mV({>SVdignF7qcq!LIx9n%eaI>TOn(DPaZN-e`06_y zAR3d==d)>ciUa;sK-Zig*lQRfifE1n?X`XcSPlSCDzu67VSiKbiYA_j|0P z{wvA9PRLCl?6{Jd`^4Odw@?g1oYVJc-|j*LM9yB|I*A3i*A>>kFi|L4EP!=4K3 zyV-z~<#&cwUqFtyZu-x_Y+rUgUk6|Hbe@lMzAP>}H=gI#KL^+Gxc(f@JhyJw*K>aC zxBEeRZkioUfOs=efL^@u*KgR#rFgRb+aeK=o(t-T-)15$Y!)$QR?8jG#wGy=! z?Zh!@;Gio9I%Pwf6Q`+?6&T~GO>P#AK#K3d-LBZlo~d6zHEIJy^BEG8wB6Bf5;#bV z3bJ$OdcK+;ZCcS-(!?4Nbo@rcr1bi0k%j{V2~2rN@)bDXHB2^>=dA84qD-saLt*=s zvbl8X=?LyaII(sA8&_+yW=1MMRqv?qm8=h1_w?jjY#Wml0|{x>clT_*P7NdLNQCz| zQ?Woz`^ij)mMWV7S)?7z%xD@wCX`=zn4nLSfOLpP`D{z(l7*lUH^b~1ILyB3Ots8W ze#lv|LopB;bT}eQG6m=h-I8SGk*J?QJ6k8fv+jh|F#l$E>L`|)Hb2B1#OJ;Ip?)T>Z z+_yl(AeV?`>2)9Lo})BpL;@ylKI z?#>704^^)Iy}0h@=Pi{dhxGY4>Sg!6vi%Ei;JNzC+`7n$>F`qc#h|bXSRP?|8J~ZKe|U7a&CL6PkFN0llOhC*VLm@ zD#VrRr%xlt4}bZ0E*ua09466>fpz5=%a8v?AT!Gmz^s+hA{JT*VcUjiMd|M7<}D($ zHa&qNP@vk6dDKOKAz*Mef7iu2_BW-awI*DRle?RWA~MRspo9UT#=UK)?x*^Y-5$NwwW&X?;tguq(hwS*5kaqQ$Hh;pkekhc!ruuD zWJUbc)SNOd>pOtJ7+5Pze}+$w_AXg@0Omo-^2ZhDsA^gZj-sMl+-mK6j;*1omMFz? zmo{Hcx2XPo|5}8ai2O06EUy6nNfaXr1j57C$>jsmIWk6=DOm+>i-r9so-EGoaY!=MbZ=RKN!oxh-A4yFfpQ12h`@? z@cTdg>2Ck<;pS%j`@in~zkgzd*--Q0y0*OSU!hBd{d02ga(uO(zgf8qBzUp@B-prs zPagH~Wc%ZBKH2_4gD3lWG{`)v-wx@xpL6Z=u<>y}$MwgR7sh#FUp8m-nteQ&)HfFZ zx||%x3m%tq{Zz@L`bR7NWN*(M=c}}TK|jCGGB<4d4CY~rS9xQ<+M7R-nV&w9xcSTf zA~E(4>zd25D?MVaD=RGkGuPZLOJ#4B)>pW~`t4u>3bRNP8UPSP6G>Q(zh>j;QlPB_ zrNhn+r&%}BoXupQ=n(+8@q7g`5lhBW_|YDXr8pinZWiC&`hM}D%l#JRz^V+dM3kX| zx`P$5WOnY@!mvb9O}szM9HyXv4xR{z*I#0iI}qpFtHinj0?gfE7Uv4(7+}IujT3{t zXw-b0zdqd$CgY@5uf;9hFfh`2k=y0~1Pftv6xzcm_PM3O8V7r(MSNGz2hV57O+x!U zgn|WZJ7!YZ1gL39GN=h}}}d z7!MD8STWXo*gG)@K$cyuWT9NwDH=s(@gqCkFQSM{npaV<7Jm5YPl@D?yEQ-l-~Sp9 z>s28?e}6wgulDjk?)NKP^7)|g-)J@m$o=C&(v?bJ?@8w{wJa4 zasOAge_{&m)USXOjUWB7?c#L=fY$=DIJ2zB_1j7wPV&)LC$BxOzue$l|G4<~@i@2j zd8+=xd@roSh5Mc?7tYpODqreYe&yl^ZN>iMuZ;ZgZ~qIC*so(93W>Sj=epnh_(SY= z1lEzeWr6hVNwYT)0lUW9V-ZF_rS@aN3+vBwwkZS)-~e@+SOTM{mIp{UtF2VswgG5D zLGErG&;|s#1fs*FNXaW2Nv72Ql2UYs8syZZZpTCzVyAD*?aNs{!Pd?!Y?*;w6eufAZq9;Flur44A7yS%U2Txh8Olj!Nu@Oi zpI4>nWdzpDhdss^h5pOyg+!1$IZWre=9&N)kx^F{=|3u)hZ2X^BU`%v;O&n;a2Fpx ze*EF~`;UM9ukrJpy<)y~c?@{I@NG8x@;fT$z{d8gV?ed7Jih$O_9rhq9um*o>oU&4 z(Y>z3OUG?^_}}a6Pa>|%>vLuM%hP$*z8n|gEm;6QzifWjw>8_}d$s8~pK5<;y`Jp9 z>z_Mvoa*QDMbB*i8!E4OCcnb*e)1Ls0-3r0_$!I;{_Q`2HOGE^SR-N}xgP=Se)w|` zBdt#y*~qNmYsHFzqw5sw1hgHXmB~Z#)CJm#VG!niptbGdvitSIiC*fX>CEX8Z$?qwkFO zI2k{|iWD;ut?ew*fKQy>T4igDH5*ib`}$LZ5GpMuPq)iC-w6lFpnXY&i8%<^f1s=g z;i#IV)zLK}E68nd0i?4hLcLMuj;N2Yi~_r~rt%fEYP`Z6vF$Ux9Vu%PFtD_<0jTh% z5)uk66?d)og(a}+j$WU+VN)z1Q0$`2mJbFSp_mx0VcZ&SObzt3#|5Fm{!q_dc&+Uh}QkKpT!DRu-2xT3w6ne?ZG=|{r`yXy@ zZh!sd*FXRG)5Fg{fBM@$uwMV;!bktUa`~E(%74EvQhDo0@a&Pvg(Lc=!IuV*%jgLwrew zIM?-|6mp@Sr|WrK&(E&!JLZ?^|C;*ab$H$O+mGAzJl>|?QF-!&UQ>Va%)YGuQzZu; zKK_E--~Q!)Be*_1y7@a&v>-O$3z=O!`LOr7$E59SSZ$@Th}x2&PW`Vw&vH zu%(oQ+qX=syXIw9{MQKbFqiN_NL)vA6SS3cN{qR(ckhW_kWq&V8bysN|OnQgzqV|-xC>=LO z-=vZ>myUj$Phj(AcJ8oF#n93HnK5Zpy%-kM0C{ZJYP2n6^R_1}{h?enBoAtw1X{VC zbM-5BDQyEmA>T3&iK#zYMzj||g`J^9j?hx*%y5)YV+@});6mA+PK~tG_9Hc?a#)sv zz2;*tZzVYELs{dmG`!AAu$E+@+;b4f_7hY)(A<43N62@@yd6J(L?%X-O{PO6X(46A zfF$x6G}FhPRA5no^z8#zU4`~D76vTXL(*#mcNP9WrTgFSu^-(PcTvHMFf!NFvGxBK z`bUrs{-YyWFNeVL;bwPp`|Cgd`Q3*Pw;4bG_g`^8rthzxg@bbPJ^6)@`d35b-!LcA ztNeal>iu}bcvOE9SIj5iiuRA{aYg@Ke>|jXaL)Dhk0yR{oagF~=XKEI=DsKU-*%qQ zyD!g-vzP0Mb=!{f_yBya1>nN*JozL&S${ml7t|k5_*6Y{az#$Hzt}&|jOSeax$%Ei z`{QxGm-EZ>x|ck<{LJbLm5f|JePWK~r@zE*mt$QY_E>;*?C*Zveg8d|n@EftYvvMx zF-C4O?+2BCTf@dTEMhCv1@2Z%Uw3MEHDL~hS#upq^L1N)C;N~MPr0>v=tv? zDgV&`k!yt&LJhj4haAOaS3el?A%-A$ifpwG!_=W2*7{>&Lvfbmx$8)h(kih$M~6YH zVjNksyxDX*tbUX16G#k<42SU7c9-Ne5;>fMxz41comP8lBhsjv?G~&JcWw#Nj3dYi zIul?~l5}!(8p~)9uDOO&5kLOv&wu~x|C_(;kuQ1)ek=6+TICy#{{_F;&%WdWo;gN}TP4vJ zeq4>CPSj&le+~XQhp#T;uBXTOg7F{U_nP^hT+icfpX>+EtiQNEJoxJkP=L-2;A_=CWL1~MyTid`KJ>@8lZtwmVFp-^)t4Yun=iua@6;Z&z(X9?fhEF$arN!n}H zx_0Kgb<>W*rUNmCd@oJ|5~Qk!mFsdMSHSz)0YuJIQZO#ZYcgc4nyW?Rx9scuLS?rEsiP z*(;ktDx_?+F4u7dBu%hlm-@j-FLlB~YtDN3!1z4+k)tJ>01;6kk%AFen3>~#PsRRU zb?p*Zd^}g3GiwL@2T3lL7YQi8b&|`6-EwpL@axBMfB&a{``^F({lE7A^E1aZu>DcW zx1H~ce`~%(IqsIvte@YW!dG7j*B!O|a^sx+pX=uWaL>)hQ~mQy|2#9VbM>dke}V$^ zP0CvX%fI#duT@@f|8G6yrwUL0d9Lzt6TyS?-G^-cnHxH}@MvA9kn=_-f@mf-2s{x`%3;lo zCdwDGsnn{w8*^homhwHt)Ubk(d! zS8d&f5>vv+ySFdQ`){4IZh(SNS?qQbvKy5aS1Ct%`c5wbO|aI&7K4I!TPI+)NJefT zsZpxOQBb>@0YdJY!VuZ2E(u$Fj66eY*bd^TwMmRs=d`eW$r|Ozk%$U;RFh7!&_9VB zX)>6Ws62s(;%_P{DanBah!pRJrQg?dQW?_ho*P`EB$+WVlBj6+OE7G{$jm`t3h?dI$aFb$d?Z{w>RI7`}_M}fBo}+{Kx&LU+(_rZ`kLl$UlY^4KLoyS1K2m`%7)d zb)E9WU)S~Xb?cuo&SNxvMf(GQeh!r%b$HPJ;y9mMf2zf~ZXBS~&iS8|vpdVftMA<0gFSX===oT<<@vpMF_xZ{zkmoKaR?_ppT7LynAFX{LM2Ruy{$6CH$R1Pu=E&H_K}`@wQ1s$?@Q1*hLaKY(+)CodkDs8@>YvA zAOs>Sf3Q*pr1pZ%Hlc(oEmiZn&>?bokhh^^2Z@Mg%R5P}C_0wbQdA`|@D+Y1o&T18 zHm39niA~PtD=SV&Vwm;b^7Lv$j@*^yBFBIjMm0MdyC@|+9cpM(V%LqI{ulq@RTG)P0F|~zwSjYXRxVc$wzmEm${s9mB_3jgIZsPVsfsdww08qfifl}hd zgr*hOKafL7ogh1a^_O+W?HHzl8KAi}5HuI891?d5zuLzDD7Yo8hS(L*$y8AN>O32J zZGC?W52JI02{x6gwR6Z?k=X)Iacx3@h>FyNn5Z^vbg(Mlv?Al|W!gL{^kJ5tYq*(j z;#JU;XRLaYPe8~?H3ysxxED5iNsGlY4J|BrM*ah&boQ9Wij9Gxj45*7%`ATmQNo%g zjyI^E?OJ^3&iaAqKC~~DCw#3bmfxt-lS`SmtGfRFn zb|yPTmt_V5j+bw-DTfYSCGpM7u{KMlCS0dI$jlYBX%(o)UG=G}6)uj`a4i2~02zYU z&X#z9N`@u5PSdlfX6aNhUkmZqjA+Y4Y#tihW|XX;?lTftWF@><)F1wrCUFO|C{`LJ|e%jrB z_~(EBCqJ&HO!ZQ{m+Q(&$aT$Cm=|*UPoAmAi*EE@e$(XwL^=1>RfmLlw*b7CuU6j3 zi>u$dU;Nac6VU5Ry2XCKe)eA(cYwBoe`W&+A&Dv^LT6Uq!lGPC*>BCZ6vvL5<73lC1CPDtCDy;$`6Xjyiy$`9495l) z><&2|2=y~D1E0^D)=aJw74`}@zk?{AmyzW?Qa{yYD9Z|M%SaMa^@FHe+LT%yBY zrmyik^&4uK^fby}(}e!?pwYw= z$lD`DOg~TrDs(*(K$wQKk$!U1E&EiI$A?BP$8iOoHxfpP4nrb<>gCJprP`ol(&Mbz z7-Ux@OO(8tSq25B890`!5{jQM%&k9wBr7upm=qir;I`LzI}Kwk!BzDZ1uF?Jwhw}PRY@h0T*f3GyO&4;Bj6gEDj4@(~ATfY3mYZew-R*AW z$G`n^_ub9yPe0xL@{jTNUzH$Z3pS_t^d zVs}f5h0>a3Ouc^7iF|{?K~i>#SDm7W2PW;AsDMJo!!V}=jtg%9bAXZcX)8rloXR2{ zn53hxGR;12!b2&91+Qoa*>ZUl#_R|`ee1SVl+HK9v}W2Nmreaqlz1A!+kM&tG3K8B zxFXs}TWhD~%~nNADJY=S`hGS3+NAC;oMw)Hq0?-&xVT5A`bajSJ$r?{hXb>U zg+k=m*m(ducu?_M3j-G}jMnPUvoq&LqyHH}$j%$>6dR_gpyd`p&J+zllBWF20RUS; zYYeZ11sHjWG@`c0MO$NX9yGvIHyat8TOn9XAj@N{psO&k#!12Iq$ndob{+!-+<~zA zT$(VAs<%Izr8R}Z+6Bu;MYgH6be=dZ_i!XynTd#vFF4D@*9$B+d6sM=xjjcdta3%5;d0st_TAVx-=la>6!FLMKd-*Em z;v4F>`ue)+weeWGIXBY+F1cR~$C2xr`#ap+#O~IhSuTkY({@)BjdJfZl7MvW=r}Bf zDRnR5qWKGN%tjy;bX+R*M@sU5 z(`-lJ2(}|c`5UB>vH1G%Dx>&NCY+SrM!ib6Jlz~9ktmAjA7wO%h>8-+nXdquS+1ao zVKF9(JyYQz>$sG8VIXq=<<3ztNJq&^5+G9{h1mWQ8h`ahBxL__ zM4#5*EAS;%uXsOMDQp3tq>*sQ){34_6fd6<345N@(Gs(!yc8;aE;d82kw7D$j4HL> zutTtZZb$h;!y=@k*4hlh0Uv4Goh18Onp4lURxRFErtRnhq}ia9ZnxZg_z;P^fBZdm%ZHzSdbs;^|F?f)zcF5&EAQz4 zLb>ACi~Y}mWP4bjpRvsE8+5KbcV!;$%}d+Ax=3-Zf6fDVEEqgl-UtQg>j+NA4f+AR za`(Pc{b!Hojh0KpeJv%^3m*8-x$oo4&j{hqy2S+U%>5qwJ#QA?+&Cb;Ac~e}J+YQXoVJTdX9xi1Z@PHiWrdKZw=H}9* zIamiTU~Yya8$<(spM+j!t9nyDqdoa8O32=+svX9BLZ6a;*t18U5Z|qQJqYJKbLwG( zcu4$+%z@HHW=2M@K`N{aOr|0{E%u4c&8#)}j*nEGluRI{c=U=(yDllA+yRQ@Q?hQ~ zg<1#ZNQ<uv_ z%?SznB4+2B9jzWF)<$u<&f#GdTrR4FvZ15j2VG{rqPPdzrWll!#HkOKTI zMG@^;0Wg)g*t^}H8N?K#4g@nH;1Z2CzN@9eKX)!uZ<4A3-{_WG!utmu^{x&Q=5<((KP zRX9aUvtLBn5rQ1qTMjR(VYtM(_8CB8alqEhk?O4W(KZ#WPkY6Td30gfrZ>@YhK1T* zgtl{`8_6z*_C2VntSmDC z$9~O`%Wik`;Woy&`^VoRmYeUs8@a##=f7~D+e3GuTzq?dqu-jh_!D=koEqnRq8h<% z1kB%HIVZI|Df)cna&9~?sDBxrJlD^;_K)j**Aw?WM)?=k|IG>jmv`dAx<9HvZl7oB zuj&8XcyOklbM+UddG5X^@BV@Y&uaDsJ9y1Ie%@es;;-xaKY2QyHO|ZJAGJ*6y3dDu zklfwSTGaZfuRk_CgEVw%?gC-7RQgmI3PPNcet8s3b{~GLSUXyf4cT4w2*v-AH)jNP zVfO@GG7SKZ*CH@g;LWct3MC0j5)HKrZpl+q>gf7gnbff*g^J%ir-%fYUdqDvK*{y5v`R+&(g7W*vg{S*$Yfb(XQl#nUEeI zgah`#yC_^;F)slyF{Z?k6&A<+$SD9od#M7j+PSuVZS8r8_hf+6=?}aulZ*xfd1)eWzGM+WSq6CW(W6&}B>T@iEs&OIQ96+z|NbRniZDj)@IR z6`Cnql&Z0UTOL2nhN~W9E~c?~G8_80T;5=xG8d?~rh9XB${A`S+v6IeF_x-NJ8>w;}*sD6cZU<7IkT{e|&->H2S0=8^HSzjlZh zR`Kck9bd}Jx8}h=Pv0aT|MjLzu50e^3G(K~Yz~f0L_|YZ8hmK8Bo=fEFeCVC%f`a4 zoZhVlO-=r>S3qpmx9+k@ZHo>oRL;f7fcydceT5?g8oAc47G5vqRNqVWWyx&9NX=dcdG_~kH+rv#b-Rw&Qm2uQjc=;U0r+*>!-?hwYDX+H#c z6^~x2K~RMF6nm$wD~Xu`H|Rd0u%#SBV%r!#i#l8-BVweeQVz;lE5HCoR!%qMu!nnz zr77p~F(Q_hrZ7_Jv!tyb;RFE&_>fT2T7K>CaM}v(V?;>nA711VPB;&C7ISH&4Q`Sn zXy#RBmeE2jOE@akzBhCVwv}fsinNoG9hMylE-VIAuKWD-2@encO`n=#pJLWk!6gAk zEQ}muj5QNCA8v1NKj2}#``16nZhd|IYvH{u!p@_{$B^O$`13?R8%mGv>vrT}{n6kb-{lnrfXfp*{`UI% zZ6_z&AJ_9#JpL6#;H_k_Y9A9!=dEEZV`s2!%?ss!cuJ7ddLAbJ< z+d!PDJh{eK-ssW&-rNu1ri_90fdF@Q^#eM3NzrU7O_h)+Y!yKBg>u3dmWDUYfw1D2 zn>Wq;cY~s6ui{6q8tIBb0?SfVA7e(Tv?t4`{+C-dDWxLr_o@TQTj4LEMZ=l86AL?JR42{W66 zGvAi74vmKG&DfEn$lS6-(SmBRFiN=qgvS17G%qua;Dgbf^r36i*Gh0XerXmr74)zrsa>`zK8%SvoFbonYoU< zzsKGEbo`jx>Zdyx%dT=%E1>Jhv965R-P|lUx8wf){^x%&x$JiP^=G^d(7wWk3$@+8s z9DjAW{?Y{BrUjta^fP7rbN%=i;zWI(ZGX1@+(Gklj_y{w3U>d_n zgaYYUQQ9;k-F8m3bq&o0>OeAZ27n3yv+OJ?7+ET;xnOkaHZ?dQr0m!q7- z>X4x?r~$B5!ZoC{`*Rb0nrKsfMw8gv76J-qmSPnob7m{6yC#Bgte&A5m~)t!aK0v> zK%_;oW)`(9Vx2v0kFn>fceV)d$t@f#r%?ORPM!KRCip$a8To$#d~IozR=bqen8WD^ zOw*##iYNI94;>@2RO3bWim5FS&Q3UhX-g+M7S3z1+4hbt!GiB0f>l zAS!16hJKbn)@GJj2BFQCZKu!W=3rFoD8rPeFWW_AWjIE!HqcUHw&{ihDwq=bY-;Ge zN@eC^1cQtv&}PI{C$2Ee03f#oyJ!e?t49I;-&iM;kpZW@8n4Gu>VaD z2e#JB{cpd$-2bEc?*GdB!e2dJdB07rwE#T1H^*@7Xp`9Whu@OVtUvDmW&M2pdVJn| zpR9kh&d)99PU^M+xBtFQIh+9x{^`o6?t8NSx#Q#&YjDkdU$OqUjLiK$#~vh?g|VPu zA_MHCsD%Pesdfsh&D01rFmOU+yGEU+_Z;y4ry6@R_^xzK5&=Y1#70_D;pQenOD3F% zj$$BB3VfqTI#KzjtTawm%ts?x5ke#}80gmQ%kLQi+id+iy*vr?SB%D^%^Ozr62z!6 z3b$EADg^4k)5|rNhk#>Y&UwkuU^#(=!&BPW5w-pned=0P)Rr2;7_j&>tqQi!ggG#< z#@Bt#O;WV`F}5n|76T>7wPm7%D%CjP7@Y|l7y9*3Yo%3U4<43$=*8DsR}fnS2quSG z7f;5o2g{MxuN$(rKSrNwpTx?60Er)d%WjgkC9p*2JrM7|1hmjLhb;;0iHV=Cue?C&nMfz z_`c`be`f!mUw^Lf?#IlA)vuilBFUu^#c%X9CW!2V&Z4-9}2 zu@rU!odIk9*AN!O$kH+zz7HmaR)o$A?4@baK%t)qpWm3vMil#DIyHu|QTe1|Fm|m^ z7LXLApMf?3XEJW?jlFM!MDhPY*9&DiZoQ}1$D`QuElJeOsTFQryPw8tz~*e|rv9Vl z2){K8oox~h7!E4evYIg!)xExQv`FQpN~1svK(EB3%&1c{Ql9B!XkoPxIbXt~EBhDHj4VYKDu0$rv*jCw4xTJC$sluN zh#6GI0Ww0Nir_=mi|PIHc?*DynDRq?lTadyI-(Jk=%8`{D^AhFJ?`(Yj+RsP-`xV1 z{IIJhuwo+Rnjn_jo8`j?Aost1Tz|c<<2v?-maF4(?&s?bmT&TY{>_wQ81Q8KZ-}qX z;iYHQ9}n`$@f=@xx&E4Yom;Nwufw_aJnX-lJ*q#~{$QN5*&Cn$og2Vw)}Q+BT>ZK7 zlK!7lo;$DS_H)fOJpG3KdS~R^Gk*OzUa(x>{1JFrS;yG#1K@7Q-2y4T3#4K=&6BCj zR#k>EF+||lyf5mL|4M6E*$2Zd)y3G-!-+iJy+g!zb&n=%3frpDq0}@&*KeOXYPB(J|KJ(H}3n@3#W)ud;d5 zR&*9^e>If68OprpslP6b=enV8vC@|<=T`N~OD=ShBiB8!PXu;51!R=!TR5eFuK;wz zMZh6S^ub_pVmD;UHPxXO1q8#*GR`Pw+*KxmQAnji&b#wg?kYx54b!oJXe?`(>yPZe zoiet*?R*7*hnsZR^txC2k3Ly4N7Q(mGi575F9v-^CS9jNp+TT~=QOxXa(~-Z=F-+E z3xO9YWr{vF94KgJ+VJ6Z$j)KxTh#W*Iw2rCe_+bIlSrmBz8T%J*Q8EG4%Z0GDl{k2 zJ$U2_#cApkOdqlsV)KO}>pQ#6rgFIpjw&r_BJtP?jI{z1}Z7n3^i_*O5Arg~-Di0SdLh;%| zazZ<5LWouHQ#Y7#bLx7||6+rHJqxzn()UzUmN_x?XvElG9qwKFT8HVW|CTMJ7a5O7 zLG^5n#etkCxj!7yT>g zCmP?=CK)dz6V6w*ndL?R?FWKUsj0EKnA;;$3@(W0CR0rmeZN}DPdrQT$o&I8-DAJn z@KR@Kf%rt;?~yr@16*SF-A%+YKHiOA@9{8D0`HSQ>u(L4UX7ie*TXk2Z*x@dqJa1yqk=$E=!Mfr9GqDo&Y0cATvk#mQT^h{=#jcK`Fu`QdT1;EsIbFx?ajgxi9ph^ifm7y$xc2vw#(F+PhG-H#1eI)wJr+dW4lm7A8R~K(^rNv ziDNm^eT3kAP8qm-Y$zj8Vxi7FW_F0+4&_G8g%Rbxu|$RbT=UaiKHS$XBZ!4yV2KuC zFaWvyV!7EZw>ym7|NL=$ddOi1p3R|s`t8Ik+I)rb7RK=E<##!$_wtp?)qwuF<&9H- zzCk%BZvBqSrOAH7h2YT#;x?92-50j`<$X9vsgT^?zR#ElvvQ3 z)(yDy6|H99OweC;gnnlg&=&`(TNSp)15vKAnAamKTh>NU5}Q100x9ixZr5U>W)fg< zL-5&(R$J6-I0>!1u%n>$b_AT3fq6U0Os2U@(2%evJZ^UY?Y`6WLf)_5ReD60(Flua zrp{1q-o3)u%+OORjIM!=Hey%`^*qz)yu_LrHPceXChRGFy2v6sNm8S(PoTp5RLmTv zecWj?#}ojjO(k0vvi3iHK4A7*Q?Jf+ZBh$u>6~sO2k2uOW4#>Y0lPhcaQrA ztfS8=A}X3z$27v45*mXMk?T4>-jBO=6TfM5Dy}KN;jFrEzI-w0_Fn#&<>X_{XM&4v z`7QvQDDTg#%GD~QFFXn(8us3N`&0Xp5qyb*^=KSpVE=%zN0Q3|1jUN3C@w>&KfU$WG^2jT}XZ2=kkP(Q)gFW708prau5WmoL!p z5z-S+5Z*wq=29|4TM_8^_>HooaUKlK`xclPnryl#?vHwDF(b+?bh~IdJA(Dmro|Ww zTdCg^p()~M^16GYY;#JS=C&MRaWMXr2ED#Wso1q71Qc`AynF%DmcYE{<)o^~TFKw? z8Fg+f0!{hHUfP{pjs@ElDs+4JsW~K^vDrQbmLVzho;lqkpeRq74O%4|PNYpB5oFjL zL73mx+myC8Sehy9J~n;@B{|KPos_wpMrFTf=40>FED zqI|0Z(qL#Jhi`aR&)xcZx%)EuztI0Rmk1vhWJj)h?DvJa8PPb$gq&(N3Z2bMd$}j_ zEOfRVcOrp^&6b!(1Iq~8c?w>Rg7&e5{Z+R_lGgK5u~vux&9!hxmQ?CO>!2rKE^TuJ zA89lV(VBw72A5t%&)njKVR4~q4`W!1c&Ke^OWc`5^z2T6OCx4yHxHQzxB)tM9Y$9=i4DSvQt`$v9q!TIN12% ztxCyJJ(y(r397r>Ngy{5+mNjRHa|&-@dR7Kj6Q5Mc?if#)E(@Ix+P%bz&dbupLY*4 z6jEoLs1c6De)!|a9Jyb)Pfzu*_-(^^EWG}2>KFfbJUmnXUf#=ZwmkRe{c=5z8XY_? zeBA!Hldk{$3IH!aRPW_2m4gHQZFlW5cE7%pFFFfuMwvint`E7c=1n&pq;M2Db6OFL z_(Ga##KeU?`W7&@8hUQuNLyq$-%|RN0?J$#M`ei|119i+V^O4ms7{g39!OZHq9z>^p*L*$}-KqP8F` zEUBZ~loUL6ApbD*4pXI2D-dlo=!M0cb`BGSw4EC$EjqsIY5PM}BpD8^10SmCBcbzC z`<^9*kmXu!kgZ0>$`Dp}j%9VvJ4_ckui@Ds3K1fA|e)`oH;W|Gj)p`9gd5+-|@0 z5qc75UI|3cwSWA3db+24qRH8tzi|QJ#k+lab$NExpV!a%`qTY9|Gv*1&z1fBX3LwJ z(8D87p&i5O@Gon%FI}ZV4f=VO8|~Hgnjmc&8Uf+DCy1T>D` z(z)P`ICd>vEFvs{l`!+M|4yrTt((C9&%)`Yj5N=I>AA*^tx+Ifn(wW20 z&D)Y*HaFBasTE`*XEudCqz!nrR%uzs8#yJN$(X9{191G$cE46X5;W`)4Q1V%qiQQd z*=dZu>ul8&2f0){pQ2esV64c@hX>r>;bHIHW1lX}UVt124*FOAvgDaBzUE8yO})V1 z%PW^x$1|@}f9~nH8jqbT&%+t8~ zgd8zlem&ZWX2t#R{A|@Qt0fz<8~IO=tDMObqWL3)d8@hQFwT2!NH4mwJs;A*SlWHF zkJ@J8Tm;qrIAr1F|l*MKP>Fl)_2b~fh#`-L&vxK(_euTs**OH=R z1$q^=`WqK-E9enHE-g`M&l4GpSYju^ms~IsNUUq#f5gK>&$EMl`ZG5h)%yX@`HEjp zp7`s%yq8xhuZ43i^#3aO?G@qWx$!&~2c8_#>GtQI^ur(QgKv0xJo6>;Mg)K^J={m* zK0S@6PTsbk+|=-B=`Yk@9?ymTFSq}kdYrF6`SF#?+d3*=zI^G|%WJhi_ZsPviLqi` z$GVo{al;pC&FEkef$Bw*CNb;VR!#Tq41wzJsVqhN;vPOp0zw6yGukaRn!sFsjb;`l znl%)G(VfbLzzqI^pM(|3t?f;ZR5-c-r(XU0SGGeJ!D~!R|!R|3Uc~Iq`Myny8S6r+TUQhZkVFgS)H{Xw(=yv zVJXUXi!DmFZ#8>8IeZ)v&Pw6!s5QhiEU2>%5@kuvBbN;3WNOZFQCq6#%X1wTNt4_K z!yX)IfiM&IpYrYjgWY{EI(s({%}0l#`23sOk2ukWXX?LT;n82`-dB$+{rNpe(d(Bh zaYa|21Q;jr$rr*u`BZy8bDSsZaq8pA3(w)bCvo7(pws0jM>~ zF*Z8hNx(1jf&`CwQ#}3&OsS`*b)o{j4lCmgwrQ!NcD6<+&jg~AFmEKbRvOI+J?aPc zPw-q?RLDe7ITP6VlIOrplRxXsBo>!bdUzdCv0_I4NUb+Vslt;2=(FDNg%@OS>6X8-4RQZliwSXTg6 zZ8{bY-2iBHGLRj$r-DdM#LrEiUD*G!u0HWT+^|k6zbS;rQ=!eRH*SUo26&8VS*!^}lUD5}s#_Oe)%YNe1s^rp1A0fOvg>E)Z&W9vyS+;q<59VrOsbc6r5S#EBekgbnzA9}4}%C<(W-84i55%m}a!F7xU%p7=l$WQm!kJIlN z&Ox(plLy~k`DUWc8(*hvhxzup`<<0zi1K8!%k?~8e*w2VS%2~t$89d)nUmP*GG4-| zZqK#H`F<{p|6HFZ>yHO^ZrB(4KRI_E^?dTe?O()OwE(wjsl>FR6radE^6j*;s=*Ws+K!W#3N^;&kE9j%wYJj=;T__H_^!68$1V)>K(c8=8qbtheS&>>rfjD1 zlqE)ANiy1=e^0-J@G!c+%?8cGt<}s93i|k& z>xv%u5wh6kn)n*!H0U_}`MtbS`DP1y0na=O7oF?@^XYxu{!ITTPWffrb$xjO4$Nnc z|D>tzVn0@I9gXwJnGoRae1kmt*l+c&>t-iMa5&s^?a9N2kNUr+-V1lK{T$EXUG!Ui z-2dl~XIp=6-8LhC-uO2^=9k8K{fR$1c6qAu_#&L@{#^YxDZ*Kw-A|r6L4AuW-+r%G zCJuSD$yYCa0vL&W7-P@f4!a#A&@i&vq~S4uV2-*~D&%8kj#DqDG-tOmg^h7^WB-!_ z`@KHxLdl$RRNVN8C=@Q}7g4nGDBRrff@nzWd$$*BVcSee1j3chhE_B5NjG1n5V7+Q z)Cx?BJ|depU-Cd{!$-=iwS<>`Zt7m8LSJFHU8p>5ia@KufeMn@Rt$yU&(X?!2{zRI zZC2>bmrRuqe?28Q0N^NDf&c&@07*naQ~)zqfEk^97mrU*dOzJ&n|7+78f{km)6KhF z^Bs90fcOkSk@4FxIT_61oAzm|ofcEbaiB^}*{o9n#R|hFUMHvysd^C1Q8c4A4oJ+g ze5LN%jeSmJGX*DHW38Y;zeZBx+Gwjw#n*N2nfwcDDWuo;f%EXW*rC?Vmf}%j5t2@f_0;ZkFs{PVrM@@nwlee#lD(*)MU zy5@jF7t+qJrK_eeu*e?L?l<)2={J{YA+wT4|E&gNNbV?(br$Lx)k)?EE`Y@Vz!+;I zS4eA{L|_}%NPKG$*1N}IUuBU!2qYO%;rwAsgw@{>UW7^qTdOnYP-r(4!6p<6KME+u zMm$L=@x%DBTnvirRMZjOwgi|-dr<)W91Pk`)<%Qc3`1*Z47N@fZPyBA<<2U4QpDWi zkxu62=FY3o@0P>2o6Qts6sW9~G7(7*47$?EbiMwv@wBtKp$x$u}wO{mOV8~n+sz~oz-L<~R0Xj3=Ola+G zOO#DvCOsI!j|!P^uzTz z<*1X5MeH^De=kp$uY<47)t`g`7wWI;=LvjugrlC@{$M=M8t1uw&fWL(>QD5KP5Z}H z7so%FEr>I7|K{bj-WtaX^k~;U^S&oG;6?qvh~&V{$Au+P;P|J+PDSnQf$%D2_C-4PV11vj|!tvvU_z z6=(Kcu)FMjH>4UNJh>b93%BuL8n>j_Gr3t2&CwTb<`mmVCE0Gv2W#S+O_B`=ZX(oc z*})oHFi-%~R37P`&1iCa-zIBt-lIu8YR?TfuoFT7->DmF>J*gvcPig&Vk!LJ-oOM7 z?VPa1sNajddX?H4ld4jjsMKlM5Qy@9dFmD;C%7(-II@{FK?x%f)+HCkIvWqFyA4Z& z1SZrk3}r;b=2QrzP6oxKtUwWpfV9nl4IzbVDSdoaaG2ucXJ%%!oncB+UMo6ICSn7} zq^&n#E4m01qXZ$xtEmy#Bi5I0WC_=bLR8^1X;zPfByO$TDN_hKs%i-dDTprJIi%#D zkW*vGCQ79!U@3os4y2PPLr2MKIxMYX2MD&qa0xz0Bx2HCAUc)-egBZXB|u8tP$xeypE2M^m7H6C;Pv$@^bx2wFDnmyks!1SYIUv zMn_I5inVD54eoP(1_0X!*jdA1b>*hX*#Xe%=QOh3B))|+GBr9c&W3`-n;MbZTX4*4)!e)<}?Zy6y+%5vwa{g9=EcIh(ec=I3btwQuInxUpB|P?D2BM$1@j zipUMBcH@HQ?fC1pc8^~A(Se&lh9#m6n+w3$#u`;ig5H-npwXjtv2st4B-Y~M)T~0@ z!|85Xh@9IPc1oZ`64*7qs9xohF6Ro4F&v*37z1O-Tr$?YyU*dUKmGH~?O44pv8ici zKYx8-=9<-;nwG(xPr)-H4=y`;AMnhlYu~E}{cdFQr>}S~@8!LG&GIe){83B2b0@kv z-{ws9(XYr;^1~M@C$aS_cFx0IUul*+eC~L(o==Qw^LHcd$89b@*H4Q9BjF5Fg0RUf z%JZ0GZkPyzW@EpDDQ(dCV{3pwk28N|Wtyr`@vN{g0CHrsU|>5y<0q1$T{mHfj=F`uuqH|q-IkRW$P0`e}OWOtqi+tg=uzAZI*daW^!zKr>%5zf0i5J&s$!kGJOX(4JR{ zmZRaBgi)N5$Apv35Vit99i_Z-2#zw}Ahe>v2*X+ZTBnp8(!6JR$I>Vy`%{fclR$Ar z8VIvF=zx`_{&K6eG^L0Jlxpu`7!w*7*I)LRwyXq26wNkw=ubN^NYhKn93H^N5!J&B zzRo-V1tG7<{dQ zOg2g<+^CcE<{L^jJ_e9u<{{RK#KCB5XKIJjky;TcQ0~{&B+P^j1J>(fGn+>*H5%Nq zo3%?!pF0B01nNc3Tn7?}%uGD2h5k27*{@tL^kcpLgyr*Sauc>{yX}1n9=)RuHqsv- zy|12G4u(ZZeTVYExsFen1m56J#(Q}$e}J-l>jAu%uU&ZX&+!*%h2`eu{us=jmKl%G z=vNR2&Q9kom4n~QD;r;EZ~`a*PYUwqf0Hkjtbn0ee+WZvP^|mRMv|LBPGEMGr1I#d z#Rh>vdtEhbk+75#7{bU&%COAkOk-u zZ4;)WWP_i~@^*Mcf0^!@Wf|ba{iL+b&@5)3?pdFVKU=B(|F?IAZLX`V7Tov$|KazH z+z+q-6HSv&J8frJ&pESOYf#Z73q;KUPELBA2F9^gS^3tZ3_{zyf5Dio)^UEBmD@t4 zs^iw8#ITCU2Z%3C@I)V5j2}7G%&N||YJ-SK_aGAH)}*7&+;rK7>w|Ixk@lH|`XF?z zHOz{@VDm0^_dCG?vcZOa@fjPD#>^t})v0&Z2q_Wy-+%JIuV_wwq>i5WI81;s?7bre zy_2j3ce@OO*o9-z=jWu4fMD$DmW=e(^C~AwI+88l{w_6{1U$L^;B`_c6dsR!`@QEu zV1*~c*~%ty_o|@l&*unuh8776;XKkFYqCF}e|+)2mq!Kg7m@CN!$BY83ZKL<73vnHt%1h*t}d*RR<3t& zy-))q)&##MQ-O%ba}qSVz1bd|N+X$wah?}Qu<1nI>lTMX%q(o@5D<}-Yi@q1Rh!JQ zQou|gTol1eK5)fmdpJ76+le+et&R}c32FMP9$4YLHe&W&$81~5dbbkUT8nhIsJq^* z3^NZCGsjs05kT)Zf!4*sdTdivCatfYND1n8t@-k(MM1;tGW%CyK z;};?>ToV1W;!LoJN8xi?62RU}My=f%09%E@lMJ7TGLPMuIs%xCGul>2l57BYqisKa zCFw*&(v_^MC8b*|01m|-GX>w5H27y0@kZBg&iyZQd}_@CVfCft@~C(~Ba zH86^-gHu3_i$`Z~i!!~9=>o%sDT`wN>T`Zk+^*AXOVwo4ZzLO^rTVts`M4&6|%qhQ3k*u@jcDUc^l8Q`~mto60z%)a0m{g^~axU zlLEsS(62;knW+=4a8*w&w5f?Rl!B`?ryly$-GdA%jnjmL^Kx)LF*`tM8FeExcEXp} zg^eCdE*ktkaZ^tJ7(sNJY}xx-T@RX#KOcwUWDvTyhjIE!~rnSZ^SPQT6oxH+))=?@tUSA{$~|4NF#t)@R+yX#($@ou_M-CNy$^y721lDN+k z3p#rKv8#n?mu*tL4fZoU?l|9+PUC1h)69;o_SMxaIV{5L58E_Gd1pg2*cY>+i6B$h z^)k_6t*+9U?i5M$aztFHS6T!f{X~GwyC0G5;d5nT61xs&2)xVYf8Sanx`U5h(4l15 zTj94BQcf)1fXp*RNE+EfSi3S6{UxvSfsE^ll0R->vs^Lh*6Zc>mBi)wJIE8MSWk6*O?xZFHC6> z@DJC_pFJyTu}Qz#qf_#_m(S@xN>8Uv{=L{~J*V=|eV~xQc(vlwi9xuH}1EJ9us-|H0&rkyWHRX$K@K62%e($Zx&TBTOza; z@hylQQoR|)*Is01JI^e!NL5b{CeHu_%LAn8kzDjty`n5t9THZKH;W z{0uLG_Bgf$vdW>qpeQ>VK-WVzW5~?5f0G#}wrJa1<%0$xNM9sfzC`+$>Xf_CS309Q z4@uO{%_#hd92Vr`vu6|r%jTKDX(}q40r}SUI=i{+7o?jH6JGh&LR;fT**qT@m!7(d zhnmF`;tgLeZwkJmXgdAwSrLh=ayIGH*U!GV{g_>)-&dc##(0r_H%{u|xV@|V`}Fhj zd6piT-+U49qk8B-&dCvTrih26U&UMZ-wz;7;oqwZya>tJ6B0MMzK_`*%+vIT6=bbR zp0?zAx{fn-bs}c>7>!d_y(j$Itr~U>4&gNY< zO3DK*(^ldf1GefiVQDP(6J=dqOhoMIjBPS?j5kvr&M3p!hdx>};+wqPI1R!yhi&{x zIwhNfiKJn>*2d|mW6TkNdZ)v@%mPDjk6Ssq99mC=aGEbBK_Z)3ZWxhik^yH&AZ!SW zC3JYeqi>^yF;)lER_0oJ{mHO50A>{B&Vo;JYaajGOP;3h^Y5ii{bYqMa#f7?i|tBJ`*B<@Cn|0t3WVt`>IwBN1n8mXku{r#F` zGYaE?Voyxx12Bu7TE%2s*#rgSv4fEXAkDmFhIUo1$99dcjs#Q%A`yp_Wh3yPv_H*^}PA8mG(0;04pVG^5Rjp zO21Lcv|QPx%OaiDx7PQus?RUL{9W~<<==l{^7S~K_VobEVvv*3um=5kw|AJ{Tz~m=va7-z_$&bFcUT9t>(km<7*@6 zL}aU1Wrb>ez*9=>j}brQmbtNp{u_gAElM^?1QHyHvHOfU+fxNR)`%p%^N577iuVht5vr0l+ZBcGfyk(bNf z^%Ut*R{@325jRn3r@jRHz`D(Ed?s6~U=uGosgsX67PBOy!M(GNv@Z1ZCH+fPKLq`x zA)V_*p3K8Ih0RJZv4$(7=%kbVTbOn(=UvC?&%@#HzrcHsZ=)<{`Kb86#r0Fhy#FaZq?QwdaE9>k zb{Q%7m2~E@cAjqUplyAacI6(XZ@n8E`RD7iPsfw~tQ@W9aeG(!r~UFgJj5l0es3Nv za3eS6qljid&wrw!veOV9&uhF=)7(H_u01HE5>O17YU07Z8wYjjty?3z7HzgOYua zPGh!Xvv9(&&V@jX#|9A-3uC7oZ1ISd(gJF#WqftQ7C7l<>#_bJYaBYv)rUrhhg)2M z^$Ql~?@Mug!814~y2~%L0f|wIg}pd{C zXO4N%_wyg8FXSxSOFRB~mDlBloNw33_C{*{esTujD!02zb7muq7wN0~!`Es4l{ya8 zv?}*%{!8__DrZ`skHjVrWgnLkzXUCr|GfLO$hTRe_tdw@_pogW9}W{qm&6N_DW`0% z)u8c2Uc#M?6iHu?ZbF_HGGxe4s!`7TLLNUmqcN_A9bufz>i7}C%33o9SS&0&#o1GA z5#NPd)6F8Al67An!f+dN!{S{%J)j|;o$Mg66hx93vbm=l7ODp4GB5u>w$-sX$DlFm zQ3qB%VV#ob#MGn_31NpK2DwdsZ)!&Mb=h=lc$F{_5oQS(4P&fZu=I_#1&ay7JB>x- zVib~WzkIt?M07tT1UaF#KB1xEH_Rm3C~E-TYFbaiT#cQR!12yYaXhU>;}Av_^9R0b z6OpcfmRLtFjsF=nwpKTL^uV6BfkC>vk1z?z7xgY*U)Z7w-RK(31A7eF;FQwFpU5@f zn{vWx^_dIVJhC8j^I`Lo6}n0G85~$Ql63F1e4giy z7wzVks~Vf%&F`=F`)bJBrwaQ&9OjU*9xM~g`DK)`VmVhUxq7DFg88B2;+Cr{`Xhrv zr?3cToB9L$(o(zJj4AvdnCR-?l`k1WX`)td9nV7NH44J8rp35eLZFQ>J4CZzg za6$3IxyVVHHco4Z?f;`6bh@G;s-7DU?&9d@R9{U?3nrY|Ga*6-y9JqIpjX0DY_aGb z!O;UMDjPyA$qt@wh8)J@plW0EuGT2pOD6(hMpM{31a5rf@gApq&XxGw4s#o1n~g=n z8hfVH|H~(6js~wbWBao|!0^=D_Knt8LUi1!No?-R18g&-8+dDb^WZle?}%H)6z;@3 zY4)A!LiG9k>e>AETj@Wv2H+;%LK)w{tCaW_o%b;8NTu_g^r}lQ=FL@mIiO10ENv%$ z%zrHLHg4rvy-fG@StZ_4K4;Fb(;V#8+N+BM=&MWrV)gp>(G`FCW+8ukVS-3fBBNt-W^csC{C1o%p7WvrH$YvomCN~u z_kO{=b>#{NcoiRecDYya>~e3)=YMZ{R%_u8V#ftzLSU62A9$TU%qsW?N%`8COk>Vf zp{Er!V*`^u>SXQHT>T=GnDleFg&{wqZSXr5;u)s8e?* z@#yp&!u|&?g#Z{*5-K=SN}Lj5?ExZ(EGL;~KX%9$qSgpzgKnE+N76D>!xo5w{7j+= zY4=dy7?ho2BTC-Yt*(9a1~kY--98!N4r>N06Sn?$lc5S~4Sbz#F#+*#%b+v0`BtIM zs0YHJK=wJA892vHPtFYsjzF9Y5(F9>DSDnNm z99yQ_AL*$DwzFp!v4g$f_}>k`*`3Zi_rHxrt1l6(N`C}xA08McJ0aWRj^Q`Z#til6 zq-%P}e>Z-B-n@?u@K%*66wcw_?JR}AgHL#b4fJvM1E=>KZE`A$5cr1k&uOZC5GXSt zv-(`dSl(cJtXTdDLw#RQke=Nu%JsNOY@x|f;+C3ZV<|dipcgk@i%zX4^}ieHP8OX9 zuqbtBnT?n%?w{3(%Vgv`%Eq;%?5+18pU$&&pL;vhR#~Na=yd~rM6f8cOdI1k3B?wf z=3j5{g|lsk4~mVQG_k_WLTfBooy}9O-t(4DJunJSu~RAuN%!!W-B>|+dPD-&_%b*4 zt&F_+X@8#RKKA%4=e(FJFtItZ-#q+&_MF->?Ay{K_$nfHh;XhKx%H&yv@t4Xvkf|> zBfPDopr7=-0TEvh(1=BMkrLAVJ?X2f8DPHu-l=z%PH9vh4_D{tn`{x7M3{^s!PEz1 z-MMErt@F@d9M<4E9P$`=^0`R-nLd{ag~G4Hn}h%r3Onpa=DD|=vw=a<$}LRNP!t~Qt#;IXpJV7$%ACk1Nrx0@Q9)G61 zFaa|+l4f1VCo5uUFLM}PYVxI!t)w6XRk6(hbRrR@`@Tz3@0`5;8>jw_XTZ(|fTFzu z>|2uzaR2}q3`s;mR1s#XIHHMsNf%*IgkI0nbWR^)5=@6xoU<3!OW_7eONEa`832X5 z+($o)voAW1eo&)=#Lmo+Mg*1-O!OhXh9k*LE}N#ejcZKW!hS?oY>^xHTSr;}@) z$pwQKb$oji4>MDjLDyo-b-{xX6%tDEVV!g+;EN$_7d+xigV{I8^qp|f4I1yHOupN> zzd_=yhQZ;rfeg~@z0QpZQ8h%w)I!f9y2qzeIaz)Tz7T%+?BHF`WUnE_+9U@`mA7695&aiE-{$f0b{)cAz_*{u)zjh`2 zcE$Ww8hSPTdQ^h~P>L#iI?4d}^SI$l3{S? z8y5~{|7`5lHi2Pmk7E?y)(?1{B@VZRT2j2d*tYuahz^?WarWlVMs3Ux0Dm8&ajZ68 zT_4SCks*nQvSmc{2obwE0r6S~TaI8`8|`_g+Y;8Wjf!zDf|X^CyYJA?F#uNe{FM5?Eq!{saYKeq?|}&s}Ld7%wcV; z-}_(N;@mw*KXGHmWT-#7!}$qn z$HR`1nuuTuawRbe7^{=1r~Nge;x5E2H>o*)@V^nMC6jF1g14GF0Y7(AIv-nf6>13#MW(LLG7K|lCjna!*RT)=EQmRkIZxKrI_r?by+ zvJ|$zoT$t@qp_%E(@2Q)u&{qVe!?-oRWw=GrW82o*~b>4S?WzE3``+vtf!J`o^(Ed zvU|k9Yb%=f1(qU=Ey;r^rh1@+A@pBj^HUoJ*gDq5L|o7n?#rR1?fp0#lxh_X+X#&k ztakQ`{Sc&l`1b*LInV!;*Yl^;v#`L44qUz8-vImf=5hKNe5e6X_$<75kWFvuEpGsW z4I(>c-{hZ&N`-OaSrf86Nu0|+rnN_ec$VnAEJ%=XJ^1za6CIiU1x?=25$0?#vBwXL zTLMLx6K86a**|4+yqhIB*`EqYm0a@ zbN`6*(a->G%)w-|VxN0#vSccY?e^8t-f+HeoKUtQaY|aUJARZ-uZyLGq8|FvxlfqBp zGsoNAwf+=xWP$*h2M+)3x{`KFU@uz=i(Ti9``b-#>0`QpzMChpN9DaiA!Av zyt%z*MFk%j+7~5@hj5z`k}_nhQqak5ojo*ycR%INIm3?SE?Y5PfKYFcLm~?hir6J> zo$*N5S}2i22Ps6uR}>R*cKB)>K6d#}twku2X*qSKFJ{%~n{meS&p#8Je29Z>Vb55k&?dkC!;&fng?YdFie&r?-rtd~ z^%qu)OYIF2>0i3Kmn^s+?H3WB>teFgd;a6!_!YUNY`BMK84%&mKvXe9s*f~0-&^gp# zT$zbTx<{BG3`Q)_2diO0wY3D_K@+uWEY!DQIll3Z&rb+;DH_{4EFe|+Y8XhIKdigm z!&Prb4JZfoW^&_vqyg!2ftC&~h;gnH9%O(!Xp)2lWT=+~l!8gXyzmKf&Z62^W+Ro zcuo)!`y5;<)bGzlg>DNi{5pJkr(U&u49_zF9{A{3Jw1+|D&I~&sLw(Anm4^m`U+2d z)ZdZ*I6V0sxh!<^fu8uDn-m=zAV%y>W{<-waigC$t11U&mmzsw3r@=g4%^P-$0;|O zUY%QUHVoq+o46-f68ne09PV-UNO6n(Vf#rLZ&2jqPTwq>aBl3A25}OZ7-Mg-wUGX` zH5HWn;#ezS+1yE=!(&_m${jrJ7%j1{Xy{1Bbj(h07DX`eQhsln?STs2VkJWuZegH= z_2N2$Wk4ap;Tt!32aSh3Gg{@!2;<34d=P?>_HLNor?w0|XUkDxKZa*KOK;rU_k;ax z15V4jRFTt--OdS(50@)>-}~{+dBs!_y#DF#a`)2C%Q=v6P}Mtl%Rzgm@=vPtSOdKK zDSus7sb&BQPsXnwU_AZ!Q|0pTZSyfGrGI7TL+fW@aHcH_8B4U^d3@~fqy`MCI+T+i zy|TUJIxbx}(Hl|_+%F`9NnjNgVn4l0nd`8pq-n6b(>leLsCIe>H;q$zO*y(~HSJJJ z%K96zGGg_4n}ueFTI-TytiRQh2iWTeXS{bR6Z_G}K64{jp$rE;qJz?)8NA%)S=A7F z5Au@Em^;AXXqxUjFAZ@>vHC*N7N@oizlb~db^Wed7t+(34Pv6arzff*V#hm^&(%=x zD7I6-xXkw@K>wg!i~zJ8K~${p=|G1Rkq*-VKGf?AJRikrQ~50WcHF z_oeTM%7t`3Oy_UNKdvXI-)5IApGn7^Je4yQwar-)1A{(zK;=$`o z*?>ViMpJ-7H~Cy)r)oX9oUI6MxUP50D(bC_C?^w+6T?uObUGCh!`iTi9L_h6e$M{aeunoPlN3HS^H z5#s*!s!$Q+qt+>9@~(e#Sl7?v<+%O)ZS%wUW&=lW#J20dBmCPH8$Qn7cvy@?V9gboQO)^2PM4@_z#FU2f3y8~Ih`*`YXGu#scrDshsOLDKb1gX7qe zf}Ngk$qTUA(9!}(y6=^$?qu=cApI*bUBEhxpphG5%r$L`WNz!=!Axv%>J0A7#9ev5 z`p89B5a@QGOcvt~t=cx1Bt)Hj#>J)-yd5IG^1wQXyc^hqWb7})ZO^e1m&oP}JT%{C z3ykxNj}sdOc>r{=6}c*lb<#jr>Z+T+!`@MKxydA+&V*S4vo(A|W_aTXiRp7_T>APU z@M#)EeQ^Xvz2^1usQ3G`F+81~CI2yUcC|cu4?eTp2d8g7+)TfZU%gxU)%vgUpGfCB z%8e^41gP+2JkS^{JP-S42(KS3ghiX4aXfQi-&un{Yg5Ei@g4bl%gO8kCSsA!0_oc01Ab|UD)kKxaafi ztc3@zYq)zI-g#{5bjM@eDOV!9DYDU}2?rbd1Qm9F;n7#LL(oZM_z zrNyx#3>UkY5aLGEQB&O@hm1EfqU%8xg|Naw19GeisVDs?+%Q#=5u-HRqm!%x7nZk#wsXk?p~1{5{)xc2BA0M1+pMcT1s_@ILZR?k;GcO17Yc=6jKA^}@p%j!l}jU8lav$ORhXMC z#_d8rL%haracBt`@)!LNQr^qYPqRqLb*d0$uc#K?BPNN{Dv8`Rzwy2H zJ6lyK6kdgz0Vos-g@?g8+Ju)zd{l~B5seulZp_TmgrigZi_w20Ip|3`VgNb|`8rmg`?BI>pm!5O{W zh)KG5aFoRcrQJWZZju*L=N*s!BK~mG3x&cHP%{99LZR?PF#a=Vd)lAILslBIG!mjF zq{|3!Z8|(lEVW%}R1@bKCPLY&VjzfYnF9BM3(Hl32x7pXas+HZg|LfKmWp5`ECEdd z-jfT|3-yW-3Z6u(5G0`1S|Eial&uKI!~=qb9ta7hA#4c*3uKwxL6rV%|8#!M`^}v9 zd*A1M=K0RdnRzFh`CUj+5Vwb{b;UHDkJf{aY@LPmy8gHv6SeD#rasy9Ua4j8MSNNx zY0SnrXz{CTjDf{|c)7av7c!!qUOLyYrTm@IMG-!opK)Z4@nvFr9HJGfLvCwLPbCc6 zG4&|)Fpk8Mo$k25m=#=aA|Bg;0z&d>yo&5tvwNPgU6IAJx6 zqINRxZ!N^!45rFd`u zYLEUsXlP=~K=GAJg%+^m`Qf`=KzoCjdiYf9gecC{VAP_7Giq31nH0VMFy-r7cIm8( zQ};GA%v18?e>rdxP&}LAHfkrbU9E86{oWVa_Ne+ylz)Z&4|jzAb`{3?){5QRtR9Z% z9unuDeq`+FDEx_Vy2UdoY(guft|WU?1O3`~#g+yWk*qOdKq0M3C&cP2~DYPoi<6g)y%@dExA7Ft>q155$(4?H%gd8`y zM6n=0>2sOJ?X+KFHlSa{a=*~69nv{VLfJpdYQ(0{rMS)kYNuD6r^xx>!{RuN&oRz8 zKg%qICb_72tY-*nVC$`5l_%jf{X=qt1s*c4kifkEAsAQ-T~L zl!Q^=5q@WC#ioqH1j>axT*rWiOF}5NhrNPOx^c$en$$4(ixo#Zt?7hwDlSK@`9^B@ znsWR5jJ~yHebV6kaQ&My4A#9__*p8t##0=`&g<%5V|hYfTRsHuk? zgxeUW1wo?tyS`sn$=0t){Df`BIH9z+3d# zq2W0@qCQktD!MPSrFO0;)G2y(Bi>J%6fD$R{UPi~s{DB09(7#?d7LS{O?d_y$ylfP z18Yr0I&z@~;frLb7YPx3dkNZ$qi9>q)PnVD@ilNt+R-+C?#V5Jj9qDwU&-hVA+7*r z`P-ch+=rE)Y0W<=NMz$?2`Bl1t1lw_V)?aT^%5s(Ef3fDAuB~ZVrZ`)OlTVk`0C?( z=8}R_b^>*TVE>Gw_?NoXWl|SP#{MSt?cDIQJ(BPgttm=LQFUd{obc_tmem0)%a5{g zN?~K~DZdfUGMd<;(nR`Qz1!XZ$TQ!psXhIH-&=H`50Bzu*LN;rQl!lq)GxTl2ssY; z<4~IOwCLYd()i#$@VBeFP+zw};GeosRZ4r)n7V!C_1CpedIioIb&MB>Mup6)KBz^P ziD(`Tk1!Pkl<6RpiLP~*sxsl-Qc?I4kle?a(Jp;1|G`NN{KuvksG|B zusoA zrLv7ItsH#Pzh^xV5lS?fjFUrOC_ga7e+*OM66O#PqKRZpw=}b$(8>E|*xIbKR+)IGTh*88Wp`x4L8)`sc)@B4@0DYh zeHPspnGmI;X+mVI>kIX|z!{~{5aT}f$0{AF)<2kmlQb^je3?2Vjl4p^HVDj^Ll}{f z_F2Coa=yCO|CY{fdj1wUvVa@20Gd0BTg5t<%KG_#zCRLkKo8nZ9{>E!Nq60EeiI|YgpXmJk&x8m@B_uhwp z*4pdrb!N}YJe-+1vy=67)bOx@*Z=?kPeWbV002P!kD>xFk^c+lkzES_08)~JsfMGr zHh}ZL6ea*2>HjqU8<3FxSO0$;AMihmnE!(@65;=H(*JlUlED9Ry#IK5CJEp_E#T-s z6#fT*9sN5yUwgXE8>GyxkO+eZ`NH5e?D3njn#qK}GBB5X!>K+pK)`vkM&|6VhG62Lc%Nqn1 zadZgfd+(~QAPMFQRSXF-1PAHxf%tL+rf#-1r4`G&Pw-tUb*Y_}`m&z2*O(0dUNLEd0+NYA7oh1s5Jao3%2JWz(jF zg9|!Gy}wcGIy&H0bCzPW`=hMMPfvbR7SYhZ^(7~+ zbTAjV2W}Jtu0J$F`4_(U?dLHKta*6Je97y)CuQ3%%rayUBi8qk{_U~a;xVlndAZj6 zR-T1a**2a~z4WaSw8Ms&p21;;+Lk4nwKQ`f+$o0r3q2IwouP~tvo8veY=Gqur9ooy z5sc4k=|PCn!ijE_=~zmguPkyMmdUjdsxz{*k5Ky?ZUQ4TL+i{`hT3Li;J1+N+nGg% z<)z*kX&=P68gdf-hcUSzA?kez&s-j;S(;jRSDj}_OWK14SHQkRBl<|oX!YXO;7l8^ zWHCxpFC}`pe7b{{Z0CHkx!gQsuqpbo4i1f{>I6@0dD!5kg&LiB=WX2+993r$?;(QR zK6Jx+Y@HPy_y1X~S&T#Sx!!@{NbVXWD*89kdYr_18kBgyr!`AR59GoAvy>JN2m$=* zF=#N=e&VmY`^0L4cq=H@!s4^fqeE%y5cwjz%C&1zK$xdoO>_P2=f>0dE%YJ6frcht z+3RDblUlE}#C_g>C;8tkJmT0){Vbiz^#u~H)0a`%j{Gg{Yqse{x%Q?)SvvUZ~i0f!?$RAtV-N>ZO8=lGT?A;CF>77mJfrntBLF zk>W%J;7aA3)VC+mdf z^Zz2I#AZoS#3E$R;xtm=gl0SUVI!3SX!k*0ec%vfHN(z&{Hq&;wHDPoXNr84f znlwA?_&_+z%wlVp>ph%vplry5^te1_r)(sX1u^-1Yb15D&u({>A?|`wSvuOu&X!e7A$S3-dZ86*!@rvCE?p4$ULIYHV*< zE&JIpTf6njG`}=+eq>G8sEXK=kDXZ`5Zw&9S>+VpbIz&X^1^?3( zC2=;V%YMO0J-3xKb z>5;$A!oTQ99wVDnAo<8xkHY&D|DKWB%l(8&H%)bhh9fad^WDFjA$DDt|J+W1Ax#38 zA~KirD1MiM8vmoJQV+x^2PTZEUXBiNURn&LRR_meji+B$Fapv&wMzND)m%%jtjHTX zDLUZGbB&M2$h-<=r@MI1weoRqalnuAgQ3{{?4{nn8>7U%yU4U6$hCpH=-YSgoSy8MS!DnQ065*n>GmY!hxB*6{?#1(*2eHTnP z@O|x5Es?pAG)8=m*&-szQRTui)#)L6E!q(ql$%VtoP@J_0(=5C$c{6{%a^V^sUz8 zF&qQn)7_b8^i;p(I<+M*G)?)bK;0za3&8NCztoSP%xw?fim@UA8ji;Z5|a$zQa2(x z=))Kd|CdUZHalA~f(<>h=o?Z0{=Pbf68k&EhrXNu7Hlznq>C)R@Nxh4v?_f~;V6hJ zdDMVLYLZapgjk`@Ms03JY-eA>R^?}jmE?Y4;>KAV(C^n_@LOS%*t=3x=xwT~0RMG~ z)b)4+@KX&^^$1x~aG&t4Sz4o1;xJFtrwG|5Y=o-jwt!p+fWI17Q!25C_@rmXPfep* z0_ElabBnpo$=4a9IZ~xbY{j3Z zzbiGiB^m~^6(SdpWMcn1@eE{6LQ$Z-Ybpa8PouduIaSuTf4hp9NI4)AahQpWv%}r z@nJ*fz~n|Opc08$^6^W&hoJtQ;ej^Fmz7Ql#u?aMtX|DXf;I-TTp0`JVjd75-0Ed( zTo4fkS0?z+NS{9^<%<|0oO<~e#27?`Q7M~NLPbj^oGOezWkJPhL}ij+O3W=ta&wk; ztfol`%YVXz&JSJR>7y}FRzHB(&4S1njdc^CX@8({=Vice>oV<8Z)l-x2!_ZieBSibyar)d&?goBoFK8p*b(cB%7}`E=)(dGxZEv%#75x+3FhPSF|ifC+-* zRohtygH&|_&27i0&qWr^&A7MxPt-As!MgFVeSZHbn$mhQPuqyWrwKjuZ12L=4$_h5 z+wB8r2?;yLjZTW+#JSC?SgX{wj9^z}?r+=2RgZ=x64aFHetupEQ!3X~l?hv&$dxiD zHr|z)T^hSzEN{FJqcq&k7=*Rnj!f3O(CTLVCE72nUe-%%fBwP|RVMZjnSTd1vGl`;gb&bWs z({EFaS32j(^XavNsRyw>UUYT;T;&#VJX`qhY*_t*p@x^_EV0g8MwOPx%{$FhLgw>Wb$VMoT|1!4n6lJ3u!1cHd_G)2iE69H zge2Jhm56Jy@*Xw&v-GTj-0YH`njF3Xl<^7Y z^()fjr#fE9ctB3@+o@{&}4^SAHz4`$am3|{F_KY7;7c}}OCIJ4ry;$=qZ4emUabrpJ z$#-u1MPd8X3|gc(8#DC%e(1b0v?9e94P#%gtaZWkvP&m76q|@c;+YNUrL)1JT0hdt zscOyc2Al|$#G01zug~+e1|~Mv{Cv3XDXhAz;Bjt`;o(q+W=lXU2LF}@N?CM@`a04v z&~KMb&*zmH&yeAmxH*rxoHU{F)300^9xWsvUD$lRaEokZF(u10E!B~TKFFH=cz&?W z+0U&{#Jwd^Oe~&%G_taicdE#}!LfFeR$P7ehrZfSj9Du%h_5=>O|t=2T)2d&7~FKs zQ&BWv{@hOL1!0vs;;9~O?R%~v1Z<1A=;L&(R0WNdZxN0z?m^$}_TGggOi*st z#$%3$X7-QzI)t0GX5~QROLf?iw=almOpvwwI1-dul`U+Oj@eBvJTBS>`rpVA>_r#X z%i2{>kKb83Q=F52$T{B5CWskv*MkX{#PmfJ7ucU%;t#a}ckJMXeAlEFL`jP-xrIkK z9PK>Hu-ga{Y_2{xuR@%7)>IZZqLaRvL_9?Snrev8FjvR-n zWBWNog#8IciSW=9XUFHEi&p01r2s00Kb=D_(;LrRrI!X!1(2HuiEDWBl6S_14{=($ zviUAOPl#D>CHyF?VRyL{?07o_8>CLTp+WE*m^`bJEM(U`j{!CQWJjbP;|=9t&=GLO z1mbN4AuH#ZhjJJ%n)^B!tU&GVor3TaM+EQwnpNzTc#i1IYWO2+zm&Pk68$vPal&sl28KI&1?b7Y~E`kF6-*88bv7PU8c=jUrkz_Ds z#|l1Q=jAqPN2bHRAkSaN#;NS;b~bi;j0b8r`skg45X-+RBX`rjHy59W)Ui(V+>1E% z%-%S#P*FvOBW9I!sSD~6xB9D8D} zGm`N`9bqQ>+z^(C{D~)se3)6LDq1E>~@|ma6jn;7!@NJPib4%Z6xaNOT zn~v#yFjtJv;`^8E$z?ZVB_2Q+^V&f0>zu?S8@D$!Am5dzH&Be zx!d8vh8g)&aFuj$f&5oGL=Q;CNSME;yqq^L)lbT;yAs{C^^0`5W7l5ax){b+SlO(>5N7E?oocP z_{OH04Hg9F7G5xQz(slpnQUqC)u@==|2S}Bl905YV2+M|%y6#%v#OV|`rqT58}{P+ z`q+9>`n~kxw~yCKMNs31=H^57O+&&$M(2B=@!w>vyn-)Q7G74sZi(l$1Je{zJb095 zLoGrF6=;BRYu|}}xf)CVC3Og!=tbUxvE{h^yDT&F=tmsB_@kn<*;U%H>CNN zcm&)I4GoT-S(~kU9yqyb5{&~VFc`foo;hE@KXU&>p$GP`yj7R;tGuUaK~rrkN8o>hD6Lud%yKyN_*c|uwaf+ z(Q6=B$Jzl8fITP~(|_8EQ2*xVzv^_0Ds=+5!RQ@83kCb70_FL=hu{~YMFDAYtk3L* zAXScWKv>I<(1uV`$pwR7IBXvoEw9hfzIp5aAQp~ zULE)27_WU5{`U38l4OTfE%UgEQFazsFQ?3mGbs7j*|u*kKQ$8Z6E_#x4?3&%nirm+ zr*lr)ECDR2JuZ+OkXe>Q|G6Q9eGc5?r>`5@LX{?ZWw5I%}Jm#%fN*t{f@dXUI74o$ar!*$?j^a2`FEAi)Pr{cjQR z5!31@(IQcq6K%BbVp&rjzA`c1H0n+{aU%7Zvr9-s|611rjh|+IH>zXrgsY#nyX=U< zPRwY|))}zxY#op?Y@8THf;he!UuPPsl!OleE4CkY?xSZi4U_nf>vN_^WcG7iYi$EY zyYHR`QISC}N3#84>JB!B=ylJwCp= z$lx7UdQBvDZ1%)9ABy=xMfvy7b3a3oN{?O*!vji`@F&e_sxMNTWeaEh1N_m9bKPA> zSs{ypm}#>L5l~z@Y2mdT+RvSq8_)+0vTWx!1pM_aASYe(H0KW&t5JF`r1`KG_^26W z;d$q0hMGE(3#S@RtYgPjd*%%>BhvH;4LyAVh+++X(p}h(PfW%^;@xMeI@;P4={j2p zg53~|OwA4Y^}4$J`Y4pO`$P5{6Q`iiQC|f_{rJG*0qmZ%(Pz(71WmJ+aY0DP&0Iao zHbxHwTFG1oKYsTt;SxV$zRfHhDJiO7>@{An3r{lLymFnN#zI_&t>4#iu;T|D7lzAb zH=dHneeE|0T0fU$6Tgv69<*~g%QajvN9!m$OFvkRG5h#jN0Cq3pw?w$5aB$}WAls| zxA}KNXg1N)LrFw&a^2W#jBB8ItOnJ)(EMCEsif^} zx$(RK^Am!>ctPaBEHY?KbyD~)TD|jxwgt87*i7u|g*=6`%LOZOamn&VrAxdO`I|}| zxTncl%BfYYGWj-w-L8HcWbCDZ8sj<4^j7*G!|~j>?>0`&4>9|U(*6q!sViR+&s`Kj zag=dQS%qTB=xCq=uql?AKUL$sAYJUcI3dE%!1?Gt{r z=Vn@c*|LJ&SN?a|5w7c#va^y`{9%lur2<#fUa{6y(W({~ohV#?@-=L30J$@qe&(K) z3?rn=< z)t9s&nNZi{?`UhS8Fa9KsNCEIVLV1%O{_eeW-pC=j_<2yLmx--d@GZ7y)(_K4?td-%QwH-jfkg*qwQnzs{xT$qjXfX6S+3aFizES`zx0X57!UyUVn=Exf4RyrjHXJCI$E;6v-HTKIB1g8wlwT zXuIdB7@cjKV(Gi(-gpy*s;sA~wwf|cwM1Tb^oGnj5*dHfEmmv}l?}Uo%HuYut5&J} zMlTPcF~RlCFsd3lbDevECbe5V#$050Gw;y=-@0t9MK7+m=H{`gY+;k{F5$+%o%B2U zhpUwHE$_3!@Qx_is}ITCdvGY&dh8w9 zCo?TBbD;oUw;rHxSjfTw2O}L;A)*+77TazneiwcF8+!Hdv7ly{;q2p#xSFe{wg|1S z35(3vV$Vr~PC4jJc8sDjeJ(*sZarB3I~lyo+|Vl#A)=(BHVuK{XCHzjg|F8l{}uHf zgBr!bW=G;SXq+SlK>~{dzj$d)R(iA+c<(5K*Eh>|2eMB9KB;a;^@?~?k*CNV&qJkJ zB2)M%l8Pl~8xHbk%cc(<{-3fIE$0)|-u>jU4{$Q5P2X0R*>^n}deSac1ARJUT(9mjbSy%Bq-cJ@EXRVIBEkOS zI$R%=#yn8gYTsyQRWsAhT`(=d&GuGl*^}5Puj;|tCLwU#Nn){t{P4EUiXJX7bK*&Y zN2OKbA0hC3IQ8E2lcv}P<}`#+sVht|8#hW*u6H?~w+xWhQ#W8}O--m@^DY}&K5My9 zgCp&@I2R>NbWq@dUaG~hjtKmo-BJfQ35e`(iwq-*AB5u>7i{H(mRC-`F_39mIKga} zf@bpMG|ln0V%PrK9Zr`KVcYpd6vZF6zTP6uVa{WqTwZx^e`for>y2h@-|!0sWzGMRT>tzs;pc|WCmf^d~+2jW|0UT1KDE3S4kg&^#0<&C$KhovgYdmtSw zt|A!JnCPIZx6-NuNMBDD<-Q6WhYLWJE%wIV??|XWaVmYt)$`B!T%Ox*}8J4 zA?si)UHqvZ?7Tbe!qPzWVJ}ws?Lw4^^7JK;^$>Xk^!Wp3fWL9&BOBwz65T!isEpUe zhWY)EETpe&GB}0-@kS!zs$bL1LTQUi@Efo{{qqAYWgmpOYp_=N0=uc*r)%-)be*I! zGyCyHy3vdiK}nZ{LF?hUoTE~vgv|@Du-z6}3lUsxPs@*w14>kvV+Uw_d zbiaUX6_RKoK|y_dcEf?yww#gThwM@T!ul+obD7daa@Dbivsh>=goI z^ejj+4~0}mzJ#QWj(?51orG>+GtXHC>Rs<^#J8vBh*qw~ySA4682XqM_G04kBw1== zQQZ0N2Ch_BYo&_}0N(yAn*SCZQ*7b!UP%tzsder&9Lno1`?zwmP{-DLCh_pb%S0^e zVF6wy#MnF3o!6fDDAdNs`+kiJIA^$mwxw%xFR!8Atj7|S#6&#Xr69yU^83w0Ma}P5 zU%ATwczact#CcfAGfExq*8R*CCD5o+37%+fxjxON5&0A{YZ3XIc-R4b(S#S-D>pk* z|KNP)>NNRh*vsSPDat{*+d=!gXO43$54J4)-YpviWRS{idWq%EQ$SeQ*%MBmCTdW2 zcSmQiOasf7rLm309|yZmpK66r_#_#ZCwAyRh*lTZxQ=dvwFG1BZ^B)%c=I=T#31^f zL@B{j7x8e{#hNw^*c#tORK-2pBH>59rH?r)y5VOEc*?ua#-ZN_2=)1j^kJesNJ$4_ zlN4w?)?rtTP}kw>tT-GT7E2xx^^MQ+=E^=UigAnD$TwUdcLvV&T5Uhz(+Yt0AxRl} zwghYf^i8)FT))^P&uqTbz-5rlK2lhT3;;tOYB4|{UJ?J>kUC@i5pT&)rUgHWbyy5m z4R&dtKS%A_pOsL>04JiW>Z&8KdVg>7pD4<_^PUzVv0G}gB|==$%{Db z?ygWMv?9@6!O?h3x&qEOEZosTbmPFszu&bqQR1XnEV4Ts@Rt(rxpQjRCN1%G2vii6 z2lz9*P4uqp-`%ud6{IEX_LNhv5SvPAUn{o#goWR>{=}FXt@=~-0t9~kIsH&4A4X)_ z^%wk9s*_r-dS<@V{-hh(T9#b3%s6Y|_lWgLb7y~NTrPg-UbAfOEOF^bc65XV5B=U6 zu_QEtRPsmY?(0Hec0XklS?SX6g=WFpm7g#0?5Nq_^Jk}(={yeN9y%sI2Cgk@S%0sF zg(m8QHRJz;b=rlPpL+cC3~5xmgqoA>7s1UOb%aZR=G*1YvS#+dVz`Lnm=aeE(6 z5h>0m`Zp4bOMVY#xaYtI!X5)Ma~g;Ak$h?n3>kuRo*3Wwk#ugm4vJW;%czb0MRhGm z=v*OlR?htw(;C1P8`mwUsQk<;@poL6cn1dc4?P*bcMpKOgr2`489q%$&a~b-}b%3ZKKwF zmAPG7Xk-_V7E~#K5ol zamAho=KJ$Sn=N0p-L&a%ykXd=?ldQQ{;VzH?ajsh!X+*g>04}d{P3oxtH!@xc^CCf z;JhVX`(OA2_-e^D$8w!!NdxjUzx}D6ka7)2357raaAdqps+{zzD4%l1c?5aYH@Q4F zmSAJ*$F7WE0-=7%Y$T@SH?GPD;z}n1r}@8pV?PYmB5qY$QKa!s9wCg6S7YZgb=EQ@ z)TuD+sE_FA5~*Mx+QnY|*Yi0Ob4In?BK_BP2KEUfW2aR?AN5<8Y7kM0lU9g%T}l7c zdcuC64fgIdRmRn+IN4`1^NVK+mZ{7Nz*IJxH^T2<`aW)y@Z81{p=T1Ww$@eK>;iEa&QLxqaIT_@@OyRb{zi7 zAToJhzThjuBxyF*mtoDlHRxorTVrR6=-)$ER+BPfE`4G+!(#Lnw|tRMXPRN!{WwT(8WEqq=IfrQ;Q5}fHHYBmh{b*-;8Q6~K&8Y1a z6_uYz{C3k4?h~c6)zT%Uj0+PDB+ilH`Ot2|Df5h z8&&w_@6EK@XeZoyCQ$rTtPPqnKW})n|MfGMA|F}y!Mp+i4h37OU+uCyxr^0S<}u>e zVn%syD_GY)3*QH)hwZ$7RF6@=5;6UMqH-~*)XXX_xe3UE8s~L?F$9wHB@p0LK3ZKW2l%wJ^ZDoFY%^Kcz>*|Z$ZTrPX zS52*CzJ&~))dqDR{e*oTS_@W$$)3#C?TD>Tcz4Nvua}KzuNk>qE=)KsBnSAIIT*!O zNZSWUsObN)imNT;wI^rS<7YLNw9f`qUVPyk7jZvDB@=mkI7mf1{l50X4+c)l!#~(O(>ov1Iqd3sV_t# zWQqKi^0%zkavFHO&axsZnk?L6weuOdPcU6N(<>omIA$VGLnEjOcH3fBO7PR-MqH{h z5Vcm1?{Sy3DnP7dvsUj3qF5^-)%|Wu-TKo`$UgbKpMqbI0KGEx$)FRgA@`iSjDw^j zmHW+h;813GQ=ChZ#`xt37tQ*8ND>8#@xDR{N%^v*!x93Pf^kkl!vXq~dn6w=AX^h3 zoG)lF5wkQf%K(l6Cnim~bTTpe0zClh$a-8mQ}X1{7q{P}5^P&ZYUSwU=7ebzZ?ohmsTAAvH^1N})2hfV+Tz#$*OQF9$D15Ie*wCpR|}3_Oj& z_o!lqy1#2;DkqGaX)F`DD)=YcFv@|NOCj350Ua52t_lJK=CGcf9i4I|UA`nhd}Q?l ztW7sMs7ylXv`yGaI|0mfSKkw6W%{4?c$Fe+O1f{91$Av?#^tvc_Z9+OG-R}jWtq7V8Z-JHai5$m^|N3C7yQ;9foc!Zh|**h>uYXUsiMi_{iHIga+KDAhbV z#7k3C%k=v-uotqKf$pm|uz0F=sl=(-Msga`A@;5HNxl>0nPGagd&E}mSbPRTeAEqM zgd)y9e>7(3rfV;+6Z7KrlGOj}_@S7ew&UsaA?{GO5MwP8Ywulg0g^K^^l_};1Y?+& zNI$S7*{E4oc4?U8#w3=bW|jz`Le$VOAnNW}_L6{u5@m0a$G*G7hKKdm;u)(&JfLJ) z8}NC(0ab;O&RK6FO()nE#GX<2fwzdU$clwce9 zGohHm#mau>kLn`O6So@QYLCAdK$!-E5NrluYL=#n5y}$cwFm#u@H{py>9$|9D^ z`wJ`jB+S6BA+*zN8@t$)AAw|lV)0ogA1&;;Juc)fv}T=Nul86stZNicP*rvnc@Zyd zrN?^`FEv0jj@2%sze?=XLQ2nvm^?%_GQtGWUJa&uJzzirN&m>$UFRAiK8Hbswok9I z%w)#y6s#3!b7-y#)a7gpZdSKvqiNTRu5zrkoaI~)xo-Lpq_mev6|prR+*+3U$EHi5 zC(civYeGaa9m(0V3CL(aGL`}>HX$dzQ1we+B`m`)a(JljhCR262G5c(h= z<9qBtr=yTiT2>=Voj_3hl_K?2znp)*NhJq1b_Us$6y4ie?4-`yt_PI?G>H95amM zUpBe2{9(}5VJ>v#yah!e5KD_^myfd7yv%z%u`a{EE#J1gUVg+;)VcoiB|?asChO(K z;8h9K=(NvM-LBd}n}s@pyLo(b@yKWhtk5-Er_uoHG- z4>p&~e{B1&`{EzVM`PyG8kd4j1>DZsHSJ+uu zoMw$=+})*@+1sNx-w@pfw&)ax`tyQw6@mSw09hpr>O0Fb#HC$_v|v<%A!M*N$VEj( zo)fuJI3v+k9v3qQ&+AH0(0a6?o-#?E$Yv<2H;_`?;a`Q(PWmiJkgTEQJ=UVtJcz%H z%aymk{`6{-EmrgPw6QOoDA;j)4Z>aAeDK4z4!pO9F5X;oafST|#V5c*Iq(}%7~*8B zn?rnwSje>%)7FwnI(2vI{u~dC#_tGQ?^N=@YQY)2doRnd%~j4cC4R5u=p4f_AIO{WNDpZY*H{cbI%tTWWxk(6>SKLxBFY; z&L9yq#I(ikqkry$!Ma}p(4a4pAP&pHqb}dsi4I(OJ|sJ%W9^FDlWA;q^|X(ugcYcn zF{^9cK;{&^WB~n+rL_*5O;M65MJ&T)A}G%gG^l0U&f9y7^>DqtW&&;Y{L4=0(NsDi zrbh_=n7R45P#;O=Q|Za_|PKh-hIL=YaNTA+| z=$QKR*9n*JTJiN#UMnkT93Rh8(P1gPEVpg=w3Swt3zhYP^A(4%nz0&m$K`83`0IP7 zqFpxgurlvQg4=aM>JS5e!1mg^f-vE_x4RqBmCAbc=3`R_ z7|9B~c}ss=u#*Eo9CQ0B&`2CEF>w813hrPYSR?N>$z}hI0$@+u0i?tl){t{KjIfx! zo1@vfpOgbYp@}G8Dp?J!u@@KNl(h)eGC7LBd>(RI+#RafpV;Wlm@sSdFdP zk7Jk>`HA}%UE0rV1XcQgJjMmF8QCT=<(K`a`XFJx;DQG4yx$!uuXoukXr;vt0mQ#K z#G2~xc~UhRs70AqK+|~oEEPl9P22#o1+t#I=wc`F@44QuD0`s(A&;QBW?(oLpD?$6 zTBLs2lDp}!m&QENFEcv^O;jL$LnMB*q_E#T9w*;co|1`K%Libh&?(x$n6+tnJ6H$5 z0;g5(dWkjLM#Fh3FNo7-Z~o}y>fa%n?=8mdKjaxd@hBC^OLDO@ZAdLMwga6Hr^f^W zk+OMOa>Sbj4UGMF>OF#=XQs#49=O~G$1*X za`EeFINF{ksAo2+d&@AVg(nC?A}TC@BmGs-<3<*O{P|sMRO{#LPT5a^pwiVFNZU$< zc2|a>#O>qsSKvuZET*U3Q3I&HojI%{m++42dpJpvH^BrEfy~(F0)*`MkPGhykX|$k<9pxzDloe`ijL~ z>*r#J*|=C2M>7fFqWsURYV|`b^nv8bN!kRm=#w2V#i*a_$sUo5Y!s$dSm>P5L$=i1 zR!?+2 zgnB?fAyzo_r!h~%6Qufm1cY>Mn%s!YN@EZ=#dke%?JGSD#7O|4kKZsg zqd@xcozdy`$yKiYf4ybYXv0;tTQx&T<~KnDDEs(_!A3zTF2^dpz@dh?=tnG@?ST^v z;;ADts!KOoG?qIspyO+QNx*Ce?;C@qxW?UVB!?BwZZ$XGcYcAEKZ2BP-iqI_r)`gN zaSu{_it34DLtX2-O%N^EuRTbo-A;nJjQj1W zaG0~tfYO=qMwap6!!!G|-ze0xOoTB}X@Ny!(b_;hg*6gd&RlhW7p3Tj+ro^GQ1m zR3h+ZRRfgKwJ}UVQOwrphedFLgG!$93mGugE9_9+F{(Wro{TINuW4XlU~K#~$iAUW zD4)KAQ7vl=f41pA-%W|HQDFegCm9xRR>RD55?ik*!~1s*DE6hK8vRweokVK78V4&i z0djGWb7=xy_8!0@nS+%Pbh6_ar(@}&E>+snun(w9`VSX~zXo~m}r&zBMj zc{SR+ZKWdft|{-hJr~$q3v!kpa8_5TE6vgwOR*h>K2wGkIRE#&cVd|ia0BD(LSSVK zC+B9LEuO1qa!0(^k|jZoV616!^N#4|=r`X!J~sHd-Z0JKU|Xg9ZX9P~ZVvuL2}!95 zMn2bSzA%x?CrrmVqbGpaHbk&`3(@1>IO!n~;l>pul-hgsGa%qe7 zEK5~7a)UrmY~b8B2hu=yG1E!+>+rv)kaRmNqKF@)mF0-jZ*>P6Y13~3aYic1t`sQn z?>(y!s-o~&+rAjNcA-Mif%t+^?T<_r6U~mzm?YLBP1Edk}-qm!-mKPDh8j7mPwPhc(^2M5ktJ z3fOe^#p{O~zVLcao6*k;R@<{Y8?Dw*R|_oa(7iZSy3i37#dD3xX_;4w=JbPp16?Ph zqm8Bd(-G28{Yii9CtzkA{ z#i$rMjfMo6aw}IVISPN^KAaX3*DVoWGp(RakQZ|B-Gixo9yl2nIhb31hTNep(K&Uz zhj<00HC*Dk+Sn`jWRpA5tw> z<>J!Wzw*y`6;x`ru4}e-qg#BB-P@Hq#5`+pC$!3Qxj!SMxL9a(=FzgwvapL#F7 z3v919E8vieN0rUU-+A=2aH>~Dp3C}^$um{g3Q+m<*SxzA(605y6x2m2@Kv_9r1Ls| zTp85?yWcO{mT+28%U%k8o-;^s^`1uW1k>^ke*pGoc3A`s-M-S9%!n$oJegQoc=uq# z8ndb>6D}a|i;tm6{g28?&(-yA2nyb6FA5#I3sd~0VP2(Bt;*{sRl#a>FxJuF&w&-s z&PH^7P#qM{OfsjTnU5*FV}uQB8Wp8v%2P%Nx6CkL8Nbm&7cU8x_$2z~0bTB7>b8lwUeI*9KGZ!Lm_r|W3>D!lVub+oW zV|URtHHjHf0l5Z}oHN;(vx#p{*sR;UlDeE|=e63ry)&)DwzfGQFIGEV;ndU`(3eeT z#nt;L5ce(o6h$4n*+$m-R%@87=f3onZ@CAo`rpkr!2G7AhV`=cT=>cVYBu9^Ut>_n za zL`DaUpDTvmvr@({-dYA=j~05QI(P)QIE$d2w*s%zK0_Z(HHLgvC%>lzpA=T;xDlI& zpPp=`6b515M%V_Jf37qy&p-iDkqM6{))?H^W z-z-+|;^<05j!~P0uA-|GUW+K0F-6&p#s~1?J?}-E8fwGWt8~rO{!YwVD|c(%YBdDU zsFN?0q#1#dw+52Jb!pBpP@t<|bq4QZdQ#u%4tGUmt1Dzl>d4+}DjZ6hpo)KbIcgP1 z3|DrKq0ySZogu>}xvQLsr8!AuRz#H@J~eB@{p_7EN&f2k6Gh!ACa61!HpxfVkALDe z`F4%t;}DVIb90yR_c0}QPH3lT^0mgJ4u9~zMFKzo)eqw`>r$&N+dLb=WS&MFJBiIF z$;x&|zx!?4q%Zg+hg7?ArRverBmAzb9(}j47IyyowUTKzW+9XI^Ep)+u4H?gKPAc& zpMcHK%MjHpdA)qz0lCdUF7J?{{y z{#(QFW*>=8j)Ju(R_{VbVwu@K?iWM6sww0JdlEEWI_I4#&NO!(G7xeip||FajWptF zH|qZZU_hV0hEMeBU~pelQQf2+C!_7PqcxfkpYdr$?x8D4^lu&~_eYt<0H0U*R3Y#s zyo6^3Rrck0!?+D{YS`IXnmc!1rq+w@%QM_!FY3N-21nsSz4HbY3O#QZa8mza7P{~#G(o)6Dv?@_etR$ zgs65Gw;)kr|0M9)L_{z(OWmhUUN(Y|$AbKB7%T=fZyV?qAW;duIwuBq(?N6%*^!v(ED&J=uEW(&BonpB1Ij;`+Ml)hbU!tK&(fH*M_~ zr(_jpO+`YKYH+6zK+0VycLlHniM4~>b{k~8J4;WG{xoOlg6c1Jsk~8fL8Rg6yeN7y zT)usxh39MGzp7C+jWu#SvkK)u-kE6S&eEKjCwSu!7V& zK7j!PVr7Q-Pr#5G7^X7vBN7)ETe}9d1d^V0Ni>6BMyQBr^eqVW8i6Fa0m|M5b^4N$!`gz}d!{;T=$M`dORih9tG+k%&2O}|s*m6A$&=k~ zedR2}XN>5rSd&hRt5(o-tdGqm)%PD?yzVWm=$5Rp?#$N3u#duRO9ND8D1CiZ zNvmV1Wq_`pUdbq=cF1Ri_9>!?YVUuAvUeW}_5?-9mLoug`Kti=sDs*Hzjvbzf-+PE zt^c8J?aujx#u2$ zxDcAg<)OW?yaq(*{!#{51tJ$@z@HBQ&`ahU%J^!V4MRZa&K=CEfVfo>^aP~WC70jA z#;SUHYTFluBYX)j!Nd6+fqYu7VcYb1;1u7LHGAIO;2PzB3kg8r#Cwt|>j$vHg>h$v z;X6}@p&H200n|HhtDjRb?X9P%I&LXkm5M%(3*VTy|HY0UM5M4Js@%sQMl;+!rUxeC zi<$nPBQCIJRL;j{tjndGvh~`fL%u{9zmGcUUl>xKzX*=LLNPMORrTIr)dE+^>5;*W zcbG%weSkqFNdhWCDYQ@|^%G4%CB%R&Gy#c%3E{aWpi+^jT8;IX^{8E$wP_Rp(eiYU zsU}5o{E4Gr)WLhD?0 zr!@T%e#zkZH}&+BICoA6)h4qu)a*Ew(8|Tm!^M);A?2n2;&X?IC__9^s*W@rxRmyp zK&$5!r`R*7^FYO0wBw@`s-QZWu7{0K7P3w!N&+w|EGd*A1nOn|-FwkI(-KW)XyqKk zdrrYyo14p5r8qk?T!`Q@wkk;9FmL~SWX*dwr z0AG@P4oDRxb|~!REzc4-7Js+P#8T7aR- z^-&tM1532OL$SCV#3a~nom)eSy4%?q~eRf!!8f{M4?I;zr?1u z1R9+r;pP$L=s=hj5j3@Nt?VMz!zCRU{IzVC86Hko-`ViEh>-AN9I*m=_-d6I3P38p zVaRpLcRqM=5IKQC+=Zv!DX7C1IC4q=l+d1m=hMu7v6@QMezYJM zBSe|d7qc4=+{GMJlK^#zZM33<{${a?Ckb4N>u_CDUsm)MJ?Uvbjqh8aMwZ_8YSv5a zvSRaaC)R(El78U+a_#41R-<b{I{}F~SouZ~|*|LJHPy^HfQ&$*Bpg_60@OT;M_=tHF5ArlGs668;9FH0Zxx^>F3kNk3=M=Q-W_ z(vrIXINqf-UH$lZ+Pb%tf-MbVpA$m-D-j3IChpJB`~-t7Dp5h%%U*%oYmZ{7<)_*0 zCk4*=ig~Hlw)4s=#r(25Uiz%vYwi8jp+}euj^-o;Q9zZ!ihzU$fLgz20FbgfLJ?6O z=QL7n)Y@`hd4a<=q=F%Vb1iyB;!+EOa_7gZ3QIyvI=TyGkV08#Aw^o!&t}JEUYyKl zC9%!YDJA7N-7<+LNtT(!n3PB@NR<=`Va}GJ2J8X`04R`;` zYO9cvs3kz1l7ig~dmjl=F-n%fE|$ryA~!u38lP^BhT;Tj*+K!MuA-KR+W(1E!tyNjta8~Yf zOBm4YtppubI2_Cr+!sjWZCEz;Ul~>PCP8{Mkq_4X6Q}DBA-#IrDuk}|52(Z<1 zhu46HYdCq%QdO;jMHL1F(-?20FRQi4EAF7Bd#(2VcYmr&Ijc^tR?+$FA3m5Zj*6VdBeS)&GairAWMYW3(FDe6->J%|tYND# z-~we3+~FF1rXC%IAGoRkIe*F_G$0|clBi=rybZnuY14cXra}s z>YYlyj-VhCXh)K7)Wx9sCNip4N7-{hRAEXLCQeup)7vsqr>Y1-41h}?YwmWYvXd*u z6>v#Lli1J66>L0@s&dJa%g%=J^*7EuA|N3`PtFIIjFm-PRX=c|qD?9YeVEpD;` z={-cce*n-#382z3^yK1z11Z=11^`O$yRt-7wc9z;+U@#Ppi}`!K}2d{rASOhDXC#a zVr{{kk~x+_%DhlHG_;dsInx%UCGm(g0i;wzVpUV<0vIVRR8rzXH6^kDGfG9uVxe4R z46D#kU`Q++9ccEmCdaGIb0k2zB*A5eF08rk7ll6q%^%StaEw^E(65#Lft(WDA6 z<=2Tw%pE8z8zF$^j8subsj4bvE~Sc;B2XGA%fB3Gs|9Zkj=XGzK!jkYE!k|ax6GBFCuRTGB5QZPxfw*yow zpxRQ2I?hMUU9DuNcz(>?5F0~2BRM6cL|mva10<{jNTRCH#Fi9-wP+7-4LR5{yR+Tsj${9j6L<%_K7>Sv;J5di$sM?%v)%KC!xQXe%3GV|sZqO2?z=bhQn4>vBl44yhax|8!fG2yeoUl zyNItU0JnC1u3`;^-KUS;cVw28U33tUi+*!DH&rDAQBopSf>cpD?t)JuWk5nIdhegB ztnj{k&d-DymYh#85vhw1@G=Iuh{uY3X~w#pdEQ(<)!j~`1mj|7opj>#Z#HDxZcxZ> z7{juhuT`MshE)9uW$%ozwqs1a-#x_uE(|*_8ZKnxtP=4U>uqTu zXyC<+y_valtw5o6LM)5J{E zEJ=Apq-mBhGayb-XXTDYuQTwVDR0yK;T_j526(S}R|!0pGITQD+8A$?(+d}5*?hEb@q zy6uBiSELU?-`|fcUZQCH=I;SGza6coGflU)wxz7EaHF?<2HYpq)#nf*1^KEl$n6b~ zfM5gwsXIY&c(8Z8cX0WYSF&j)h1oy)4w3$wE28d6y+?RFGNbK zP*!VoGEFCw?dj%Zyt$R6l8rBABP1JF(vdZGtcjR(LTq8S2*)Z@DrbZV>kMS1l8;;F zsz&({B!IphL_?k-OZ!<-GiP}5!4{eb0Vugk(l5f%?);8S zzqC;K)-u|jb*t9kJjmISn0nW+3a2^tO`_i`ns9 zVHKRDNVAPJBi6()l7v%}8AC*dNL7u%ibYY*7LuPF&kqhC%@2#&;-nP$((A9^xcR!N znuaLC{QyzSlD{rPt&)jje%fC9elU&8jNNcVMAgRw)P^`DtN@h&P&ixbJyj)9u#(eG z+8YFy+o$eNMJ<&KskK3T0$^Y?(8iV{Q%rDbG;{XApfL%fD_Ij|t72;ET9scHB1Edz zI;mm{Dy*skP%a8uj85;?>e>XV=PeHDB@97Jr?|=?r?8}9^?@WE37fLS7={k#@BjG^ zX2s!e{I7oh+O4ZbCWI6urBy*8pu(Okj&)%VXNQZ$Y=3@K&WoeHdnb#N2eufA=9BT| z?aR~5Oc&*G%9H8NXlv_cn#gqfYMK#cB@5@UUR?D_uPtUHKLW7eB9~ z&wG)!M&OEcDTO(O@%rLIW&auY**mf5snp-a3$iD?42YD)tSo@hC{<3t2@xq&Sd@ea zB*7P#2vYOz#HzwX((aV=t`!i~KE&RVP-(FG&cDXpJ8>XT?cLNjI+v%3h|9%E1ZaZ`TGp{u&?%JZ7y-1REj7=e{#O-ZA&5~U zARQ&+EV;b1DN3a+AXv;7nx8E4;)oKXwt%uq3Gj51X6a^{nlv3{NjlXrGt zd~|sK(c{BWnklRZE35dtAt127cECIO_-+cA0A;7VByYUXY6y`89>(bGpG0I}l!|g* zDtrb~lVk*tF+_f!dL&|1Vi8fbCLvh${@2b$#|W&q7^qLh3Id6c^vHWZ0Hs8Ca-hqVsZ=Nbz7co2Wh}*_s#kjt}Wz|Kr&#$>kyG29HjrlUJ|5 zdGpm9wa4z>t@2ZQ^(4A|eyG$-U2*8Cb+~Js?3K+f z(Co7+grlO$59T~m-CZ0$vp8E7Q{Ts_iGSjJSi>!z(8StemM;zt4h*N;S1)H-#?}}q zv`p9tS(3zBs5>uVSk*cmTh+P*eVfyG1@izYYgNYeOf2#> zK6m$1gRWiW=@=5@rE+W^{D+oWp3IIG2aB!gWOM5>s3gt`DZq)s3at9@n?z9%QcVij z6lxi!-t5b5WlNx&z9zbz+7;2t3l0sovQ9)3)u3Qvt3mCpBqRwz5b4()ORYi*o6qL6 z+3e)__+al)wSX8hG8#?DV4RFMw#Q?$Ihqh*6O^;QuBk!#Ib=*&kaK)zqh_HJEHfmq zP*pG+avDp9S`||6xurP1hgjTA!woxtEedOGA!-OEbK|MvnPZKzJ@cU1%eWKve?zR-C$tdAzA&(wEINCeh+MMoOzCIq0 zi?OifM7bne20avoGlGHw+d1F6kXr)8#o&@Cdy6r-EFTn_M=-JjAQgpEp#|-D(9byz zn_d25ce4lqZ7YY8UqWc&1@oM4v!A!W-;B^FlkC)NC|LFPnt7dHB>!ptx+4P zs+(3_?p4iV7$^{&AKC}#vRQQ!rsJAi zj82Fd5Rr18mn@62m|ID?9D`uU>#eCY)-bFJk&9z@nf5KKz@5o1CME7Lhh$t;_tskR zYKtuyPVL3#-w9R%c4i2L`iLK^?$=sEP;uM@F$@w0l4L8{*x14~DPGgX?ATgcgf^`YX$y1U}dRSu&U{WSUp<*lwSXkIRFY|n6tz`#_EE$c)DKn9~Dm9pi+}(<* zN=l+i>^C$KDG?c{Rv8pR`ZZK^v5s~CQ#&Mpm7JqK0|fYc5?I+GM@{X0G(uP`Vo~XF z57XeN8FI)5c@F@PKsmm(+0mvzn|nmhUL$o^g%%gCep-H7uWnCxQm~?n>Qe-1$N=m0 zq+SsU`9-7It9?$YH6kXXJWjw`%oa!cCs(#FC+S9>J*c)2s}X<{M27VWEePoY@K~`> zG0Y`$V-_Qnw~~}G-xBkXIU|$_rW;6kb3?Nmswa!<2ebUWg70j!^P*18M4eFr>ypT_=`AI{!TaBZ)xxeBxmr~vYgpzwWjm7ut2n#PdcG4<8 zYv>Vdn!~AGM0$tmbT*=ZhJ-pUc#U9pNmSE&fKi_aQi&4e*8xU)r_yvop~jFBG)+vI zZCoazd!OI^>CfNae)RFx8*imou28YHVXkJ$=Sh(oospf2oVeIKa_62Uf~bP6zX6n7 zt~*6gRf0e&>aO@v7kLcpb~RV*0t6_z{bNtDS5NokB` zpU!8a`lGkYxi8=IS)3Mf%nuqq|Ws&4bT8@N2nh$<7i0wO?yw#E^1>lg-6H6}^y^=u4`9yl)p zvPLvt6gn^V^99fHM-Ltz&h7s^J=nQ+<@)xOtK$sd32^}(GYFa}iYj+O;_~@;mqn;& zfs>%>P{0roN~-iD!EwhcQJ9Qs;i3x&DW!lNF^j<{%S1%1wIY%)%Dgz4pOjo!))5;? z(=<&}!y`kgP+|)t#L5aI#>Z@^MR}vj7e(nkz{OL$JJOP%{%|Bf$r=D&dUQQjLt>N+ zs3b}>zYY?l4?*k|KLmFlD?ATV$3nOmU!T|j*6Ck7rns2s7Cj+J!XGt{-r?ex#$zpI z?qmJRsr*^5VIlrCR@6NukD%v(lQ;wIZd*%+P1rSC>5{Vuh!aLy%=YJtgB!PA86~Nd zxw2t0qDszxn>ESx@a|*MWb;O8M~PEc*hRUR%|~X-Ot>VnwonK`REWtk5`q;4C(P4y zGQ0HP(VkkE46vfoX2+i^2Hn9k?9y88R(s@B=B85lT==*K@wV?wl?JT{a_exHD&3ef z+Pb4a>ABdjh!i5-AuJT+$!0 zVnLLow0Y?&NjjJPho3%}AIjyOQjYE%;PY3$K1sJOYsr>p0u?5RSAeS#6@noUm5 zno;raKuL@-@Y(JGRSLqLQ^~4oC9=8Q_2f0g z;d}MEP)XzojT%vLjwcMY;m%qo4JaVw()_C>B4SopA50`d04o!7O6iEw?TM+r@j7x+Vp#Q8Y=}|kkK@uLISsc$1Lah5 z))WrXHo@J@RZY16NmYrgDqsm@I88Yv(v1}0sgzX8tSoU-950UY!pw zG)=8aLhKwgyeE`1C=mfh2@tg+4J#8l3x~pRWq4I$OI~wQczk-w~ zU^d>6zSKlz)#;?12e ze%hmH@1{w~&smt(oP<3Z_F%jsw%QLEB{61-3O^v8mVeC zn|^|5|5iz2q})}2()+4f_15k8-u?&@R*)$m1QKZ(xO;kamVDaf@g9<$!L^tAvRweYn~(@!4U%@?1K)AH_*e|C3v>5X6gtqHxg zu{CDC2RmZS$)-%X(x^l(GJ^TslsgHaIwH&jDX28a6os&1QWjFRA-0162on^El8_3R zYipzr*&~1{CAK6R&=b*=xF8`b#74yuNDK@sa+FG$n0#J5eDvtSc*AUGuQLi^vLs4E zPO(=9x}}Zvt-WC5WldDnmucNAQmsz;N(Z9=?Y|d-h^l<#sPwF0cQZhRKV8IV<4KHQ zl_(Wy_gew4r0TMx*2o#3XiLxWcH2+-KAa0W?>OXaaetEYVd<+UxV}*;y*rvA~do(UFiJS#!J<-x4E0HHSf{kDt`mRL9dt zkv~L4!a|nBacTrH>iE!*H_SN`D=di;;3M5h0FmsDsuC&4KotrU*8qlDFqcGWHlnNJ zS1xbbS#fi5czj$e9-iDQ_fguPkCW~3W-=aaGLV=onA}%eZNJ_CR z=12;oSY&3)q@NK|g(V$xu}v~U`Z;71m=VUxdCLYy>Maf)gVn`M7Yb&S&`hXgus9_V zB=I=y=uybi9!n0Zs1Uqgls3p}*G*{X^(8JD9j)-GeOV7;U0qKHE>wM@am>vVhFEvx z-GYIz^SzcUmbjdYEh90^TC;#dnM!?Y>sC>;mT3Mpx z2KDXh7XeGE>Z*8AF=0WQxGbfh_pcWqXdlUCxVKKt4-zHzObVF5mV{DLO#tB{rG*$< z*wmJ!c_BqPFXV_y92`C(oor4tmoM+6+Z*}8Q8|askB#!B8~Ww# zTR2(#;s+l-%I_PS9eq}QQvUGDRi01AH{TkG5h7DS4I#6-D2s71HW`%=fmoEvBxE6y zkbogtk|GfhP(W?sy`rrfhlMf;3k$1?B;+p@CN^TnMM_#iEDYxcD&+VMr^G7>qMDT^ zO@X=W&5s|?_BJl>Om~be#1avSv7}y4*{`*&74YE})u0FMDOP`R?3dTVHr8=DdNgFh z)6S7zy?$lpie*dkxkyyvR>)o3;4Lib!^4|+_=PqFXz&0T8f4JxN3RgMdoti=lKqvy zVU2q{f5MtN+mvw_3Ik>yC21*Ilt;%$d0w0pwp7mM^ZmnvnUp!{txK1cR0URrRK??} zI<>9_M|aQfJFEjsjd;#Tr9>>XR=3Z<)rO8sPk5QT1-y$9NQuN5MtqbJ#3+|wGU`45 zg5x{9{S^R&EWBPPaDzVh-57#a;itZ7OCTk2`EC+3FovGf=RK6KymRhEaorAY>9rV6utSm|~?E+oCA)JYT4l`7AF^RA@9#vvkUg z#1sr6*3@LANRlk%Ommtb%bIYh%1R8i4A#Z0I6QiMQXY?{Y|;rqvJ?qTnG&@E77*D; z52YmH`X2QD6<*ZdY-F5GL6ita!4RfD{mx(ZqfH}Q+c0)TIkOPa#6c5h2V&B^l9BWY zLAk4{P6zpXCZee){N4Y< z=;q|;BtPDt!OS)%SJRBxq*-ca2lEf#|M@##{mNu(Dk3qrx)Kj})=6l+(=hlBNTDwx z49%qWF)>eK)Loj;hIj?5Db^n9WZX^EYm7zqk7bhew}&_;-K#(;pv> zzjPbfKQWh2uDtr?jJ9vQb>-64uU^_Vc``CoBy+QO^1U`_ihaqVsv^Sb=?q&4>!?Aq^%K74;IGP=mi&9zBWMs0km}kc)4;OQN{nqZzr7PYK z24GPrR|ZfEg%x$7q3-tRa5p@u6@)vvHMnaw{Wg0zr7lJ_F|D>^1ZLg+>hAkV45DEZ%_8~7~tCEkPs5T?=>#AyTzZ7d} zVM`Uw%L0PAicz(&tSAhR5<@0Ql$AMAwMmj0Lt+`WND?;0rC5P2p^M^VcA}b{j7Qfe zV&@1tsU7g_1PUaBs1)Q52#Hs7!|V?Ak1_6uSDwmfrDH ze%kG&*kT^Ob~H+VA3yvs-#_?t^YY{CufDl+?b_~ia(VmeV;&#x z?=9}!IXRl$mHo@(*KgiR-@LXr86 z*%5wpa4;>8=IQOvuU)+{`_ab>dzgIr`t-*2bTZ;>Gb{4_2X{aE-Vb*E;fL@3`X6k+ z`L%JPLR6HN^1aNQq`H%+-2Uj}yT$C4H@-HVUbl&GQret_j%B|rj*qvly=AtjSRArd zO0$A!!8$gO`N5rE{4~$Yak?|wumWhJ0;5LRVpJxod2u{DINZxoUfH@l-AwcOOrRo^ zs8Pd!B09@j7TuPO_f&Iq1s(3mh|qxJ;*6nk6ZAC^2eyVrZdu zvMAHrpFNhvLQBgD0it3Qs?PD-M-P*anGZ&$sznKA2_iQM-k{`B$)|_!a4a3}-`ffz z>o+JQVAkrE4^c8;ao6f8LyUlk6$VxUdv<0z(9S5%xZ9p(X zzy?HIS~qTn%gGN5G6X4ANfeY33BZ<=jVZaHgh0e(L@j|`_{%6utu3B{WnXmM;$<_AX)PiFId!xLi^NR-ONjK^a#JYllq>2{i#G)a%2Fglc<>XXScq#>1&Xk{lpN=cMc#gl7 z-L^`4vTvo=!?CNqs(X zBGJjiVu6SE@7#WP_mx{WUfX>)0T%@kE05CA=zeJr^TTO2`jTWHJUF=e<9i=`{jFD$ z$<}C;5al8&rIJx)Ep4v$*d!L5X_X2q%AmK>pjLpEjvo`{F>q+H&c` zmf~{>oM%EBm!b4R?FkdGI;c6Zg5sc2&xLdU&FuS?)~CAP(b+2MU#axTC{zKu?8@B0pbEJ2}ah?I~($mN+67*!R(s+1xiLXzhY8Rf+y*UitDbuKwrm{Nwe%y7PZMwEyNezVX}to8Q{cPyYGs!%yD7{n1~4aOLpQ zzkhf8Uzc}&aQDZOe3F-wFXQGYp;tHG+ufGgkN(5o{^*B4{mSqD?w4NOx%&3kl8xfQ zr~Bqe-+2AK(P(q$mF++K?)!g#|0i#K^4t4=arN>yc1BlW7G*x&|NPG0r+03>YO?%>WczG)XtZFWQjCy57ss>1{l|Aw zx^(@@<&oLQp)&bgc}$eUX3kK6&cV?=O5ByLt%pN@*0|7AlTlOBa7$YqC^2SM%B=dK z%hD4vfmC`!?-1qQa(YIGEpA-iFh+p7Vg&EfKtxJLwIuZs;R29afZeBd_t7)8l~qf# z24Nw8fnYb?r$lyub$Nw2vI4_3w7LI^rBPOLiF=@=NQlckWMg4W ziAZb;E(mP-GjigTg-f7hU@mQL&1OlS&#&%WPp(j*H_A8W^O@xH12Plh!sc8QNBIJU z-7oX(@Zh%Uv?!0oCdTAhwv(ii=s260bhMF;(>R*r2>*?hE#nF^TvrCt5q+>4Tz$o3id@q}BFF#l*vRkZ$tgg@H5Vcs?t1O&LR0 zj?*d{%uX;^xX~~-X0=W-;GFp^8yp_(&ldKT%dfC8sv;)D8VNF?@^SI;Uw<&&*?9X` z-;*uMr6>zAiJ{MCcR&1@F*Hu@e)ZC|M|%(FvobwMl7$_Om{V0vgamAvOiHFG3S&}3 ziImn63rAR=)QU8@|H!27(vx^yGOuuoJfA{BwIzGC{&C5c*trBV&pFc5-)&}k@z27w zvMV9^TH9ewsPf;t|3F>BA!jioHLMU-cYaoZdH^%j__27WOrntIM#6FJrbYpBcf`7& z3?fkii4n?+eDBWVqr=CbG8=8AR3>?Hb3D2J#;+G-WpQK=A3ps2$|v_f-QPca_~`iI zgO7gtKE3hQdtdz~zp?$hZ@vE8Upw6W*~#R`Kjxp`{cL*l-EV*M+GPCA@z&&(@i(_# z%g5JV`QAS+zW?JtetiGIzxu;p{qmcW_qM;jbz^>{TYvdC|Kmq{|L(v0wJ*K%mAB`X zKK|_B&JR9+^x#LAU)%ZpU;EaV|HaOq{^kGp{r}tl>u1~cjo*LE;f8#4}{{5f++2fD@_UauTf8~{Ir<@%>I{x&NkAM7OahKoNzIt`@t&jio z)BIMk@oWE5cSb7r_wRpq{xHp6zhO4`usl&t#~3-yn?N5v-rHLoO{Symjh$qiLTw_3 zlu-hdC;)IvYum*=#|>9ZbM<_!V`a!-Ud3EC#M0ivF0+aY03{-YFag9$s!*nipCc9( z0t>;pwovJ=UGZvQufg@677!vLALK+l%VNPB6H9<)Wl=zd6rOQ(2T-7U%X;EC zw4h4oV8CBWFk5UuCmz7Es2CzRR3TNeM7BsZ%a}0NqFBuGqL>}-n2o$#9DI70Qd4YM z<}FE#7WwSRl9_>33bMt5aG0j4D%oNoA_S8pNs;G8@%cEJrWtK-U7Bu<*o-qE*)S)B z!+f?lna}6td~tx{_IOkpv#BVOWHO#4+n29xY;LFNW;QA~9kCIVYDkp~tfS5;8JcWu zCS_XW76xfDNivg7M}nzp#-@a3iiIK*Jy98{J(e;n%VS%bJU zxnsNy5-034m^m@v%pl>>1}o^2Q7MHCbS|0|I9wFzqP#nwr3d?;mP$oY&L7@2#>|e6 zM?5Z%H@BwO5?V|)Bpov|6A>|pNo}HJp`>C}m>4A#hD167fU3`s5M+QsRY8}@aMw4S z%~aa`^Hf{y8KI>MAJUFrw2tJAjxIeFt<%GAWto5U(U0;)`QER;Gu^&9rxHd8X<-fzPagc)-~Y#x{NSzYH!g8@oE{!!v)69O z^wKf$E?6PT3B$#PvM%OFqck&#+7Ohy-sGHbb)4Ra#N{#+b;*NQ(;=-QBs?ovOJ)u} z0yVqOmG-Hnwmc{FTAv1XpK}0OwdNs95>Js&-FeyP&zz7ut zz=9a+6oX7EKz_||s^)X3L2*@;IJ*rYO(Y=$Q82UYDn^?9g3IZmcxY$Vq?k-iA?4Bi z#e+{j&iCf!V!tepM{2jPrq{RM-5M7gw?^5GEBE%N3p&2GeIwbInf&DE-{buH-Phl^ z{?)DWot^8y@!s_xUAy}?KU_Tchdck|YuDb;_bzSx=(=3F^u~j?w_o`$8=HUn^MCjM z{GR>Sy!orIJbDwaU;DMAuU&fOfBN&!|Kz9t&g}m0zV_>H{MK8Sj_w@(^s`TY@cAGA zhyVJcxBuDZZ{Pa8AARSKzxOvsKm5*r{Kf4@zw_?zX#Q6pA3xZhe6(}vjc>lXHTw0f zKmDW6|KjieFJC_VH@CjDv48vV?D(UDYe(Y}aW?RzYc6}J@8&j!mlY*`I9hai@#uZICr_FlvU zcJ3La&(cPASA{gW%a%c+C5x5}0|J{7o04eCbiyQg%IcYR_1s!#S+%+oqd6`Y zy-Ny}L{&jXAj-y?IomOf?@D22i^u!>j~7Qu23nBXyLrjkG`Vs0)s3AELy4$b+s+r53W#9AGWS)7kOi@$s=eu@4?Um`*pRo0HUx$J2CYXXnbbtEyDw zC$ohf9y};Y6ve^(B-=lE_;8;;d-!-`bHd3uH9DSdPbSPdN;6GI8`&7l6T_04@hIcO zWR%WqZjlv<9Vx6xYHbOKa>1gc%w`i*DorIJB-?7C(}@U(uGn1_Ep2J2lu}OS^OHr9 z7uEteUKIK9emT=}z6~1D^yky`rWwx?-W-p3lx?KrG|k3_!K5Z3s2Wb;)jA~+nKV^Z zMGohJ<@~NJ3*w;%z$O2De)NFTVq4z7^8$P-?D`%r`Nqts`;pjxhr7t4Pk0gN&gYLAP(xj;5I zPmGpeA|()m(!$*P?DoeGe(~1V-nsVXEh-mj!2)EIpx7v0Dc<-eU%UI!ok!)ty}kVz zA11F&uYBpt>G)DUFPQ)^gTPMYF!RM?yt&OLwYGsm=bexhpqejl`EBO{Sq579a6C^? zEoiT@zf}^EX95D>4*;wZ_$P`A4^_2B$5zNzp{nKBm>3sWtSoSu>>Vp_NXccX zcV*z*GK7TO{k%@K%y5^23c=;KA|eYFTY|-aRm6GPk|9zl^3s;`#eT7v9gVUb${wOP zWStqt=4A8c^=szljpA*dm-|OYM-LC~Jbe6Me{b*h-Cun8;oqcowv%4IerfVmx^}PB zS|L1SLbMTF~ zuWfB8=et*T?SJz-zkl;a`p4h>(?9>C!}Ryw`RZGAnZNd}U(?rbUHb74|Ksgn{5;9; z{_0!5{>qK(uiX67Tkl-^>mMb@fBpW*-2B~J-`*wt^B@1{=Rf?D^5N0DUw-)D7sqM( zrEmXMdTILZdtd$f;`p7v{NBI&#ozq(=-~Qhw)@u28`siXckfLf|HJow^wB3tdVo*I zAKm?{OaFR2&K}|Zrw4b-`*#noe`Rm-cs4(ojW?&1CVR)nckk_OZ;W&^-Pegq| zOegQH>!hnk$iUs~4Jg0`8B-=@Dwc|)#X<4t;iH41EVC`XeDmgXGl7gx9zVSQ@X^)R zE?*w+0Fyv$zi7fxW#I%PZ~|k=j)AEpsir$*4HPDuTuF0>wh+i`*XNkB<+ibT=-f$wrzA z(b!$S%VftJ)9HAFIbmZqcDBdkF%fZ^fP|DSiIl<+sEArsf?Ad6+AJ8Rgww>NlyN%Q z7^?*-Gn6?KE+t2qZxpj}o|NcO}CTno$G0uCfRhdxdpDW0IidkSJh{brPOB- zC$&n(ebyS0Do1;*BUv>MG1(gcy7|k`kdHjnxK!)k{tW$WJ@00nui71|p$+|c#MjRs zkN#Go{#0r$Rdu=l>rBRxLMTcpkOGi${)4~x$2*_=J|cU%mDFxMT~L?pBP; zbCPDJoX>vp!ynwZ@#eKxF69N-0LmVJ;Ir4e+3>WyoM)fuKuB2!?-S^o ze=oU_ptwn*Nbgly0_39Z0XS=Z?vA0UT~GYn-38Hd;fv3?C1)NGPL~ixhrl{}?k6}9 zu5@0X4gZ(y$5rd8=fT$+F$9%Qh9v;Pnkie>1SqYhpgHU%B#%&DBYuc-1AIif0XnvJ zOOr#$Ch3Hd5yqdFB^H>mWF{#RzLccLuqh-j74!1s?&qI<{_yjIPml8Bg`Pa3`TaD* z^~+!1{n9tD-?Edfcc!DGlqbd}sby>Di-Y6)kG}uu`#=8h7oRRpK7C{3t?e(RpWaDl z<~!eh^?&<~_pV;vN{ju^?mRrujIM1?C!@6^cL<@dk#&7IAg2_KW#hbPAeho9MUJl!d-OyA0qyeKD+ z?|%R9erP|u_y0+=f17{#XCMCfjUONW`)|GRf8vw(4?mgy{_nj0hrhqGG5Xp}Kl=XP zfAA-N{$ZhazVXdBzV`L6UYl&p_YXh#?9)#^{j1~0?|gpnS0}f>@!B_Mzxt(redqBH z{`Qm4Hs5{qU;LB*qkL8zKm4!n{^7r(t+yY|<-voW?POoQv{Q&&g5J-tp(uzI6XxyPE)* ztO&bM4+4a&QO*mUkU3_$%;^E>dXbQ|mnHqMoTt`2K6+H_J^uLcKK4$2c>iZ|aQ{a~ z_r^u}8SJ*;fRn2mugq?|_1kZK_2wI|{Py*m6SKI=xMT8LhVPZw7IQ!tDbEvOr&v^` zww0p?F6uszmFS!I&H|{s0YDo!lQj5^ww|%sZXLpVT7P0XiOR2L;FMAEw@I{KaU`OO zxTvCos(Of(m5W9G^N)UZaJcvSt8eUFy^@(siA2O3V7WtMmD_wp=t*^ou>$EmtrBiJ z+gBNGbE@j8UIePWplnr)JNFHdg|!%ik4(93=}~boyMO2YZ1FJT-77aXw?{XUP3GcN zO3E31_TbL#lUZ@=^_$alYk?9}5>lasA|o!K8KHzGY8hmR=Hy(Yln8D^2`iH%1SpjP zwXkI&`N?)UFRc08=EdxIPAF3u@z@$nOF1Ckm>2g>4yG`-%fl%%7?a0z|3F1f-G*Qlh5qK_k z!s!XwO{HVTn6xm;N@AeUjHD1XmWr}~E%UNKAx8)M^OK|7BwMq^=jCF$D37jPy*1u2 z{T6`bKopFbs*X^)Ymil$Q0J!g`=okp9eu-jh5S{*Ub~GpBjthceSJ+yt&tswqi9=8 zo=_gf`bg_3Kq1~Ec*lw{DT-OScQnz_)Qpm%gk4ZU<-C0G`J;n_2iL!{d*iJef>cP9 zOR_{r)Mz0@37L&cW=E!EHu+MjSBdt;8le&jQc_j{ge(zG3Ml|HGM1q1vYo5DvCL7c zpssTx=}h42?oY!$;y&PXEr%0VakNqrROPbb@;!O8d&{vWejXL*jgKTmT1;~JC7i(1 zamjNUR#m5EWKji^kW+=b!tE%`ICL-WYW$`3ed#zaY#J$8PZ04C&OQ#OCq@D*fErj5 zqYz`6NKjf*B3Na^S)wMhHepVwNZ2H5)fRSfa4@&`=121fk8kIP0=vI4y*inawPZ?~ zUY%UKdh6=dci-Fj^2yc1&mKSe{Ql4G-@pIx(ea%RzB~Sh2RoMzu3dlQ%FWAHUVHW0 zrPn8$$(S&`GXDCtuYUVWZ$3OY`1r%`y#M`==xFaZzWmQ_Zoqv1vjSoH;k7l1Q_Kn@RWiqpvkCL%{>($>JU&3Gh!_R*D z!RPki@$de|?_In6DvkHHlkM$mT$HIT4;R*m$(e6$zx&#^UjNnU`+Gn9+hl8>{)b=r zZ~yG4pB;X3?_d3!FaPadKltna^jFyt{r>M|S9f0fwRg8HUj2(d{|`U^*>`SSGOvH> zpI&`C-Tl(n_YQ8|yZyDh2bbReJDcx)K$q^lef`FxefsI&?|s7m>6Pr3-htl7Mlw6P zCzH=-o4dtyx*#b_q?(kL$&w|cAWMe4GedMRIoXXoev-mnsO&PIumZ#kHENjLtzphq zK;a*Vm_aPUAgD4aQAI^7N3{Sx6--bPyUVDA1!RN;Le@Zxk}4Y#AhF68FbYo6k*2hn zV}9~zetht8@#y~E?6~+$bn&CZJLQS}bpPkaj~>5Y9FBASRF1A%exPzG$@W=xEI&ed zSd2gY6nE}y@Q+^kW#hyoD05^sE{|`IA0#`Rtm=p34s$c z+1xmqo$MVQPPV4WB%`43r;vBkCBt`f(P$<5v%0?m?%W}PH|3BW{iANq?9C&9_)R5`0=rQxHY-;%DZoG&?`xnQ*IPeAZJBc%Iu^VZ;sP+B(zAx zxM}c-=gC{xoH0xV#No&g_$zriMB=HEAr>%kHqNHJqnn`2LbbGNQ9v_IW>QXtx5{$L zg%+FBxjoLXOLj*2jzL=FM)Jhw?v^6m+Q?|UT`q)YrD2;UTAWC+e?P~`adKkJgXy%K zZoXpDVw_%^T!Jx)Nu4Jr5tvOvoPiiZmPZP>O9B$4Wm%S`h|Xt)94(IJI8(hVWG9zr zQy@)8hD?GDo)|z>)mnS?#x6u9zeb{kxg>e9uw}8B&1d<1cKmq%k=;{fF3U2{^E6F2 zM$>FG%Cam;IZM*7;P#J41NFXScNg+y9LRC${Iay*70TUBvV>9Ly z$r$OzwTZ<}soRH-b`BmN+eg?dju*G*gZBV<9E#ZJV9Jm5g6`B5A)kLYXmC64cFj_Q zjb0R-*;a6VSXnbYQJ$-kpkQkh1t@halVh|?;ndx93M)*IQpvDdEt!&1%ZyXIcPIP& z_T4wWG8%79oCM;t9WS5PAh8VFZN!;Vm&`l|?oQByiVYlII;(Y;0JPkSmJDIF`p*ot zLTmU}#P*+-8mv`GG(Q!hQuV8-y0Go=#Hu=LfWtTYB)s^Z0JMfrPE|@qQ4o<*X(=fo z(8Qz*#l&J>Sa*P`Wmegr=d=7!p>u0D(y6t2DM=6Q{)RodKfk{?IzEtmw)gPh=+UFa zVlm2!L`K%0*tt=jPiVSxb>qu7Z@l)_m8)03DVO#i&9CgwfBN`Amhb=M@ojnh@CV07 zneLRAF1>R7^=q$QdwsGooeHkv%C(LAqjLMO{O;lR4=-;P*^Mio7U`#-e>h2tw_km8 zXOdYi7X^vVS%pil9hT)St40h7KP^JI8|6V$@$*hUwrWMgN-eH@11XK?rbMo z!sdh#C|4ROEm$T7^AuA?zW+!+|Ha>FxuaJ;{my^g!IwYyC;#k!`1^nO?w|e9XYal@ z`orJ<-D_9hJ>lDb|Cj&pr+@OnbaVF4f9GF({hhb3j5x7J`FtY$(aCJ@2m|o45 zb6Z-BlS#RA<16p|)|cM>&Hv@iSAJ!@y*=WG)03Ntxv#QqNBdR@tUhT{9Nut_L-}my zS4*KVR9jn9c0tgllLN!=HeLB9G~RXGdBb*8-8?q3dGYYkZK!T;?Tp7`V~kWDV3gMc zw#t0a3Ok89r`i zuV22tvyoh}$x&&y^3lvD1&|POW3&0Cq-Gz z2qQ8xTa3sYtKBGNuUL#kN?VA)CMl0Lwl^=0$K#!ijU?fz$s|oCNyKUiSn)ynP)$dP(bO1Xlfqa>8I%Z80!$E)fm%_L(pqb6 zna>x+VzID`tCm>I$FfkB9DtPqqRc$r98X7Eqv?jpOqz~THcH00w5i4=abZ#mYlPid z?CxK1lWz%WLJDgLg5oA~fplMB3$aq#1d$U+2mRZjB&{vsb8iu;vjc!p>AYjz`LIy? z96^&W_s>?KYb_el&?xl29r;C6zJq7&^fHJ=i?&HR(u>zDtSUs(QE}zk^|nT)?Qy@bj1`qp+P847!-loDH}jNKKbKJ}cZKngG=mPCjI1(8vLsFX%jSrfp3 z7JC2g9VVDjCKk*@Fe?4?)jB6Uq7I@E1AU~t^Tas}P>7DGxCUifB^scrP6JQBqAqR4 z`p*nYaH*^E?-t;&smY}YcJsyxAtiy5aFsb%$!Tg0>+ey8E2!!Sd=I8(Zo_REsCR~yPq9?a(M6lr};eF zxjwyq^Q&7o$H~S>bdz7Zl#budCYhRqun-a!OS#S$2eXsn_~>XcJDx2bKCr2z4@+BY zpxBs9$2+gxc=c7L>$XTw4&L3nb8!6dzMK^EPY>?={O{78%_GU}BAH|#ymtMaFTZ9s zzO=JADz((tH|W-zUp_kiY_ymG2BsJruo&yC@OD%t$85?JBN0o-C9x$4ml^S5gRZ@K z%e->UC0XohdGOB08~;7Zzx~I@y8rO<#>jm1P>Kv5CTyMzEfg`vWgX`T$F^8O0dzi;K|iw=Lq>_nk?og z6Un#B#bR2fS+PjUluqs!xAiAK`Nz-y_`9#Y`>%iHpZ|-u-b)I@Hi7D4NkUA9*g3PX zMyZpY`k~x3NH#eBRn7 z=z~%KRd12=39SGHSp@-P$-1EULX|+8BqN`%6bVpjMq9igTWUp0Efs9bJTJ7gN489b zj`KX7&mZQwK_O&R^2C~=EMUay;q2h>(TO?OH_DtLwOKMrQv)*0oMz)}l%yLQNs=UH zY_c>lU|2GV5)p%tjZBsy(cD;GlpplcA;iOhDVCR2&bcj%!rLJWeJNz z1_qF&jC6$YWSdCU7S{pRgs6n*yqMXe#opom(JY@G-9ItdcYzAoXfz#fY;JBEW3p+M zWtkae2_VT*Nm5o$n3&R~7Jvb0@7Ul!;Cv<9%6@bd z3{S@b5JN`|T-j>l=dpwn9Q=YL&?(vV?P=4g?W$&W-1Jm7326ZDn@wY>9tuf#<)~-L+D^E2Kf+$yP1M z-QeiM2%Yal0+b{a;>Z-7D6Fc2nIg`<+Dg?e@X|!W9!*={xqDGl*WDCV+=7b=48@G2x z-%2*_7ss2~mC4r5=5%Kx$#&B5WHdIJZl&qMV>{)lr1p4zvQgUo5z%C9k&kjYN|LS2 z6eu!d_qmv4>Bnh)Yccz~pMAK0clP&3i#H#i{PpbNtz`Vs>!V-&`n%H`mv>Bd44Eg#-Gkkc0aoPy@&T-nQVM?>&ov;u05I^-Z)x(I6u6! zaqaN+SHHS_Wo#%bY)++Q#7VM%7?n+yy}f;Ty#L{>yp?R+H}+<3ZvXVd%}?(A(VfrU z{b=($zxU7o&)@pPKiE_=Khj0AXLvM8#)0NoW#jK^2$;v>&L)rK&gjP>6*9TlNC{iU zIr@_VETv6M6+`8219eF`owC5$7SzQZLNic_A_3)uk!68uBBD$SARmdOS8{VWKib|q z`KvqMKl=GU{(tZMWB%aiZ}s6-DsQL7Yn0v3>?O!!61yl;&F=8*6yQniy#~!HJlVCPEy| z55;K67{(}ql0q7~AqhwTVH#+d1{!En)m>FxF0<0x=gqap?Y-BUALrcr?t3pYyBa2V zG9n}MnsblYXYaMX?_1we_tIXww0F=hq1g?Cr6_TLO^U=|tdl5eg=88gl(@NI60gi$ z7+d9Mky7GVDTf>(<{$xJ&JG;u|7Eo1Q6Mw{Yu4o?7-S&Xv}CZ%i1*K^R3zvlYexnpi7`+#=f&HWJ=p z(STfYOG+a+5hMZxYGFt3UaDLY4&03amnp;$g`7ZvW=x z>%IMA@u|QENElLOWHvQU@SuF+VDde@!%6aoM_bs%_zXNk;H)w`&2*0`dc0t zs<`Z0YdN3zgyG)V9FGtT>6=YmZ7akM1l%F-wltvW%_YNs)c)M%jY zZrptP!{0AAZCEk_-j=6@i8BoN5 zco17R9OlZP$chAlES!S^$HMG@kxA%V831LAl3hv~m5GHAu|QNJUIZ03$XLe- zD4`@Yb79jidRHdHi}9jl`7*n<~o!#E$8~o`n=ne%yS*moYr3Fk5PzeDd0M~9m zC;~)G_1L3&P*Ej5U<4rI8eRqh5JExlBEo{;5y7(;k*fD6RC??3D1uT&LoNn3A7Kv> zqj}eAK0fZ#r17XeFh*$>>2}q-6 zWwb#Ggp9;`X13|Qc<|!Hvh}(1;%wnEu|D;}&N~4B3YAtt5NZ=dN_%7!%n1#EXw)hU zLmf8EvMBP_m9EHb$*#0n;e1(UzTeyL?hV3_JFVr!kZvGC!bDtAqXOcAIRj^ifVqkY zyZ|uE55NFSBxpxo&$3KwZ36XRIQGyk{(MmI@4L?_QaQixeZy0MN|6Z|Gw~WVMo|VD) z9^m_I6)rM;;D2tUc7^3v|DE*6Jg!oEm~O4EDf0>yU_Ms`i~<3N+m0Jzazzacx=z|C zu_|YB_+GjTWwuc;YE8|?Owrp0Fh$D zC}n25<@)yR-5cv4UU@xT+fiBSI4et2a?jd0Oh$oT4LdF}3-hZpODi*TCs&qFG{}aA z_L5+RXhcGWdPY!Tz;+-uB_Tphj8Uu=F|#9I0vc8xd??~sN<<~j1EWU^qM4K+5PI<> zqLueVwu1i=m4Z^R377*KK?ZIz^ue`2?|@xGwTC>*v;%HVXrFxx&92D2$PlHaf|1B9 zP@k!-C?ne-E{H`;Nus?k9Fx(pLbPRG*wkUb$ix~*oPf7k>7bO1I57bRCIMZ7Xbl1q zks1j%N$2b}hy`h3lzN`~;L6ut|KN1{$MA_K|88?8x^wLYrOBDc76A>Q8^@JOz%m3R zGX#l*cSHgKi$Y{P+XPerr9qW}(;h9UkbFmcjFO5(4(~W75gVvLl-FhkT_{Bx!kg$5 zQ7QNkp#)S&!j=7KB_;%@qL7k1g*F)Pg0x1kwtjWvgDb!J;kWyDZ~YN$%$ev`Y!`s~ z!kIWt+J&qeXWeSFdTw?8{Oantlb^b9a%FktS!FK5!Y1S|Lh~kMr@&lyqg8L-XSY%| z-}iD-1$QWWEW&rq){j8)TQK^|P<;L7yY`#6|K%_J!Q1zSA4KjfG&Ut&c42ht;>pkb z#9w^r)#pLYX*&YHYQr585>>#YHRSXv`Gc?257hDZLEcHJU_oE$lgu)TRi$w94G3*<5aVYgS&Qc4u^Pe6hdGD5(Gz1 zFhrC3u3q1~$$9?R>N7zcV1Cj@qcYq;FanlXPmhF2TSfsQK<~YPC)7uOdV>9bw5_UT z5UGpjE0NwKK0h8g3_ugDkxb$@s8181>S*SK6;!uI)jhLO2oV(vh(-ZGmMSarY`wRx zD7f^Ny*Tf^wYJRi(%P~d<(VteJzFYJUA9f@2bz+ksT)a)5tLFw^CoRAVz-cmC2U?*b zgPK7CmQ-vLp%lTg>=n+tA{}MkM|N&$rqwt@m;y9Nxng9qvI~q_YXC^X1WBA_b-WYs zqwm3iU&>SfjpBg`AvlnIJuKRW2eVp#+U*hhd{}+V^jCzCro%yi!$A__19aOJK3zY$ zV=t3KhlI>9)<7$%9B>d(gb?vha~(eV?D+uw&mZ zJUe6O!V^(4<5&P4R?qW;x-uUnE~b4FkPip(^}k8#@|SUnMaV4E6-Wq&Ds}6AiU>js zl^X+Aq89oAqyH+7JW#nj;*%5G&Ra)(bws*ID|H`jz-`(9CZd(VPmVABB;Cq^r*or2!KT?G%Toy z>Nr4)7erBlfT#dV=d}e7-bp1$K##~C3hN^89C9W;6=wib@BxH9^aS9*XP`@D2QD|1 zD`a8RK#DApLm>ol!d4Vgbw|Y#h&JK@P@y(RHp0j&p;E}RFU1#`_hbSjMM9;w49kFVVPgD0MUcK^=W{a(6s{zn6?EBa1IkjZE*R+nW_ z=Im839Txpz4=G3*0qK?r?QpNi^ZhVvcyS`pO!H)t%!?|O(hLGx8RaFDz4iM=Z#yu} znWfXAX0N>fv2w^BKvgMH{SZ-?eG1@wLiR_?!Kw*nX|!= zX{M0aqBYUn%*p=A#ZR93^n0h5{@TiudrPfXI&&-ScBtW;=0>hk7%q90!H_9(VZ@x< z%pIJo26IF6~fXWGU@+${37$10pP8}-) z07B(?C6xgvRSn219$*SmWUMK25~zem$_EC@pwNR+c4v1pO4=u%UIiQGIwR<#M=c<} zV$19FreexrT$+pk0)ir*K?P%Sek!bb;97PZY^pZKdb#2{syvvh557WDmljqH*Q*)^ zIY48_W|TEz$3le6fXD<~Wk*$IKGvOnxCZ*FhZYe~83=p`afnfq1pt^Bgji6dbV!~H z>!2w2t!U@>-AEPLpe%|aPm99lS()c4uVbW*i6f;06aw)<5Cm}&1X`ush)gj$;4g9U@~TO#@X4{A!1r>NhLTV8QU{*?mMGb0~ znNA(~cxIW}4^}lqRmX-y&N%`e0DyFL&^1M1=K&3xd&mK5Cyq>jBpO&)O3S2$#pyiT z%C=1qZ%23@qH7`-DBLGrK(jAp8*m^zqjcZEiiBPE7jr%G!49GVaEhy9s6)qDVGI?n zFlp6sNHjU7M33bON6ZkMu!6BU19@a6$WmDi)OrR~fXvL`xoXu7RW5NT2bjJW4;i1x zFrG7~IUj_O5hi^Y5D%#*AMh;HFCPDu2aJdDeS`<}m#8kup~C$G$`!_7d4c)7OW)o%NmHSbI}$Tz$WLGwJ!SLV3m(xAVQGEw>G{Ky^c!RchdiM0Dn$ zW13mJeQxz)a^}g;oqzJei4&h#SXrZDRm*OWEsEK4U|3lolq&d~4*d_`z6X;PQ_stE zRsd6r`fGzg_k(xZtWypYi4mH+n{bE0SOI>M9hcq474_bD%PEk5O`0 z^J7tRN6QoO%uSs=7q{O}?5C5tZ|Uqch+kLs;}pJaiq};8br^mETi>PfbGr39jb5Y1 z>$>tkt8!CN9qVGX@PKRscSCs03jX?TQ*iLc( z>fLLvUtMx5>zn1vH;OlY_DBEAGp8SelKlu%f3>^4Z!u5v(dNcR)=v>7AM{-r0NHly z#ULTT9*FqT&MhrAI$?J=D>4&xmYNIu^K(x(IwPf4qp&CGYFfN?>;3+oT=F}oJImeW zlP}E7HiO2DaUGOcsk>ljHF)-mzI98aQ$`zUuf5m*t@nRdzJ2*$-g`UX@><8ATU6Ns ze;DpR6Q20sMDfEHKmB_b7yhgBuk}yP{0DI15{!Nx7QY7RYta6t%U*HO4R*6-(DllJ zihbNCyO^iHTHaM<@g3WrC)mP#h0U7A1<-pgpDFNe=1vsh{kZk?N%KsLxA^{(e(=5D zdSm~OzVqMSxqn(#?z-MnS#$KnOP~7uFaG?`;DL|nO5dpg*-~~6>d+VIDw#f249hRkI$$d$}C5v+0C72P~_pa$>-xjf` zOzTdNSPdNvG7X)e0VZhbKp_|kIG_j=5NV|p5s8RWii}3`m;smgMY z<*rPd-WrPUM9rm0@oZwOy(O(sxaxyb zb5heQ=)pidvQ?-*3sAji2P&{om%!IHRR{+O;*mX!W%Lidhku&wqp+682kSAi0ukXj z<_}|j0QnI1KP~Pck*Zt_1aa&PjQ~}sM2!;B9^!kXzA6cfs z^(vS~!2``=MVwU$vjTL$g5byaH!2`;f}l_nhyp~gkdKD_;V2*VNySkEJtVt>yZvqx zihG@8KGFkK$`%=7?ee6y3myl#A(C=SZM~u2z5*HquqF=PT;3SS(QuoL6nbp z=oJ#+sSsSil9;kAi`+LF4N-uM70M;Vl~Hk(cT`{jf&xTAA}^DiZEDVaz->8tg8%?v zY|RFBt4BPnejFn}2hYM`E)@=%H$U(?&-+U4)`udjV2yS13wJuRm?;e9M((L)Owj1o`*>XtS}H*6Vu zZ@C0J>Swp^Y+qX6|Mja^+w)83Ui#P%EkE|jFD;x3UF1Ds$sRO-pb(iF`iBY&qA5IS zV&{Hb$TT_SgX@Y_>#q8x<6z{(_*{?~m8eQE<%yIhROEuaCXd#9x9vMyEjcANqwBkI zte=d}T)XjBr)O*&>UXzyZd~1#*Oe&(&58(v9!bZ9-bmn~^pb#)IGSxO1W|K!bTXOk ztm>FacS2=DjoPF}FiJpKaCylYTieY>YLM-Y%AV(jKvCoyBfgRLmW$wCIoQeJZ99Cz zs&{Soj*s4Q{o7^u$I!dU$#*cjhw*o1aLvcxcKg?D{0$hq8L;4L4%>(l2Ye2WJ! z`RFY-c)twZm;4GR@A=^+uiwYf)iQe5jjol!hdzCuqZ@8?#)WrabQPkjKE27|9h-l^ z!MYTSKH7%z9nwO@B1hZGFNN_I`O_5LMfVm)*H!Um(7E1}w^j2)9KNj@mt%L?w7x6d z_j2>Lk@v%yx5DB>O1`OvSHjtEhUFdA`9`t*0yW-jO3#O{x7GUU>02|wC~DkQu85;< zpbBlW*vuM}7b2lLeepy%6Fjp?Z|BX|;&+2PfAlpub#v`bxv{=e${i~ zyS*H&wtW$Jn+Y4AOP2Qu(Vz@&Z;w=oR%daL= zgfT6g-W^_Ae|>Eqw{_A9qf^n`3De$%b`*z-a%-}|@OJOkt>3==wfySUU*6bQ6m#D#~;Z*o9#~>OFZnT4q%$UvJ9qK-#!FH_lt)Qwguv(baz7c2m(T16EIASeW8 zPzY>T8bmDsf?A|1_Zk;49F{#Fu*t~P;_-;k9}(-AyAt=KRAn?cHDhx<=c|Jz#g!aL zP%2uW5=u5WZ3^xCvf-g8HnFzL*kI_3kDVJ3 zG%#??$d&hEo)_SqE4{S{N(Ck|feI8kLbicPl*X10O%#JSf(}>-#RxIjPM#7QpJm{j zv+R+A*29Q4ed!oHS-<|sGR^6aeP7lR$8vvRq`KSXu|kN+rF)=Cs(J+#dpjHs zgOHM>LyFmxP(IqvcDHw2>D%qL*1=$BUk9eyX^J2k5g%IHwO4c%QP%xg19>|JTWYz=N(n<3?008LsyQ9%)d3lvkPj-cj2nJXb zF93j;Ev^cgs;rh}x=w?>pS%C$a#aGPy1OtucyT(!06hFN@Pn_HRRT~E?$i6JH>z5- z2&r+08X**~m@I*IO(~ms^LC*ZGTq&|vvK*#uYULko40PgnQgZK*24m*EF0RkBu?hD zMz5_;^y9q~d3z0ZJ2cxXyK&jcM(cC=!k*h%Eata;dcv!nqHH^cAjX*|UV7z+pZ(Y? zKXLLzCn)CPG}m^#{$7W+kG2HqS)Xc$vO(dl+)&<;N~yL597Kfk#AUfX>! zocUHcdST|GKy>~)*WTRQ+1k9{P1@y!(=VP_g8A0PI8K#a(yi?f&PuW~O6N+tFZnq! zTfSWI`kGCbGJB)U+a=#0j7*WqP z5B7#?)7{$PCwhz7{PJwxZ>{Zq{pNS&?%o^RJqh9c2FD9x&q8+Iwx0OJ;Dx3C_?aK+ zbi%(2AHNH|kHhRO7+kQO%MO=(G!henMpT97hVw(0dFw{rDaJKjfH)dm`yk(XH-Np6 z=CL(GIqhW!NqerG&KcMUkWwXxc4p7gEU4Io&>8+%bMflEy)S?8>;LGNzVX$2qf0J7 zN9XB>f99`!`p17HlopdxkRvNbSJ{eJ9$1s7=A*>c#~HSVCF+0X(f^-dl6=@4^3a_e zTk#JH)h4SIqA-eG1UTRp4(f{#31D-5Lup)GT;!T@eU#0S$hhb9aaMoa<@aEg6B6(T z-NZ=9BMFpLC>bXCiHbxkU)@&R|?;H^4Eec&gj zJVpt@)fgTJ%q+&$e13gB-Be6C{bMLx!*e*UE*NJrUPxi>v*~O3fKb)DfaE{|Cw87h zquxSL=;nY?%oZ?uVi6^-#a>IW4iW&Ch>E3T58@=xOYc)5Z4{CYoJ~jj!$EK8xd@{q z2m(cO=P{Zy0GT^ z%xK>iS(#Q7R!4mh!A{5eMV`|*>h&CC#EEdX4)Ol6exeIcG%b$Auhu=M!{71{L{t9gWtyKNPR{o#T$$xF{ z|1`IMsdw*Z)A0YbbM>!u2mg5gy{EHLwzqC5pAmp4i!r@Z{@%M+|M>0hGjF~8TJiC}$nWz*l^+PS;EIkHesVN+nzfWU08T^<oRsi?l=YX2DO`(?h`FE01jjY+l)Jw?H~Do!}OQw~=g-SKwb zhxb9wN$Y+(IJ2?)_Rh{uZr4Nlm_$1+pYwV%iF8`5k9JlI+e?CGn6L$Qr3?mJgPsic z*L^7}+E8U-IyZDV2xi{b_EyxqnZTX7xA64x83bNx#-I7*kDU8hb2fPlbywV+s4ccj zj+k82(NOcOSC*+wVIQ%r!ew2Y)KLI_#mOn>+Kk=Og;r&Tnu@X_OKjftW#7sm+g$=2 zNV!=&bCi7ZN=VdUB*jZsafSjQg&|O9Wr*U=u!ddP-#OkQaD58OPk|J zc}6=dxj)fq13SBar7zNoY!|%Dl)C#~L#BGE40%xmP|!1~nnjg>Ad8CzXLRrG)!W_Om8X`@be?0~OYJ~JK$xWR zqp#v}uDZTdMGOxJ`D$zzh3W?JwHM#`A^=M5J5+@~gi`g}LzJoxQmS&iy82Co04vNN z$5a4MoI9#M#V}zBSjLV?V~!mU*(Z%J<~XB(ibGJylIJ-FWU9U$RkP)alc~Ldr+r{O zu!af8fDAQrz$}DB!nI#1U{zdE?XSufDseraAUHtu!c|MIDq%oEr9gm{AfYm#YSsl9 zg*0l#n!+$fjH~K25X~-5;$~weFY;6y^vbJ|g!(UIusJqQUF z%K+fbI*11Ch_!`mdwshoiiM>UjVK0E0ItF)aqPVy(^V5=>wx-D`A{VHz!N_Ri6$pF zHYKQ#2^!Cm3?L!|T*D8KxJSs-M(%}LYIIU zVfvA*-<=*&;9=Z6a-#JIr&%+2P(I4yUP|X|W5ezSH!lCud%yYijZ5$PEy}q^ZclgH z7oO{XVe!kK`e}Y<`R@fE%OU?9%-(?gbD%zi^bE}2g5oj2E3mZ$<`QJ5W$(Ss`@^mN zuUx;q_~G6^yZruhJ$P?;>rAnHr|6yNwbz>S7nWXm>D3?k^o6H>Y^lXT_d=-N3d$4S zY%&C%%36GOEYYnLt`qtZrapEc{3oF*gJP*_xdoZ42aXj0BO|dOsGO3*^hBTY=2Gvj zx^e3dw_g8n>({NPgv`B@WoKQy?`a+} zakx>W0GU8$zi0aV{%Ck=RKC~WUK*4i4EL5GxW$8uIb3qt`Nr&K1Wz{S2aD~eJM%US z=JvX~Yj;1~-XEsfW-o=Z+^}Vm<(Im<^L={-M~l$jj^I4Rfeu_CON~a~$h^^g&7sy- zlR{vH(r&i2Qsgm;QLWgK&s^n(4msb1EDe-8QP!(hzXYX(xpih3GUz0=c55 zF(xb0(xreJ$%%xZO7M{;D;$8z0V2lKTC?BlZtUlSexV4Ojn(-X+ca%M142$30U{RG z7p0)&AR(b6t%xF0UO2LTB&dL#QL%)UUy zZ6r6lukY`l9pE>zy^m-5ySDqHi@sM5o`(23rmtz*i!hxHlMAP2&zydCb*YH;`I)8t zNS ztIgGy&dfdKlT%lI?<@b^KmDKA?|swrg)^Ty|8u|iH_klqloSD@5gb%NHAr1aJze|s z=cwNK(`>bb_0RsOWFq~C!@y#-yq3#|SkLOn?4kbfgJPR{Y9iU$*kn}83#$OYR384l zhm=Pi|Hst+k!r+!qH*xxs|(_lV{Bl&XK$MuAAZlet>>Tkp?EO>*T`e%F;fzP*vjgy zM&Ly%)}1CL#pB;PqHC~hHEJR_+6fx~{c>II41mElLv5c}1x{WR2>?2Tx@B19d0&C+ z0JZm8ZR|7F{ZH1fgNra`LMF{TDmGxky-!*MFezW;MPcJcq$uREngE0d9dH7`fR2w{ z{E7MigdP|S918rZ$^mpD8mJTrgmUBvC>Mx%n}d<5D>Z2sClSeWdr~` zvL>#Of3&6B>vcWI!oor`iHNI`9091vlbR|)#M(qiCZO-Zy_hGZ;{^-Xxul{5HFqz+pqrEdH@0z zBtS%?#3epy)6m}Bzk2CAZ-4dbMtN&s^Ab$!Njf+8sWZ=UJ8LK2fLSZzZv<^`$WF;Bn%cAU;`#YQ4xHh-Cy70IQSv-;xPwYxtsM4Z{7wa8W z;ex0Vq2#i#*&Y!A2^!Xzkhb2JXdH;sWJw~@MQFk_FK*u6x_0gMW`BRDR}zgdYA(&h zr%$Y2IKMdGXnHQiLtbVs*V+hCsYq1-Bcd%miDyt`ln9Ykq!l{Ee3VnDgRsHMj>H#S z=2J&kQ+zT^?#%|z%r0-ws;86oUScjZX7(0lUsydEcUDhPqeEvq42vZI=aLt9hkMF# zKY}vMih#SycX`+l+BeWL>6S}XufMk4yT5+}Zmz#`#NQ!07wdgGC(Y;XEu7HcW+&aXJ(#w z`9zZH=kf6D0^zTb+$LhYB4z+?t>Y7OH4P1h_`Y3E*(8LKQYl0`WR$K$1%F zP?O?VRcQeMIl|UZ7@D0D(QAppa5k$%O(EptQ7F z2c!d@f{YNc&Tk(k`>Mi{O5G%C5=;sU;G{EAJsuQR?G8MG1_tl~3ZO0`ka4sL!XgTQ z1(1+|k%?&>J60u&C_M455JXV`;90R2Kz5)N5rnA6ky~ISAwUNXNTU~aqMT$|UXo|R zpvY3MVbB})^AyCB%NhYo0weCLpj0~PSyqNV0s}BXqo*`H(-5N)$cvQhY7|ARH^*0q z4|0J=W)Tnj#iJq75e3myZpz2{-T|->GPAV>f-!+2A!H2zpnRQQPX4a)&T)6xBirRD}z6s?X9y+I|?hpZwwh+c?Do9BM@j zb#?D?s_wWJsPeU}lKZLpm|u9ryU6Xz>QZ2$iG%);QBiY?54%=J zT(pm&HHWyP!}sd}gM1oOIqsc@%v?iX2e8w_6ZINOuk@jI34l>`Ntj(SFrBouq5V>? z`#V>D_nmkD;PuE9DTikE2Tv++k7hnGPbB~?;%v|hrx)S;=WtUvABdCN*CWYb;X_)|I5(iX$TdU`E zQc_Zx*7-I|6Vy6z0(vBZLf%Vg^?qrVT(DQJx88gIf4KhJ8~1L!iD|YFcP>0}^{FHC58Ce$EL}_XWoMNlyx8?oYH`_Ni-@3J~w+4Ho{Z{VRC~tf7KKc-V1w1oj zBq9-#CoxQ+0uf0Vx>z=m`r5Cwo4ZS`r_Wr3#knU}XTwD^KRee6APZowF@wV_zk2Pf z-?)D5=B;n_dW)*D9hZ-T3M1f9f{-*R7f=+2Try(F77_CTST$Osr_Oz%b21@6W2JD` zGCKewU5Q&gqN*Nz50VW_VyTHZAY+Wu0>D%;+@OQdM8nMb`h6~2bp(}lsm6`AL_rThSP=1`E)c;F_JV;{3Agaot#-4G*! zhS6f73>T;zIwCiMvUFwXikvGsuOk3pZVMJ|`O%;KnV+AXTk&>;+|rVXH>^Fdu&R*lLmhHptSPPM78=8u2zIR7By1my=dNqE?HZ04*Rl^iF@r+w$D&N&Y4pH4qyDmVg$_eB^(Llh{ zPCzo{6D8nb+@TzS#fJclP6mH57bS3yX|KUE2Y$g7EMDDxthzEt;ZZX0o;0|Ql% z1|9$^UqKw#sXkf<@lpPM09jOzq60Yk(CY_5NTnDaXIEGS_f=?G8^$1THD2nfgUQ#c z(Zthch<5ZZ^V)Jz9@W}_VYaVmYubEnq@_bURjqN)BrqKOIHmA zAwUm8m6R676G82%LPAQFxicVB)h%Oe@}pB2HioJ z#Gz?(bwna4QoUPwzW|9$dc;=8e1!5toG8YsUmPbm_25>B1dKr5`69KsE4;WcjGHsY zK#U|gIsinOu&N?J6PW+F-^-y6xCaI}N9PwgmWdrNEvO`jz-aR_2m;iqcE1sPfO>vv zBdDBNu!=elj#d$91UAe*Y9xk~*xXVw(@9$GCVF9VHI4xFRjs9QG!AN~fA+|D8=nyX zRwTwD8B|5jQLP#j7&{(FHIWE`00PqFk}77~ANG(41M<~Z#-=hLBmyE#z$hL(V<^JN z;187l2o+fIhzkW!-%EOA58w%oawzwE_;#>JOtwmUDpe~}k!b+taLO^EaZ$5)#l%&4 zmv{seR0J*}te(#22zhw>w@{GXc_vi#1x zvFk+F{Jk~jx>WQP?-47;f(y~o7YG1L@)1GF&U@Pc8iJoITD$p3sFRBeg1NTLH}uo5 zKAU{{9DU-M-iaHR|KH#ItH|r4GdmLP=r8)09ceq6lYL_QU#XCENlQ>#8Fei@*Z6M;2ZD!`u+Fs zeq~Uq#^S>A$NQgq@@Gyyb}x1(#P8)?NL-XQl`_Nt(b3lS&0E*juk8QP=8dGE-<08s zAsaj64d*@uMn@$nr8FuPAzAMv*8t z`PjnA^4ZR;NhmUY&&-_ay!OHuKJ(d&cb|LXyKDE}yHpfK%aLJ4VL@oDv5u4w5-G^F zLW`m5ESx*@Og=-i9!u{t&t4Rv(xgB{SP-i&xkRLlvf@LNrNbl%kQ6u{1WFLul?aMe zu7Ws2E;-kM8ab~CX6KfdOe->TVKhhO45(Sp#+8->8<^R!)r6Tw>N;szl+J=L$cE?? zi`PPc700bvqp%V{k4S{V00@krj3!Y43@T*uiZv?n5Qt~%Y@UwxQdG=gpb8j z?&s92I&j17^AOyOKqxRO*eBA7y{@mD)Lt+5HVoYaml!IPN39Z!8-k80BrX6zC`9&z zk(W|YNtT(}qX>)uOBN-{6Ew9ZAQojo1A^9ybf{fP>~k;Zoj4~V&Oj_+Ecl#D<;4gF z;ApRv-J9FH_oiC;?8%S+++TkD?Em~6I@mp9<`L;mLwXPzItz2z*VV9K^m=Q)>W#+KC9OqJ=7=;BiDG0PLM0 zkOZ~Gc*Rx6KP@+U z6hCux&=4FnVVIJ2jGvRqj;8ilNsOik6^`$!f@(d(D*kaGmE8^^&{UgR1Z1deErQeQ2hrI@GF7I6?(H^zX5I(s8li_#z)+LWDd3u`7GsL2IUE zOjsN0KV+L$tyKT5;_ymWE#zxgenDYmDa)?w4|l^b>?F@Zgg)2+QVaz-gXB?YTv1fJ zM<3!bY83w{0dgR}0|0c$s_M{N^;I~i+-D(Bf?SzcvkL%XmNo|WMLJ6FD<2Khje9rm zt}LADEYEQ@|B6DyX}{`Wzdy=Hv$LH>qebP42Sl|Fp6hh{*r==7!^!jVFcg3MN_|Hp z5D_8&kk3EV$NISRNJOf4(QG45wMRJU?NIkN1_c#I$qXRmK{9ChXxo+-oOxf?qpNTJ zvunTc=H0cOZe|OXK=#i<=V!mr|GC%x`{tMWlK!||`c_}k;i#X>Te!Z8bXoOJ5_M5J ziZkK}RR$aYI$^_-iy)yDm6X&K*OK6V!KZC>SLkAtKoT!E@Y!axuHCc7ZuYX|-nW1E zo8P{1{qDbg`@<8xm9?3bFP#3V*Ped*GmoA5VjRA&MrQ)LiNzdJAKeVoo+njdAytxz zBsKYlM#L)H0zE3UaVFEeFvOdK9p{U+nb}txGy5St>*7nJTk-Xqzj66DzkBo2joeuv^U){YkU%#NaUYMmN2)P*Rv8d@HQgJGT(yX%|#d)sgB-k#qpzA{{YMdrRk z`ya;#6s-PFxQ(@;4_ZrBuxST(K`b%cfmt~al$bmydBqA^sgjUtM0OSNQ zDgY5M=<~9)s1s+fsS(7pFGs>eK^zBy`XYvV3Z8-F7L;dHxGi>B%!Wa-0JmL@AtkwSi87c{%*Fv+E4GNy+P?35^Pbr z(mB0x+C2Tl%a>m4{LN=y9X^-*AIAUC$Ty?5*Kx4sd}@++U1 z@0c$PJ8RCJ@d&JMDTn|adNrvJJfbgngwI~}qr&Lf3Xst>3C||+ApihbVDwxJ_eb5{ zUXkq+H^SJXGz!}*GK^agP^Z1nGA1OAMu(z2dL!hk4z(h+sSz?F@Pve~N)9F~T{SX^ z1?tF*KzVYCiJ1g|vV4&CGG;fwyreM@L>5;G8NQD`ZBqI;y>SG{N8_$3eFRJ!r&jCl zVdu%CQS0IFPIE8S@R-~hJRCyTF;HC+J*9SGh2#;8ARzgoUkpdxqOeN~%RwAjwnRW2 zOe*3OAORu;ApRqD0{oky#JN zO_4+xpsKyb${n^6gjbqikV@ZP;Sj*cswy^TA!I-zrBvK%x&ql3iVd?THAazZI;EC^ zSU)a6pBxW~)YMolpIX_MSmNz!O)wam`Is z0KkOSm_i*Mrbjy@D?hRwAFK?3RBP+1G@1atpc4m9NQuYl3?HllnzRP4_l7mN4#Jf- zu=0&mh*(lS8tiTCLZmvYNtFeYSXE-xK1M1t`>G?MR5CY|aXw$A@|C6W5y}N>=QRdY zQazHDG7v>bSI#hFyLSNubVlhS5Dl!9L{QkG7^SYCX505SZe^o%kSJn`7r9uhEW&N4kkvxk=Do>l@<^NC5Wg|7)6*Q zL`m7&h(;!Bsd5Ba=CNeA&vHsS>sCECTv8?g)zPE*v&qY!dhT!k&G~!O`=2`ZfA?3i z%YU%>y}Nw+z3wNjJo$WVfGHpzMS#f=u!tZrgP;%_%Xuiw3N|C^gv7BX9g>?t%O%x#=3eX4x%#lQRUpS<(*{C~DG z|6$91(wH9b3{Z*^3lvVcq}{BX>j&T6+V%#sJKuA&AJ-rM#Iry2Omt;<`E`$5OWN&s z_8YM-LzRs($d(7E&OCSGoJG(hpE_amY>Wb1C%QxofXp5}G7u;Zf{2kzk)Rcl6(prq z0FpC>pfHRf3|E?AsbI-c+V9()8~_Bhw{}PG}FH9x#4zq zy-ar+ZND^sHfgszp^k=_jc)mropYG$O8+2CEFJ(r129tsWU_!Dp(hVYL=$=>&n1We zN+5_73TPzsNTLi7Itz%-7CAtgl@bnHT6r#V3bJNfqDYkSV>nt8Yc>n|QrNEtV$S}H9_5yHM%l$UOT~+4I*eU=OgrE^rBxnd&^oV_f z4HF}vVpWRvAX+(9xkWREtUW8O5J7t(cGh`YaB1^APX&-YIH(#td2s@bMy%q1H4Bk| z3LH3(iBWmz5)WCb!vbai5Nu9)R1W91`LdE9TKde7|M)Eb%K!NO=8Y?tf2|*{rJwt2 z^JmVtbI6=1CJ`VYBqm^CsvPBIQhRfB0+XYw*AJ}!kx3JnBgp9iEr2W&(qU}Sd}!^H zv7H82f|dul=^t70YOZ(^7fyUHrZL@PJOGk_R5Ed>R`*F?po8dbI$lQr@yL}`$QUnw zbX%F}Md3*3KY6hVj=_dLh8%8vaO>SW@6A4Lo;v?UncdtU?t!3!Qk4Oq5|GLvvtmgF zxb`_jkK;Pa$JFx-H7$*X!r9p%Xf=9+062=8UTmVhF*)REH_f{J zwLT(x?}^A5ggoLqb|7!fUjHkJS-(U{;f$o11Oc?qRwK*o=6pcQ4OWB z@S=*4Wol?u`w@|YSP&VIK%~ljh^l}XL{%KZI6grnQXoLWNG2T&%TXboj8O{7b0MHb zgqfLKr6Ax+(c^&x9$o|RpbK=U+I<{LiT7S)Z-1-TPtTq?5ryqZ+%gvDjI)`CVB5zP z{L=;3$huN@Apm$KK!ODzRuo;;x>+O_r;$8iAYj20BC@wAke6BRGoN~=oB^#&fI85I zYE)i@QK*O`p&A#T;0KBc>V{{v7^OM|Pnjz)EBk3b2aBy{vl%smAW~vjd2i>u63|LsH>hH;Dr>9LQ%6rS_PG30p z@Wqa?v4WT%AHXPL2YyC9QUQP z;)aSWl31<0Bj=hDZTjFeCs8~@9s3hW^Bt4@BGI=;gN1kB_+S3*H{bl;``;P5)1#Aj zRr-uik`rfw=br!a6HlL>S!gyJCymT&y3g4n>JhR=EEyuNiy~aeB*I}j%)d4&qM7Ex zLYZj(-iN=x`Tp(eJu3J5dwaL86}xJd?ma*M=@*|IJiYkS!KrPVJ~e3H>q%TBd1UlV z*evoP6;Y;kZr+aGTzmWO?P1?-W4@Hjws(U#n*I38@h^Px@4fu_kI%F(Ku5?-#pM3Gj z&hr$yY=ouiTjhkUci^2vs*I3)9Dj@F$gk!qr6*SBpI&G!8L%$J{r($+4V~jP zoweI-j^^eYu}QbK_6NIpIxHn)A9$Av1PCPwfp=6t-ImAzc(w@28;Ve)pl2b05`|EK zh-fmNm^o0U)IJ11)D(JY^s>F(O+;N1`oflhHjSo)W-badlEo<5P4t;4>{59$Y~9QU zi@*NHH@^DE-`eXd^hr3gJ=YlMcB8X6I@f;o{3*UL`|A80uf~tBEHp#^6tr(b?=#Z* z4i2C9?eCTT^eDNJkz<=Vw?kd+vRxQ@!NPc>$taBoA!#oXi7r4QfD!l^QoX7&L_iBz z;6$hdjuaN+0;to2jC>O%6E>{&!M7Q_V|I>5l3A}%tapmt(3Vl=Ew~2pHkU1w!UVG+ zb#$l`^k|8}r!L54$Crf4Is$gWoB`?i#j~x}Yd0?c^1u4++gG-}YTc7dubue$pZFWk ze(WWXTxvT>%~&9SC!Dag6Lrd;Smpn~?Eu}F;ufaa1~^uWT={*$#Ixn#jY?~NR3`K& zdD4RF!)JoEIqz@8X1cS{8bO288916ROF>j1k5z!+>F`H=(F12CXsbnM3^c_I~y zUa>J4Y#!0FR&gr<5{n25J0eo3iI+sgi?gL?wIPfpm)Ri8@{ucTHcChR0T3dJ38H2v zj-ntiNf-yZ(Fg-g7$A9(TJDKLAYKsa<0N7bhkzs`P?V#)m#>Wa?%WfnXJ$@90N#6T z>S~RLUfb!L{%{G_ysHW< zYb{y148WA^m07;OyOSiix05#-jrr9DTO}oxC8Qu8Ac_RQ!1$3EfNH}d)$WraP9$Z? z=e|041aMx%V_=22iYZpvcr6 z0FIBVacAGju$W*HP_?{%G)#b%xnY%Lq5zPpO!TF;1Fpn=N+>W`X*;LH<%n2ims4{h zgXpT6g9ssEMxoA52wN zOz5C$ZbGW49q=MT%uFD{l~p_-s({dYE7s-P7CCG8^x3+-m8gU*@L zr%s(Q0p7lKKWPL?AL%k&FFDMtjOi@Q40`=hf8_F_IWrrnkcq(`tZ)qDN`T{XwefwN zetn{(!if-W3=#dwwg=be9Q_arC`J#893m2MF_IJY9p7@LHg|z?fmb-o3x^`d2iVNTA; z{D}+C{`g<|_x}2+-~Ibv`OPf2!C|`Lw&${&_uu{Y=GyYv#@Uruf9gliJpXyr;iyO( zp(YAhlax(iq>cT-&9&|CUA}Su%>vuczH%~C^H)Cj+Iw%@*ck0$h^@x*`4jUepa1x& zXVyxf3)yP4)QKRo{n zKk@fI_4(x!@EL&&as@yj2&h0IPx|diwH$#D4KfNv=uogFF*rM$g=S{usV{wD?)jg8 z@ekg-{npq1_290|%1RQX97bHYy&m=MUH#^I)_daIOHcp!;!5bEY}Ym**)VgDf9%{} zGHV-`UjOE(e}Cn|+{KCPq|9z#c>Z6+u->ja(ESz=k1| zD$RAI!$us%q-KVk-@kF|^2X-Q{=3C6@O~IlQ;TSYF(?uVJ#)b>mtuYQ&U?FeC^xFp zZalegdgbI}t0ynMa^lH_lV{F~TI%n~UUqLI+gBNO=96TW)J`+Vw#zWRfjDd8e3{Lt-z zEtJ|5-q&W^Kpr&jN78S^X|`V$MWdCR+}Yj%aL=AQ|5yI<>eA|`qt)9B@N$&wgnpH9 zsES#u_T4ZMV=)+rFG1~KnsIp-Hg^mUIR#B!>nKD=q%|}l62MTD6;EDSW-sWgt|Oqz zaVZxI3wl8CB3=Yaa-b9`U?2~ql|x9ybwoviwZFPXN+b}kl|l@HAZSU{W;4R9Gh)Bn z#qGgxdv`rg?~BO-uVUR+DpxvE#03R8p~BSoLdqHM=5KGm72@*Jh0{OzcRxqJb?d$F z>~6ij|7&;tf9|jLKK02@j&$O1Tm#= zR#yhABDGr1JzhYOWt@P$glcIx*ZYz0AMQ`S z`H0>H$eNLSxV^Uh;k(z)zI^`t$!GE?E%F|)XkbFcpkWe3)cv$%d=5t_YX#_PaIn%V zfUhiqfT^zIQ-DC?$1630fUi9`g=*0bphrZbkS zXl`LOo^4iU&!hlZ1Rt5QjsxxocNUtudQ=^*WO_b62TQHqKh^9^Q3!Kx!oZKX#z$>Fhbw*2S}z~pdY>fx-| z99NcmyW6|lTSWTY6Bm=XDT0-E2al2LBmONQdSZGy?X^E=5sz z>*7YDRP7ufT!PeSWqmJdG{siFI#N~QgC-RA;1MwJ;$v&~$~~KBBNcEvSyoMt&=loa zDtfNsYHP%3(wlH>>O+VkY!Go8Ss&N+Yau_W3P2JpmKh1fGKQ?~0AuM2a8mGaf4AS? z-v&-hLNY=v4iOg?76PpT-{{GHCw_KzsZrRqyIXgg?MAabBS0{2Dm=Lhl}wrd(sa1H zvxTT;7v~yLivfTc5ryi;+v8g|Za_Gtj68DVrr|4?!iNqX@dp7;Kj?O(Hh{)I%>W|E zN|;z&0|VH>yLQv3NVFDYOG*-FMHT|=}XziWNjrYI)kN)6yXV+UF_HIAR z(H(GeNz}OT+>=j!<`Z6KIoUt8dLo{WQ#ab&*x%W|-@o2nyKy7Ct8VRFc6rtcIGUP-cQyj3eAc0`U5+SC} zH$Cm->B0~{*uO*Fy|mEPnc@W#v2v$Kt#;Kr_cqt>ZG70<92Ha=X+i`ZYEb^GrW z>#b7VA@o|3ytvlyyS&^MHo^f|3yuJ+BBLsjiMjMnL{ND|tx&4hg*9l;9#D}|0b*cv z-`V-S(fzl6|HJie`qcAJFV1N31u7-pVpaU$HINld#WP4-5g26fVSI|$cG7?S&%fTV z;s52YKlzJ4`AHal85VvYikEEjinFVt*V!+WLEoy8m(c3#GFq{42?u8#-lD-G(WWWe z2o4n~BvKBg!6pMvSP#WDP!;Tg3hzf?AVF{_F+z-zImgJFnV6ju00o2*pz|Cnv{Hts zGVn73nTTRZ#d%h__La#`gt`Jg&StOy|NexWNvY9J-T({8#~)e ztnP&FbR6x9S_zYG1gE0rm5cFbU;WgK&w^k7=8fO~-haQFJ+<=G;xGK{554&5zr>Vs zaRT%P+K5zE$&4qR(LWL%@sU}AhwiG1e#erD3FQB%eNCycj|3{yzw!}t5k$~ge2QT&4+do=JrWsW!gt;y=6848dy3kAnnh z%s@HV2vni0sjzbx$LPmu4C<(?sudLRipT3O0E+-s?SEyO{hp}aC)elD^+aSrq)NGB zkph7Nys7Q^E6H|+M~wS%5|Q^_y!YV2N;YhlS>DgDXWhbaX025ORb6FsDUPE~)M}bQ zAt)3=RN&j}A`I3^f}Nc!8@Kn*ojN^xVwn*vTOuMBKvl&y6P3h7eL(1>>ZLI0>~!Ss zvFGXZ)vGVU*x+cK3O=Z`P|*C4v+P zh7@~ews-C-OMy{?-QLRL#hH_eDf9?ZS&oiroawQIh)4SX{GeO)kee_}+O6TRj{?3Z zRRq4uG%6wqDxn-Al8Bl@f4#7(S`_hQ6miv20Hp511_0jJkeyHW`$M-s*eMGc+5#w4 z$$Y5enbwL)WI1U%BO!q(P{LSY;2O|-KkN=wsB{qP5E&y4Mo2Vjjo>Z!$}WoV)F7@H z7eMq59dgDtccu45+-kSm%_xWgz&jBSX|Zq9a@gNWyH+5nxsM z=_0y{J>be=w`zA+w;`;x`-mXMD=57mxh&83M%^%I%&#^OB8LUB8{2xS8sk8$+VP5T z1$_vjjP0XP4ma^81RzEaR3kI>-wuSGl8`i%S!pR74YR>$*xTFJ#vz2waBgOKHHyhu zN|L~Hp)pVjN8RD(+NSlVqNONzynek1)VhjgNOi#H0}gQI!NMX+-yV*QjDww z1%X7W0%!pMo~l@>I&k55&>VNAIN+eGMGzTeTr%}1j{Xmkzg1CAJq5E6l4n)`fCwH* zoNn3X?(X|v`@OG_4xX65XfJ*E#y|YMjlC<58afQl{IL70KlyiF{QUjF#)W;p1f$=> z^HLJ!Ez#se99mSdfD>{6LJG+%#L7TR30J)ogz8z$k<%b03bI1hR4NMR6Pz

Ry~`hWYO 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 i1sN7X~+Y>F%^8kGPr+`kjiwvlIO#~#@^`x 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 Date: Tue, 25 Mar 2025 07:03:43 -0700 Subject: [PATCH 009/244] fix docker compose --- docker-compose.yml | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 1cab80ee4c..66f5bcaa7f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,35 @@ version: "3.11" services: + litellm: + build: + context: . + args: + target: runtime + image: ghcr.io/berriai/litellm:main-stable + ######################################### + ## Uncomment these lines to start proxy with a config.yaml file ## + # volumes: + # - ./config.yaml:/app/config.yaml <<- this is missing in the docker-compose file currently + # command: + # - "--config=/app/config.yaml" + ############################################## + ports: + - "4000:4000" # Map the container port to the host, change the host port if necessary + environment: + DATABASE_URL: "postgresql://llmproxy:dbpassword9090@db:5432/litellm" + STORE_MODEL_IN_DB: "True" # allows adding models to proxy via UI + env_file: + - .env # Load local .env file + depends_on: + - db # Indicates that this service depends on the 'db' service, ensuring 'db' starts first + healthcheck: # Defines the health check configuration for the container + test: [ "CMD", "curl", "-f", "http://localhost:4000/health/liveliness || exit 1" ] # Command to execute for health check + interval: 30s # Perform health check every 30 seconds + timeout: 10s # Health check command times out after 10 seconds + retries: 3 # Retry up to 3 times if health check fails + start_period: 40s # Wait 40 seconds after container start before beginning health checks + + db: image: postgres:16 restart: always @@ -16,3 +46,23 @@ services: interval: 1s timeout: 5s retries: 10 + + prometheus: + image: prom/prometheus + volumes: + - prometheus_data:/prometheus + - ./prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=15d' + restart: always + +volumes: + prometheus_data: + driver: local + postgres_data: + name: litellm_postgres_data # Named volume for Postgres data persistence + From 62bb7d6605a4e5a26734f203505c0bb06e0ea430 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 07:48:33 -0700 Subject: [PATCH 010/244] add vertex gemini-2.0-flash-lite --- ...odel_prices_and_context_window_backup.json | 22 +++++++++++++++++++ model_prices_and_context_window.json | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 3124ae2697..82691f3c99 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4595,6 +4595,28 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini-2.0-flash-lite": { + "max_input_tokens": 1048576, + "max_output_tokens": 8192, + "max_images_per_prompt": 3000, + "max_videos_per_prompt": 10, + "max_video_length": 1, + "max_audio_length_hours": 8.4, + "max_audio_per_prompt": 1, + "max_pdf_size_mb": 50, + "input_cost_per_audio_token": 0.000000075, + "input_cost_per_token": 0.000000075, + "output_cost_per_token": 0.0000003, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_response_schema": true, + "supports_audio_output": true, + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", + "supports_tool_choice": true + }, "gemini/gemini-2.0-pro-exp-02-05": { "max_tokens": 8192, "max_input_tokens": 2097152, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 3124ae2697..82691f3c99 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4595,6 +4595,28 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini-2.0-flash-lite": { + "max_input_tokens": 1048576, + "max_output_tokens": 8192, + "max_images_per_prompt": 3000, + "max_videos_per_prompt": 10, + "max_video_length": 1, + "max_audio_length_hours": 8.4, + "max_audio_per_prompt": 1, + "max_pdf_size_mb": 50, + "input_cost_per_audio_token": 0.000000075, + "input_cost_per_token": 0.000000075, + "output_cost_per_token": 0.0000003, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_response_schema": true, + "supports_audio_output": true, + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", + "supports_tool_choice": true + }, "gemini/gemini-2.0-pro-exp-02-05": { "max_tokens": 8192, "max_input_tokens": 2097152, From 0af9a5e8d007563d6db79171f802ae10876515a4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 07:51:42 -0700 Subject: [PATCH 011/244] add gemini/gemini-2.0-flash-lite --- ...odel_prices_and_context_window_backup.json | 22 +++++++++++++++++++ model_prices_and_context_window.json | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 82691f3c99..a098e8bd7d 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4680,6 +4680,28 @@ "supports_tool_choice": true, "source": "https://ai.google.dev/pricing#2_0flash" }, + "gemini/gemini-2.0-flash-lite": { + "max_input_tokens": 1048576, + "max_output_tokens": 8192, + "max_images_per_prompt": 3000, + "max_videos_per_prompt": 10, + "max_video_length": 1, + "max_audio_length_hours": 8.4, + "max_audio_per_prompt": 1, + "max_pdf_size_mb": 50, + "input_cost_per_audio_token": 0.000000075, + "input_cost_per_token": 0.000000075, + "output_cost_per_token": 0.0000003, + "litellm_provider": "gemini", + "mode": "chat", + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_response_schema": true, + "supports_audio_output": true, + "supports_tool_choice": true, + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite", + }, "gemini/gemini-2.0-flash-001": { "max_tokens": 8192, "max_input_tokens": 1048576, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 82691f3c99..a098e8bd7d 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4680,6 +4680,28 @@ "supports_tool_choice": true, "source": "https://ai.google.dev/pricing#2_0flash" }, + "gemini/gemini-2.0-flash-lite": { + "max_input_tokens": 1048576, + "max_output_tokens": 8192, + "max_images_per_prompt": 3000, + "max_videos_per_prompt": 10, + "max_video_length": 1, + "max_audio_length_hours": 8.4, + "max_audio_per_prompt": 1, + "max_pdf_size_mb": 50, + "input_cost_per_audio_token": 0.000000075, + "input_cost_per_token": 0.000000075, + "output_cost_per_token": 0.0000003, + "litellm_provider": "gemini", + "mode": "chat", + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_response_schema": true, + "supports_audio_output": true, + "supports_tool_choice": true, + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite", + }, "gemini/gemini-2.0-flash-001": { "max_tokens": 8192, "max_input_tokens": 1048576, From ff61ce675133df7442c91049d54b18b59ce0e0fc Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Mar 2025 10:17:34 -0700 Subject: [PATCH 012/244] docs: update release note with patch --- docs/my-website/release_notes/v1.63.14/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.63.14/index.md b/docs/my-website/release_notes/v1.63.14/index.md index 9aa97b2148..aaeac639d4 100644 --- a/docs/my-website/release_notes/v1.63.14/index.md +++ b/docs/my-website/release_notes/v1.63.14/index.md @@ -24,6 +24,7 @@ This release brings: - LLM Translation Improvements (MCP Support and Bedrock Application Profiles) - Perf improvements for Usage-based Routing - Streaming guardrail support via websockets +- Azure OpenAI client perf fix (from previous release) ## Docker Run LiteLLM Proxy @@ -31,7 +32,7 @@ This release brings: docker run -e STORE_MODEL_IN_DB=True -p 4000:4000 -ghcr.io/berriai/litellm:main-v1.63.14-stable +ghcr.io/berriai/litellm:main-v1.63.14-stable.patch1 ``` ## Demo Instance From 1a58e8bfe5bfbc306d48a8a6928723f7fb4d7938 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Mar 2025 12:25:16 -0700 Subject: [PATCH 013/244] docs(admin_ui_sso.md): add logout url --- docs/my-website/docs/proxy/admin_ui_sso.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/my-website/docs/proxy/admin_ui_sso.md b/docs/my-website/docs/proxy/admin_ui_sso.md index b7f8ddd585..36a97add4b 100644 --- a/docs/my-website/docs/proxy/admin_ui_sso.md +++ b/docs/my-website/docs/proxy/admin_ui_sso.md @@ -147,6 +147,10 @@ Some SSO providers require a specific redirect url for login and logout. You can - Login: `/sso/key/generate` - Logout: `` +```bash +PROXY_LOGOUT_URL="https://www.google.com" +``` + #### Step 3. Set `PROXY_BASE_URL` in your .env Set this in your .env (so the proxy can set the correct redirect url) From e8c4cd8c1af9532ea259951b3b15d1d111be733c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Mar 2025 12:25:42 -0700 Subject: [PATCH 014/244] docs: cleanup docs --- docs/my-website/docs/proxy/admin_ui_sso.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/my-website/docs/proxy/admin_ui_sso.md b/docs/my-website/docs/proxy/admin_ui_sso.md index 36a97add4b..882e3df0b2 100644 --- a/docs/my-website/docs/proxy/admin_ui_sso.md +++ b/docs/my-website/docs/proxy/admin_ui_sso.md @@ -147,6 +147,7 @@ Some SSO providers require a specific redirect url for login and logout. You can - Login: `/sso/key/generate` - Logout: `` +Here's the env var to set the logout url on the proxy ```bash PROXY_LOGOUT_URL="https://www.google.com" ``` From 4c87084ff7bd99966a33b6337ef58a5a2f11d9b0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 14:07:14 -0700 Subject: [PATCH 015/244] UserAPIKeyAuthExceptionHandler --- litellm/proxy/auth/auth_checks.py | 42 ------ litellm/proxy/auth/auth_exception_handler.py | 130 +++++++++++++++++++ litellm/proxy/auth/user_api_key_auth.py | 55 +------- 3 files changed, 136 insertions(+), 91 deletions(-) create mode 100644 litellm/proxy/auth/auth_exception_handler.py diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 80cfb03de4..98685e1a7c 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -1009,8 +1009,6 @@ async def get_key_object( ) return _response - except DB_CONNECTION_ERROR_TYPES as e: - return await _handle_failed_db_connection_for_get_key_object(e=e) except Exception: traceback.print_exc() raise Exception( @@ -1018,46 +1016,6 @@ async def get_key_object( ) -async def _handle_failed_db_connection_for_get_key_object( - e: Exception, -) -> UserAPIKeyAuth: - """ - Handles httpx.ConnectError when reading a Virtual Key from LiteLLM DB - - Use this if you don't want failed DB queries to block LLM API reqiests - - Returns: - - UserAPIKeyAuth: If general_settings.allow_requests_on_db_unavailable is True - - Raises: - - Orignal Exception in all other cases - """ - from litellm.proxy.proxy_server import ( - general_settings, - litellm_proxy_admin_name, - proxy_logging_obj, - ) - - # If this flag is on, requests failing to connect to the DB will be allowed - if general_settings.get("allow_requests_on_db_unavailable", False) is True: - # log this as a DB failure on prometheus - proxy_logging_obj.service_logging_obj.service_failure_hook( - service=ServiceTypes.DB, - call_type="get_key_object", - error=e, - duration=0.0, - ) - - return UserAPIKeyAuth( - key_name="failed-to-connect-to-db", - token="failed-to-connect-to-db", - user_id=litellm_proxy_admin_name, - ) - else: - # raise the original exception, the wrapper on `get_key_object` handles logging db failure to prometheus - raise e - - @log_db_metrics async def get_org_object( org_id: str, diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py new file mode 100644 index 0000000000..88a94fd5b9 --- /dev/null +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -0,0 +1,130 @@ +""" +Handles Authentication Errors +""" + +import asyncio +from typing import TYPE_CHECKING, Any, Optional + +from fastapi import HTTPException, Request, status + +import litellm +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth +from litellm.proxy.auth.auth_utils import _get_request_ip_address +from litellm.types.services import ServiceTypes + +if TYPE_CHECKING: + from opentelemetry.trace import Span as _Span + + Span = _Span +else: + Span = Any + + +class UserAPIKeyAuthExceptionHandler: + + @staticmethod + async def _handle_authentication_error( + e: Exception, + request: Request, + request_data: dict, + route: str, + parent_otel_span: Optional[Span], + api_key: str, + ) -> UserAPIKeyAuth: + """ + Handles Connection Errors when reading a Virtual Key from LiteLLM DB + Use this if you don't want failed DB queries to block LLM API reqiests + + Reliability scenarios this covers: + - DB is down and having an outage + - Unable to read / recover a key from the DB + + Returns: + - UserAPIKeyAuth: If general_settings.allow_requests_on_db_unavailable is True + + Raises: + - Orignal Exception in all other cases + """ + from litellm.proxy.proxy_server import ( + general_settings, + litellm_proxy_admin_name, + proxy_logging_obj, + ) + + if UserAPIKeyAuthExceptionHandler.should_allow_request_on_db_unavailable(): + # log this as a DB failure on prometheus + proxy_logging_obj.service_logging_obj.service_failure_hook( + service=ServiceTypes.DB, + call_type="get_key_object", + error=e, + duration=0.0, + ) + + return UserAPIKeyAuth( + key_name="failed-to-connect-to-db", + token="failed-to-connect-to-db", + user_id=litellm_proxy_admin_name, + ) + else: + # raise the exception to the caller + requester_ip = _get_request_ip_address( + request=request, + use_x_forwarded_for=general_settings.get("use_x_forwarded_for", False), + ) + verbose_proxy_logger.exception( + "litellm.proxy.proxy_server.user_api_key_auth(): Exception occured - {}\nRequester IP Address:{}".format( + str(e), + requester_ip, + ), + extra={"requester_ip": requester_ip}, + ) + + # Log this exception to OTEL, Datadog etc + user_api_key_dict = UserAPIKeyAuth( + parent_otel_span=parent_otel_span, + api_key=api_key, + ) + asyncio.create_task( + proxy_logging_obj.post_call_failure_hook( + request_data=request_data, + original_exception=e, + user_api_key_dict=user_api_key_dict, + error_type=ProxyErrorTypes.auth_error, + route=route, + ) + ) + + if isinstance(e, litellm.BudgetExceededError): + raise ProxyException( + message=e.message, + type=ProxyErrorTypes.budget_exceeded, + param=None, + code=400, + ) + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "detail", f"Authentication Error({str(e)})"), + type=ProxyErrorTypes.auth_error, + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", status.HTTP_401_UNAUTHORIZED), + ) + elif isinstance(e, ProxyException): + raise e + raise ProxyException( + message="Authentication Error, " + str(e), + type=ProxyErrorTypes.auth_error, + param=getattr(e, "param", "None"), + code=status.HTTP_401_UNAUTHORIZED, + ) + + @staticmethod + def should_allow_request_on_db_unavailable() -> bool: + """ + Returns True if the request should be allowed to proceed despite the DB connection error + """ + from litellm.proxy.proxy_server import general_settings + + if general_settings.get("allow_requests_on_db_unavailable", False) is True: + return True + return False diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index b78619ae65..a2850ca294 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -26,7 +26,6 @@ from litellm.proxy._types import * from litellm.proxy.auth.auth_checks import ( _cache_key_object, _get_user_role, - _handle_failed_db_connection_for_get_key_object, _is_user_proxy_admin, _virtual_key_max_budget_check, _virtual_key_soft_budget_check, @@ -38,6 +37,7 @@ from litellm.proxy.auth.auth_checks import ( get_user_object, is_valid_fallback_model, ) +from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler from litellm.proxy.auth.auth_utils import ( _get_request_ip_address, get_end_user_id_from_request_body, @@ -675,9 +675,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 if ( prisma_client is None ): # if both master key + user key submitted, and user key != master key, and no db connected, raise an error - return await _handle_failed_db_connection_for_get_key_object( - e=Exception("No connected db.") - ) + raise Exception("No connected db.") ## check for cache hit (In-Memory Cache) _user_role = None @@ -1018,55 +1016,14 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 else: raise Exception() except Exception as e: - requester_ip = _get_request_ip_address( + return await UserAPIKeyAuthExceptionHandler._handle_authentication_error( + e=e, request=request, - use_x_forwarded_for=general_settings.get("use_x_forwarded_for", False), - ) - verbose_proxy_logger.exception( - "litellm.proxy.proxy_server.user_api_key_auth(): Exception occured - {}\nRequester IP Address:{}".format( - str(e), - requester_ip, - ), - extra={"requester_ip": requester_ip}, - ) - - # Log this exception to OTEL, Datadog etc - user_api_key_dict = UserAPIKeyAuth( + request_data=request_data, + route=route, parent_otel_span=parent_otel_span, api_key=api_key, ) - asyncio.create_task( - proxy_logging_obj.post_call_failure_hook( - request_data=request_data, - original_exception=e, - user_api_key_dict=user_api_key_dict, - error_type=ProxyErrorTypes.auth_error, - route=route, - ) - ) - - if isinstance(e, litellm.BudgetExceededError): - raise ProxyException( - message=e.message, - type=ProxyErrorTypes.budget_exceeded, - param=None, - code=400, - ) - if isinstance(e, HTTPException): - raise ProxyException( - message=getattr(e, "detail", f"Authentication Error({str(e)})"), - type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_401_UNAUTHORIZED), - ) - elif isinstance(e, ProxyException): - raise e - raise ProxyException( - message="Authentication Error, " + str(e), - type=ProxyErrorTypes.auth_error, - param=getattr(e, "param", "None"), - code=status.HTTP_401_UNAUTHORIZED, - ) @tracer.wrap() From 59040167ac7dd3e1864f76a51a8b29dff6ef96b0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 14:40:11 -0700 Subject: [PATCH 016/244] fix ProxyErrorTypes --- litellm/proxy/_types.py | 53 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 220a0d5ddb..54612868cc 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2067,16 +2067,64 @@ class SpendCalculateRequest(LiteLLMPydanticObjectBase): class ProxyErrorTypes(str, enum.Enum): budget_exceeded = "budget_exceeded" + """ + Object was over budget + """ + + token_not_found_in_db = "token_not_found_in_db" + """ + Requested token was not found in the database + """ + key_model_access_denied = "key_model_access_denied" + """ + Key does not have access to the model + """ + team_model_access_denied = "team_model_access_denied" + """ + Team does not have access to the model + """ + user_model_access_denied = "user_model_access_denied" + """ + User does not have access to the model + """ + expired_key = "expired_key" + """ + Key has expired + """ + auth_error = "auth_error" + """ + General authentication error + """ + internal_server_error = "internal_server_error" + """ + Internal server error + """ + bad_request_error = "bad_request_error" + """ + Bad request error + """ + not_found_error = "not_found_error" - validation_error = "bad_request_error" + """ + Not found error + """ + + validation_error = "validation_error" + """ + Validation error + """ + cache_ping_error = "cache_ping_error" + """ + Cache ping error + """ @classmethod def get_model_access_error_type_for_object( @@ -2093,9 +2141,6 @@ class ProxyErrorTypes(str, enum.Enum): return cls.user_model_access_denied -DB_CONNECTION_ERROR_TYPES = (httpx.ConnectError, httpx.ReadError, httpx.ReadTimeout) - - class SSOUserDefinedValues(TypedDict): models: List[str] user_id: str From ce49e2721789cfaa4a236103cef90742f7e51bfb Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 15:44:13 -0700 Subject: [PATCH 017/244] fixes for auth checks --- litellm/proxy/_types.py | 7 +++ litellm/proxy/auth/auth_checks.py | 47 +++++++++--------- litellm/proxy/auth/auth_exception_handler.py | 23 ++++++++- litellm/proxy/auth/user_api_key_auth.py | 50 ++++++++------------ 4 files changed, 71 insertions(+), 56 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 54612868cc..c0fb0750eb 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2141,6 +2141,13 @@ class ProxyErrorTypes(str, enum.Enum): return cls.user_model_access_denied +DB_CONNECTION_ERROR_TYPES = ( + httpx.ConnectError, + httpx.ReadError, + httpx.ReadTimeout, +) + + class SSOUserDefinedValues(TypedDict): models: List[str] user_id: str diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 98685e1a7c..9e71147706 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -987,33 +987,34 @@ async def get_key_object( ) # else, check db - try: - _valid_token: Optional[BaseModel] = await prisma_client.get_data( - token=hashed_token, - table_name="combined_view", - parent_otel_span=parent_otel_span, - proxy_logging_obj=proxy_logging_obj, + _valid_token: Optional[BaseModel] = await prisma_client.get_data( + token=hashed_token, + table_name="combined_view", + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + ) + + if _valid_token is None: + raise ProxyException( + message="Key doesn't exist in db. key={}. Create key via `/key/generate` call.".format( + hashed_token + ), + type=ProxyErrorTypes.token_not_found_in_db, + param="key", + code=status.HTTP_401_UNAUTHORIZED, ) - if _valid_token is None: - raise Exception + _response = UserAPIKeyAuth(**_valid_token.model_dump(exclude_none=True)) - _response = UserAPIKeyAuth(**_valid_token.model_dump(exclude_none=True)) + # save the key object to cache + await _cache_key_object( + hashed_token=hashed_token, + user_api_key_obj=_response, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) - # save the key object to cache - await _cache_key_object( - hashed_token=hashed_token, - user_api_key_obj=_response, - user_api_key_cache=user_api_key_cache, - proxy_logging_obj=proxy_logging_obj, - ) - - return _response - except Exception: - traceback.print_exc() - raise Exception( - f"Key doesn't exist in db. key={hashed_token}. Create key via `/key/generate` call." - ) + return _response @log_db_metrics diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py index 88a94fd5b9..5b314ef931 100644 --- a/litellm/proxy/auth/auth_exception_handler.py +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -9,7 +9,12 @@ from fastapi import HTTPException, Request, status import litellm from litellm._logging import verbose_proxy_logger -from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth +from litellm.proxy._types import ( + DB_CONNECTION_ERROR_TYPES, + ProxyErrorTypes, + ProxyException, + UserAPIKeyAuth, +) from litellm.proxy.auth.auth_utils import _get_request_ip_address from litellm.types.services import ServiceTypes @@ -52,7 +57,10 @@ class UserAPIKeyAuthExceptionHandler: proxy_logging_obj, ) - if UserAPIKeyAuthExceptionHandler.should_allow_request_on_db_unavailable(): + if ( + UserAPIKeyAuthExceptionHandler.should_allow_request_on_db_unavailable() + and UserAPIKeyAuthExceptionHandler.is_database_connection_error(e) + ): # log this as a DB failure on prometheus proxy_logging_obj.service_logging_obj.service_failure_hook( service=ServiceTypes.DB, @@ -128,3 +136,14 @@ class UserAPIKeyAuthExceptionHandler: if general_settings.get("allow_requests_on_db_unavailable", False) is True: return True return False + + @staticmethod + def is_database_connection_error(e: Exception) -> bool: + """ + Returns True if the exception is from a database outage / connection error + """ + import prisma + + return isinstance(e, DB_CONNECTION_ERROR_TYPES) or isinstance( + e, prisma.errors.PrismaError + ) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index a2850ca294..06fa560866 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -683,37 +683,25 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 api_key = hash_token(token=api_key) if valid_token is None: - try: - valid_token = await get_key_object( - hashed_token=api_key, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - parent_otel_span=parent_otel_span, - proxy_logging_obj=proxy_logging_obj, - ) - # update end-user params on valid token - # These can change per request - it's important to update them here - valid_token.end_user_id = end_user_params.get("end_user_id") - valid_token.end_user_tpm_limit = end_user_params.get( - "end_user_tpm_limit" - ) - valid_token.end_user_rpm_limit = end_user_params.get( - "end_user_rpm_limit" - ) - valid_token.allowed_model_region = end_user_params.get( - "allowed_model_region" - ) - # update key budget with temp budget increase - valid_token = _update_key_budget_with_temp_budget_increase( - valid_token - ) # updating it here, allows all downstream reporting / checks to use the updated budget - except Exception: - verbose_logger.info( - "litellm.proxy.auth.user_api_key_auth.py::user_api_key_auth() - Unable to find token={} in cache or `LiteLLM_VerificationTokenTable`. Defaulting 'valid_token' to None'".format( - api_key - ) - ) - valid_token = None + valid_token = await get_key_object( + hashed_token=api_key, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + parent_otel_span=parent_otel_span, + proxy_logging_obj=proxy_logging_obj, + ) + # update end-user params on valid token + # These can change per request - it's important to update them here + valid_token.end_user_id = end_user_params.get("end_user_id") + valid_token.end_user_tpm_limit = end_user_params.get("end_user_tpm_limit") + valid_token.end_user_rpm_limit = end_user_params.get("end_user_rpm_limit") + valid_token.allowed_model_region = end_user_params.get( + "allowed_model_region" + ) + # update key budget with temp budget increase + valid_token = _update_key_budget_with_temp_budget_increase( + valid_token + ) # updating it here, allows all downstream reporting / checks to use the updated budget if valid_token is None: raise Exception( From c6d5793bf6077ac94b7884ee1fde97521ac6ab52 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 17:50:27 -0700 Subject: [PATCH 018/244] add toxi proxy tests to ci/cd --- .circleci/config.yml | 63 ++++++++++++ .../setup_toxi_proxy.py | 96 +++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tests/proxy_reliability_tests/setup_toxi_proxy.py diff --git a/.circleci/config.yml b/.circleci/config.yml index b93a9d81e8..87432c7a24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -470,6 +470,61 @@ jobs: paths: - litellm_proxy_security_tests_coverage.xml - litellm_proxy_security_tests_coverage + litellm_proxy_reliability_tests: + docker: + - image: cimg/python:3.11 + auth: + username: ${DOCKERHUB_USERNAME} + password: ${DOCKERHUB_PASSWORD} + working_directory: ~/project + steps: + - checkout + - run: + name: Show git commit hash + command: | + echo "Git commit hash: $CIRCLE_SHA1" + - run: + name: Install Dependencies + command: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt + pip install "requests==2.31.0" + pip install "pytest==7.3.1" + pip install "pytest-retry==1.6.3" + pip install "pytest-asyncio==0.21.1" + pip install "pytest-cov==5.0.0" + - run: + name: Setup Toxiproxy + command: | + python tests/proxy_reliability_tests/setup_toxiproxy.py + - run: + name: Run prisma ./docker/entrypoint.sh + command: | + set +e + chmod +x docker/entrypoint.sh + ./docker/entrypoint.sh + set -e + # Run pytest and generate JUnit XML report + - run: + name: Run tests + command: | + pwd + ls + python -m pytest tests/proxy_security_tests --cov=litellm --cov-report=xml -vv -x -v --junitxml=test-results/junit.xml --durations=5 + no_output_timeout: 120m + - run: + name: Rename the coverage files + command: | + mv coverage.xml litellm_proxy_security_tests_coverage.xml + mv .coverage litellm_proxy_security_tests_coverage + # Store test results + - store_test_results: + path: test-results + - persist_to_workspace: + root: . + paths: + - litellm_proxy_security_tests_coverage.xml + - litellm_proxy_security_tests_coverage litellm_proxy_unit_testing: # Runs all tests with the "proxy", "key", "jwt" filenames docker: - image: cimg/python:3.11 @@ -2451,6 +2506,12 @@ workflows: only: - main - /litellm_.*/ + - litellm_proxy_reliability_tests: + filters: + branches: + only: + - main + - /litellm_.*/ - litellm_assistants_api_testing: filters: branches: @@ -2592,6 +2653,7 @@ workflows: - caching_unit_tests - litellm_proxy_unit_testing - litellm_proxy_security_tests + - litellm_proxy_reliability_tests - langfuse_logging_unit_tests - local_testing - litellm_assistants_api_testing @@ -2657,6 +2719,7 @@ workflows: - e2e_ui_testing - litellm_proxy_unit_testing - litellm_proxy_security_tests + - litellm_proxy_reliability_tests - installing_litellm_on_python - installing_litellm_on_python_3_13 - proxy_logging_guardrails_model_info_tests diff --git a/tests/proxy_reliability_tests/setup_toxi_proxy.py b/tests/proxy_reliability_tests/setup_toxi_proxy.py new file mode 100644 index 0000000000..3625b0bd3a --- /dev/null +++ b/tests/proxy_reliability_tests/setup_toxi_proxy.py @@ -0,0 +1,96 @@ +import os +import platform +import subprocess +import time +import requests +import stat + + +def download_toxiproxy(): + # Determine system architecture and OS + system = platform.system().lower() + machine = platform.machine().lower() + + # Map architecture names + arch_map = { + "x86_64": "amd64", + "amd64": "amd64", + "arm64": "arm64", + "aarch64": "arm64", + } + + arch = arch_map.get(machine, machine) + + # Construct download URL (using latest version 2.5.0) + base_url = "https://github.com/Shopify/toxiproxy/releases/download/v2.5.0/" + if system == "linux": + filename = f"toxiproxy-server-linux-{arch}" + cli_filename = f"toxiproxy-cli-linux-{arch}" + elif system == "darwin": + filename = f"toxiproxy-server-darwin-{arch}" + cli_filename = f"toxiproxy-cli-darwin-{arch}" + else: + raise Exception("Unsupported operating system") + + # Download server + response = requests.get(f"{base_url}{filename}") + with open("toxiproxy-server", "wb") as f: + f.write(response.content) + + # Download CLI + response = requests.get(f"{base_url}{cli_filename}") + with open("toxiproxy-cli", "wb") as f: + f.write(response.content) + + # Make files executable + os.chmod("toxiproxy-server", stat.S_IRWXU) + os.chmod("toxiproxy-cli", stat.S_IRWXU) + + +def setup_toxiproxy(): + # Start toxiproxy-server + server_process = subprocess.Popen(["./toxiproxy-server"]) + + # Wait for server to start + time.sleep(2) + + # Create proxy + subprocess.run( + [ + "./toxiproxy-cli", + "create", + "postgres_proxy", + "-l", + "127.0.0.1:6666", + "-u", + "ep-dry-paper-a69g2y1q-pooler.us-west-2.aws.neon.tech:5432", + ] + ) + + return server_process + + +def main(): + try: + # Download ToxiProxy binaries + download_toxiproxy() + + # Setup ToxiProxy + server_process = setup_toxiproxy() + + print("ToxiProxy setup completed successfully!") + print("Proxy 'postgres_proxy' created and listening on 127.0.0.1:6666") + + # Keep the script running to maintain the proxy + try: + server_process.wait() + except KeyboardInterrupt: + server_process.terminate() + print("\nToxiProxy server stopped") + + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + main() From 7b09d88680430d2fe5fd79c0f57e6079fa08113b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 17:52:12 -0700 Subject: [PATCH 019/244] fix setup --- tests/proxy_reliability_tests/setup_toxi_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/proxy_reliability_tests/setup_toxi_proxy.py b/tests/proxy_reliability_tests/setup_toxi_proxy.py index 3625b0bd3a..1338445f3a 100644 --- a/tests/proxy_reliability_tests/setup_toxi_proxy.py +++ b/tests/proxy_reliability_tests/setup_toxi_proxy.py @@ -59,11 +59,11 @@ def setup_toxiproxy(): [ "./toxiproxy-cli", "create", - "postgres_proxy", "-l", "127.0.0.1:6666", "-u", "ep-dry-paper-a69g2y1q-pooler.us-west-2.aws.neon.tech:5432", + "postgres_proxy", ] ) From 34c3825d1306c095730bdf1dd52f1e9c25c2b917 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 17:53:30 -0700 Subject: [PATCH 020/244] fix path --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 87432c7a24..ea887bf9de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -496,7 +496,7 @@ jobs: - run: name: Setup Toxiproxy command: | - python tests/proxy_reliability_tests/setup_toxiproxy.py + python tests/proxy_reliability_tests/setup_toxi_proxy.py - run: name: Run prisma ./docker/entrypoint.sh command: | From 6cd6ff801fb1e25bbef6f7d574871668761b9ab7 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Mar 2025 17:55:59 -0700 Subject: [PATCH 021/244] ci(publish-migrations.yml): add action for publishing prisma db migrations (#9537) --- .github/workflows/publish-migrations.yml | 103 +++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/publish-migrations.yml diff --git a/.github/workflows/publish-migrations.yml b/.github/workflows/publish-migrations.yml new file mode 100644 index 0000000000..098bb59287 --- /dev/null +++ b/.github/workflows/publish-migrations.yml @@ -0,0 +1,103 @@ +name: Publish Prisma Migrations + +on: + push: + paths: + - 'schema.prisma' # Check root schema.prisma + branches: + - main + +jobs: + publish-migrations: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:14 + env: + POSTGRES_DB: temp_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install Dependencies + run: | + pip install prisma + pip install python-dotenv + + - name: Compare and Generate Migration + env: + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + DIRECT_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + run: | + # Create temporary migration workspace + mkdir -p temp_migrations + + # Copy existing migrations to temp workspace + cp -r deploy/migrations/* temp_migrations/ + + # Generate new migration by comparing against existing state + VERSION=$(date +%Y%m%d%H%M%S) + + # Use prisma migrate diff to generate the migration + prisma migrate diff \ + --from-migrations temp_migrations \ + --to-schema-datamodel schema.prisma \ + --script > temp_migrations/migration_${VERSION}.sql + + # Check if there are actual changes + if [ -s temp_migrations/migration_${VERSION}.sql ]; then + echo "Changes detected, creating new migration" + + # Create new migration directory + mkdir -p deploy/migrations/${VERSION}_schema_update + + # Move the migration file + mv temp_migrations/migration_${VERSION}.sql deploy/migrations/${VERSION}_schema_update/migration.sql + + # Create README with timestamp + echo "Migration generated at $(date -u)" > deploy/migrations/${VERSION}_schema_update/README.md + else + echo "No schema changes detected" + exit 0 + fi + + - name: Verify Migration + if: success() + env: + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + DIRECT_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + run: | + # Apply all migrations in order to verify + for migration in deploy/migrations/*/migration.sql; do + echo "Applying migration: $migration" + psql $DATABASE_URL -f $migration + done + + - name: Create Pull Request + if: success() + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "chore: update prisma migrations" + title: "Update Prisma Migrations" + body: | + Auto-generated migration based on schema.prisma changes. + + Generated files: + - deploy/migrations/${VERSION}_schema_update/migration.sql + - deploy/migrations/${VERSION}_schema_update/README.md + branch: feat/prisma-migration-${{ env.VERSION }} + base: main \ No newline at end of file From 53a586e87626c8816ad8febde009e53be5890c09 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 17:59:38 -0700 Subject: [PATCH 022/244] TOXI_PROXY_DATABASE_URL --- .circleci/config.yml | 75 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ea887bf9de..15eacf9f51 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -471,47 +471,88 @@ jobs: - litellm_proxy_security_tests_coverage.xml - litellm_proxy_security_tests_coverage litellm_proxy_reliability_tests: - docker: - - image: cimg/python:3.11 - auth: - username: ${DOCKERHUB_USERNAME} - password: ${DOCKERHUB_PASSWORD} + machine: + image: ubuntu-2204:2023.10.1 + resource_class: xlarge working_directory: ~/project steps: - checkout - run: - name: Show git commit hash + name: Install Docker CLI (In case it's not already installed) command: | - echo "Git commit hash: $CIRCLE_SHA1" + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io + - run: + name: Install Python 3.9 + command: | + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh + bash miniconda.sh -b -p $HOME/miniconda + export PATH="$HOME/miniconda/bin:$PATH" + conda init bash + source ~/.bashrc + conda create -n myenv python=3.9 -y + conda activate myenv + python --version - run: name: Install Dependencies command: | + pip install "pytest==7.3.1" + pip install "pytest-asyncio==0.21.1" + pip install aiohttp python -m pip install --upgrade pip python -m pip install -r requirements.txt - pip install "requests==2.31.0" pip install "pytest==7.3.1" pip install "pytest-retry==1.6.3" + pip install "pytest-mock==3.12.0" pip install "pytest-asyncio==0.21.1" - pip install "pytest-cov==5.0.0" + pip install "assemblyai==0.37.0" - run: name: Setup Toxiproxy command: | python tests/proxy_reliability_tests/setup_toxi_proxy.py - run: - name: Run prisma ./docker/entrypoint.sh + name: Build Docker image + command: docker build -t my-app:latest -f ./docker/Dockerfile.database . + - run: + name: Run Docker container + # intentionally give bad redis credentials here + # the OTEL test - should get this as a trace command: | - set +e - chmod +x docker/entrypoint.sh - ./docker/entrypoint.sh - set -e - # Run pytest and generate JUnit XML report + docker run -d \ + -p 4000:4000 \ + -e DATABASE_URL=$TOXI_PROXY_DATABASE_URL \ + -e STORE_MODEL_IN_DB="True" \ + -e LITELLM_MASTER_KEY="sk-1234" \ + -e LITELLM_LICENSE=$LITELLM_LICENSE \ + --name my-app \ + -v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \ + my-app:latest \ + --config /app/config.yaml \ + --port 4000 \ + --detailed_debug \ + - run: + name: Install curl and dockerize + command: | + sudo apt-get update + sudo apt-get install -y curl + sudo wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz + sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz + sudo rm dockerize-linux-amd64-v0.6.1.tar.gz + - run: + name: Start outputting logs + command: docker logs -f my-app + background: true + - run: + name: Wait for app to be ready + command: dockerize -wait http://localhost:4000 -timeout 5m - run: name: Run tests command: | pwd ls - python -m pytest tests/proxy_security_tests --cov=litellm --cov-report=xml -vv -x -v --junitxml=test-results/junit.xml --durations=5 - no_output_timeout: 120m + python -m pytest -vv tests/store_model_in_db_tests -x --junitxml=test-results/junit.xml --durations=5 + no_output_timeout: + 120m - run: name: Rename the coverage files command: | From bf7241abd1cb6d92899df8796fbdb149df01af72 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 18:02:01 -0700 Subject: [PATCH 023/244] litellm_proxy_reliability_tests --- .circleci/config.yml | 193 ++++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 15eacf9f51..61f98405a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -470,102 +470,6 @@ jobs: paths: - litellm_proxy_security_tests_coverage.xml - litellm_proxy_security_tests_coverage - litellm_proxy_reliability_tests: - machine: - image: ubuntu-2204:2023.10.1 - resource_class: xlarge - working_directory: ~/project - steps: - - checkout - - run: - name: Install Docker CLI (In case it's not already installed) - command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io - - run: - name: Install Python 3.9 - command: | - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - conda init bash - source ~/.bashrc - conda create -n myenv python=3.9 -y - conda activate myenv - python --version - - run: - name: Install Dependencies - command: | - pip install "pytest==7.3.1" - pip install "pytest-asyncio==0.21.1" - pip install aiohttp - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - pip install "pytest==7.3.1" - pip install "pytest-retry==1.6.3" - pip install "pytest-mock==3.12.0" - pip install "pytest-asyncio==0.21.1" - pip install "assemblyai==0.37.0" - - run: - name: Setup Toxiproxy - command: | - python tests/proxy_reliability_tests/setup_toxi_proxy.py - - run: - name: Build Docker image - command: docker build -t my-app:latest -f ./docker/Dockerfile.database . - - run: - name: Run Docker container - # intentionally give bad redis credentials here - # the OTEL test - should get this as a trace - command: | - docker run -d \ - -p 4000:4000 \ - -e DATABASE_URL=$TOXI_PROXY_DATABASE_URL \ - -e STORE_MODEL_IN_DB="True" \ - -e LITELLM_MASTER_KEY="sk-1234" \ - -e LITELLM_LICENSE=$LITELLM_LICENSE \ - --name my-app \ - -v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \ - my-app:latest \ - --config /app/config.yaml \ - --port 4000 \ - --detailed_debug \ - - run: - name: Install curl and dockerize - command: | - sudo apt-get update - sudo apt-get install -y curl - sudo wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz - sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz - sudo rm dockerize-linux-amd64-v0.6.1.tar.gz - - run: - name: Start outputting logs - command: docker logs -f my-app - background: true - - run: - name: Wait for app to be ready - command: dockerize -wait http://localhost:4000 -timeout 5m - - run: - name: Run tests - command: | - pwd - ls - python -m pytest -vv tests/store_model_in_db_tests -x --junitxml=test-results/junit.xml --durations=5 - no_output_timeout: - 120m - - run: - name: Rename the coverage files - command: | - mv coverage.xml litellm_proxy_security_tests_coverage.xml - mv .coverage litellm_proxy_security_tests_coverage - # Store test results - - store_test_results: - path: test-results - - persist_to_workspace: - root: . - paths: - - litellm_proxy_security_tests_coverage.xml - - litellm_proxy_security_tests_coverage litellm_proxy_unit_testing: # Runs all tests with the "proxy", "key", "jwt" filenames docker: - image: cimg/python:3.11 @@ -1984,6 +1888,103 @@ jobs: no_output_timeout: 120m # Clean up first container + litellm_proxy_reliability_tests: + machine: + image: ubuntu-2204:2023.10.1 + resource_class: xlarge + working_directory: ~/project + steps: + - checkout + - run: + name: Install Docker CLI (In case it's not already installed) + command: | + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io + - run: + name: Install Python 3.9 + command: | + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh + bash miniconda.sh -b -p $HOME/miniconda + export PATH="$HOME/miniconda/bin:$PATH" + conda init bash + source ~/.bashrc + conda create -n myenv python=3.9 -y + conda activate myenv + python --version + - run: + name: Install Dependencies + command: | + pip install "pytest==7.3.1" + pip install "pytest-asyncio==0.21.1" + pip install aiohttp + python -m pip install --upgrade pip + python -m pip install -r requirements.txt + pip install "pytest==7.3.1" + pip install "pytest-retry==1.6.3" + pip install "pytest-mock==3.12.0" + pip install "pytest-asyncio==0.21.1" + pip install "assemblyai==0.37.0" + - run: + name: Setup Toxiproxy + command: | + python tests/proxy_reliability_tests/setup_toxi_proxy.py + - run: + name: Build Docker image + command: docker build -t my-app:latest -f ./docker/Dockerfile.database . + - run: + name: Run Docker container + # intentionally give bad redis credentials here + # the OTEL test - should get this as a trace + command: | + docker run -d \ + -p 4000:4000 \ + -e DATABASE_URL=$TOXI_PROXY_DATABASE_URL \ + -e STORE_MODEL_IN_DB="True" \ + -e LITELLM_MASTER_KEY="sk-1234" \ + -e LITELLM_LICENSE=$LITELLM_LICENSE \ + --name my-app \ + -v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \ + my-app:latest \ + --config /app/config.yaml \ + --port 4000 \ + --detailed_debug \ + - run: + name: Install curl and dockerize + command: | + sudo apt-get update + sudo apt-get install -y curl + sudo wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz + sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz + sudo rm dockerize-linux-amd64-v0.6.1.tar.gz + - run: + name: Start outputting logs + command: docker logs -f my-app + background: true + - run: + name: Wait for app to be ready + command: dockerize -wait http://localhost:4000 -timeout 5m + - run: + name: Run tests + command: | + pwd + ls + python -m pytest -vv tests/store_model_in_db_tests -x --junitxml=test-results/junit.xml --durations=5 + no_output_timeout: + 120m + - run: + name: Rename the coverage files + command: | + mv coverage.xml litellm_proxy_security_tests_coverage.xml + mv .coverage litellm_proxy_security_tests_coverage + # Store test results + - store_test_results: + path: test-results + - persist_to_workspace: + root: . + paths: + - litellm_proxy_security_tests_coverage.xml + - litellm_proxy_security_tests_coverage + proxy_build_from_pip_tests: # Change from docker to machine executor From 83b41f95e769a1f19d33d1b8ca934311153e6dd5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 18:05:41 -0700 Subject: [PATCH 024/244] Setup Toxiproxy --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61f98405a7..b7dc08bced 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1927,7 +1927,8 @@ jobs: - run: name: Setup Toxiproxy command: | - python tests/proxy_reliability_tests/setup_toxi_proxy.py + python tests/proxy_reliability_tests/setup_toxi_proxy.py & + sleep 5 - run: name: Build Docker image command: docker build -t my-app:latest -f ./docker/Dockerfile.database . From 6f138c79a744cb1f9ea7f44089968f132e737580 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 18:19:11 -0700 Subject: [PATCH 025/244] run toxi proxy tests --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b7dc08bced..2577862036 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1934,8 +1934,6 @@ jobs: command: docker build -t my-app:latest -f ./docker/Dockerfile.database . - run: name: Run Docker container - # intentionally give bad redis credentials here - # the OTEL test - should get this as a trace command: | docker run -d \ -p 4000:4000 \ From 9e2d230339c001b894956a841689bf115176927e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 18:23:52 -0700 Subject: [PATCH 026/244] litellm_proxy_reliability_tests --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2577862036..8e376b6dc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2552,7 +2552,8 @@ workflows: branches: only: - main - - /litellm_.*/ + - /litellm_stability_.*/ + - litellm_stable_release_branch - litellm_assistants_api_testing: filters: branches: From 53d9e33e782da6051418eff380e6b507fcafa329 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 18:59:26 -0700 Subject: [PATCH 027/244] fix setup toxi proxy --- .circleci/config.yml | 1 + tests/proxy_reliability_tests/setup_toxi_proxy.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e376b6dc2..04799ddd75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1942,6 +1942,7 @@ jobs: -e LITELLM_MASTER_KEY="sk-1234" \ -e LITELLM_LICENSE=$LITELLM_LICENSE \ --name my-app \ + --network=host \ -v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \ my-app:latest \ --config /app/config.yaml \ diff --git a/tests/proxy_reliability_tests/setup_toxi_proxy.py b/tests/proxy_reliability_tests/setup_toxi_proxy.py index 1338445f3a..b44298aca5 100644 --- a/tests/proxy_reliability_tests/setup_toxi_proxy.py +++ b/tests/proxy_reliability_tests/setup_toxi_proxy.py @@ -60,7 +60,7 @@ def setup_toxiproxy(): "./toxiproxy-cli", "create", "-l", - "127.0.0.1:6666", + "0.0.0.0:6666", "-u", "ep-dry-paper-a69g2y1q-pooler.us-west-2.aws.neon.tech:5432", "postgres_proxy", From 438655858210efc9cbc9d3d594bbe66494c92c66 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 19:11:13 -0700 Subject: [PATCH 028/244] litellm_proxy_reliability_tests --- .circleci/config.yml | 137 ++++++++++++------------------------------- 1 file changed, 39 insertions(+), 98 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04799ddd75..e4d6af8f0e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -686,6 +686,44 @@ jobs: paths: - llm_translation_coverage.xml - llm_translation_coverage + litellm_proxy_reliability_tests: + docker: + - image: cimg/python:3.11 + auth: + username: ${DOCKERHUB_USERNAME} + password: ${DOCKERHUB_PASSWORD} + working_directory: ~/project + + steps: + - checkout + - run: + name: Install Dependencies + command: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt + pip install "pytest==7.3.1" + pip install "pytest-retry==1.6.3" + pip install "pytest-cov==5.0.0" + pip install "pytest-asyncio==0.21.1" + pip install "respx==0.21.1" + # Run pytest and generate JUnit XML report + - run: + name: Setup Toxiproxy + command: | + python tests/proxy_reliability_tests/setup_toxi_proxy.py & + sleep 5 + - run: + name: Run tests + command: | + pwd + ls + python -m pytest -vv tests/proxy_reliability_tests --cov=litellm --cov-report=xml -x -s -v --junitxml=test-results/junit.xml --durations=5 + no_output_timeout: 120m + - run: + name: Rename the coverage files + command: | + mv coverage.xml litellm_proxy_reliability_tests_coverage.xml + mv .coverage litellm_proxy_reliability_tests_coverage mcp_testing: docker: - image: cimg/python:3.11 @@ -1888,104 +1926,7 @@ jobs: no_output_timeout: 120m # Clean up first container - litellm_proxy_reliability_tests: - machine: - image: ubuntu-2204:2023.10.1 - resource_class: xlarge - working_directory: ~/project - steps: - - checkout - - run: - name: Install Docker CLI (In case it's not already installed) - command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io - - run: - name: Install Python 3.9 - command: | - curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - conda init bash - source ~/.bashrc - conda create -n myenv python=3.9 -y - conda activate myenv - python --version - - run: - name: Install Dependencies - command: | - pip install "pytest==7.3.1" - pip install "pytest-asyncio==0.21.1" - pip install aiohttp - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - pip install "pytest==7.3.1" - pip install "pytest-retry==1.6.3" - pip install "pytest-mock==3.12.0" - pip install "pytest-asyncio==0.21.1" - pip install "assemblyai==0.37.0" - - run: - name: Setup Toxiproxy - command: | - python tests/proxy_reliability_tests/setup_toxi_proxy.py & - sleep 5 - - run: - name: Build Docker image - command: docker build -t my-app:latest -f ./docker/Dockerfile.database . - - run: - name: Run Docker container - command: | - docker run -d \ - -p 4000:4000 \ - -e DATABASE_URL=$TOXI_PROXY_DATABASE_URL \ - -e STORE_MODEL_IN_DB="True" \ - -e LITELLM_MASTER_KEY="sk-1234" \ - -e LITELLM_LICENSE=$LITELLM_LICENSE \ - --name my-app \ - --network=host \ - -v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \ - my-app:latest \ - --config /app/config.yaml \ - --port 4000 \ - --detailed_debug \ - - run: - name: Install curl and dockerize - command: | - sudo apt-get update - sudo apt-get install -y curl - sudo wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz - sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz - sudo rm dockerize-linux-amd64-v0.6.1.tar.gz - - run: - name: Start outputting logs - command: docker logs -f my-app - background: true - - run: - name: Wait for app to be ready - command: dockerize -wait http://localhost:4000 -timeout 5m - - run: - name: Run tests - command: | - pwd - ls - python -m pytest -vv tests/store_model_in_db_tests -x --junitxml=test-results/junit.xml --durations=5 - no_output_timeout: - 120m - - run: - name: Rename the coverage files - command: | - mv coverage.xml litellm_proxy_security_tests_coverage.xml - mv .coverage litellm_proxy_security_tests_coverage - # Store test results - - store_test_results: - path: test-results - - persist_to_workspace: - root: . - paths: - - litellm_proxy_security_tests_coverage.xml - - litellm_proxy_security_tests_coverage - - + proxy_build_from_pip_tests: # Change from docker to machine executor machine: From 9d10befa09f5cd16420195abaef99b9795bb9bc1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 19:16:34 -0700 Subject: [PATCH 029/244] test_litellm_proxy_server_config_no_general_settings --- .../test_using_litellm_db_outage.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/proxy_reliability_tests/test_using_litellm_db_outage.py diff --git a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py new file mode 100644 index 0000000000..745a14bdc6 --- /dev/null +++ b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py @@ -0,0 +1,74 @@ +import asyncio +import os +import subprocess +import sys +import time +import traceback + +import pytest + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +import litellm + +import os +import subprocess +import time + +import pytest +import requests + + +def test_litellm_proxy_server_config_no_general_settings(): + # Install the litellm[proxy] package + # Start the server + try: + subprocess.run(["pip", "install", "litellm[proxy]"]) + subprocess.run(["pip", "install", "litellm[extra_proxy]"]) + filepath = os.path.dirname(os.path.abspath(__file__)) + config_fp = f"{filepath}/test_configs/test_config_no_auth.yaml" + server_process = subprocess.Popen( + [ + "python", + "-m", + "litellm.proxy.proxy_cli", + "--config", + config_fp, + ] + ) + + # Allow some time for the server to start + time.sleep(60) # Adjust the sleep time if necessary + + # Send a request to the /health/liveliness endpoint + response = requests.get("http://localhost:4000/health/liveliness") + + # Check if the response is successful + assert response.status_code == 200 + assert response.json() == "I'm alive!" + + # Test /chat/completions + response = requests.post( + "http://localhost:4000/chat/completions", + headers={"Authorization": "Bearer 1234567890"}, + json={ + "model": "test_openai_models", + "messages": [{"role": "user", "content": "Hello, how are you?"}], + }, + ) + + assert response.status_code == 200 + + except ImportError: + pytest.fail("Failed to import litellm.proxy_server") + except requests.ConnectionError: + pytest.fail("Failed to connect to the server") + finally: + # Shut down the server + server_process.terminate() + server_process.wait() + + # Additional assertions can be added here + assert True From b4e745323a49e630b0b1f98bdefcce62690e2d04 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 19:21:51 -0700 Subject: [PATCH 030/244] add test config --- litellm/proxy/proxy_config.yaml | 40 +++---------------- .../test_configs/test_config.yaml | 9 +++++ .../test_using_litellm_db_outage.py | 2 +- 3 files changed, 16 insertions(+), 35 deletions(-) create mode 100644 tests/proxy_reliability_tests/test_configs/test_config.yaml diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 26ce6cb8f8..4912a35f89 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,37 +1,9 @@ model_list: - - model_name: gpt-3.5-turbo-end-user-test + - model_name: fake-openai-endpoint litellm_params: - model: azure/chatgpt-v-2 - api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ - api_version: "2023-05-15" - api_key: os.environ/AZURE_API_KEY + model: openai/fake + api_key: fake-key + api_base: https://exampleopenaiendpoint-production.up.railway.app/ - - -mcp_tools: - - name: "get_current_time" - description: "Get the current time" - input_schema: { - "type": "object", - "properties": { - "format": { - "type": "string", - "description": "The format of the time to return", - "enum": ["short"] - } - } - } - handler: "mcp_tools.get_current_time" - - name: "get_current_date" - description: "Get the current date" - input_schema: { - "type": "object", - "properties": { - "format": { - "type": "string", - "description": "The format of the date to return", - "enum": ["short"] - } - } - } - handler: "mcp_tools.get_current_date" +general_settings: + allow_requests_on_db_unavailable: True \ No newline at end of file diff --git a/tests/proxy_reliability_tests/test_configs/test_config.yaml b/tests/proxy_reliability_tests/test_configs/test_config.yaml new file mode 100644 index 0000000000..4912a35f89 --- /dev/null +++ b/tests/proxy_reliability_tests/test_configs/test_config.yaml @@ -0,0 +1,9 @@ +model_list: + - model_name: fake-openai-endpoint + litellm_params: + model: openai/fake + api_key: fake-key + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + +general_settings: + allow_requests_on_db_unavailable: True \ No newline at end of file diff --git a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py index 745a14bdc6..acaaed05f3 100644 --- a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py +++ b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py @@ -28,7 +28,7 @@ def test_litellm_proxy_server_config_no_general_settings(): subprocess.run(["pip", "install", "litellm[proxy]"]) subprocess.run(["pip", "install", "litellm[extra_proxy]"]) filepath = os.path.dirname(os.path.abspath(__file__)) - config_fp = f"{filepath}/test_configs/test_config_no_auth.yaml" + config_fp = f"{filepath}/test_configs/test_config.yaml" server_process = subprocess.Popen( [ "python", From 6572ba7a0ef9ddb24345dde179610365d237e571 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 19:25:47 -0700 Subject: [PATCH 031/244] fix startup --- .../proxy_reliability_tests/test_using_litellm_db_outage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py index acaaed05f3..52d3ec6222 100644 --- a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py +++ b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py @@ -4,7 +4,6 @@ import subprocess import sys import time import traceback - import pytest sys.path.insert( @@ -29,6 +28,10 @@ def test_litellm_proxy_server_config_no_general_settings(): subprocess.run(["pip", "install", "litellm[extra_proxy]"]) filepath = os.path.dirname(os.path.abspath(__file__)) config_fp = f"{filepath}/test_configs/test_config.yaml" + + # Set DATABASE_URL environment variable + os.environ["DATABASE_URL"] = os.getenv("TOXI_PROXY_DATABASE_URL") + server_process = subprocess.Popen( [ "python", From 0a401ee468de6a13c905c8b41c66a73b95d5b1c5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 19:27:15 -0700 Subject: [PATCH 032/244] test_litellm_proxy_server_config_no_general_settings --- .../test_using_litellm_db_outage.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py index 52d3ec6222..ea9c7e3f3a 100644 --- a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py +++ b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py @@ -19,18 +19,20 @@ import time import pytest import requests +TEST_MASTER_KEY = "sk-1234" + def test_litellm_proxy_server_config_no_general_settings(): # Install the litellm[proxy] package # Start the server try: subprocess.run(["pip", "install", "litellm[proxy]"]) - subprocess.run(["pip", "install", "litellm[extra_proxy]"]) filepath = os.path.dirname(os.path.abspath(__file__)) config_fp = f"{filepath}/test_configs/test_config.yaml" # Set DATABASE_URL environment variable os.environ["DATABASE_URL"] = os.getenv("TOXI_PROXY_DATABASE_URL") + os.environ["LITELLM_MASTER_KEY"] = TEST_MASTER_KEY server_process = subprocess.Popen( [ @@ -72,6 +74,3 @@ def test_litellm_proxy_server_config_no_general_settings(): # Shut down the server server_process.terminate() server_process.wait() - - # Additional assertions can be added here - assert True From 79ef184345a62945f991f8f0fb4e9b090446ccb7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 21:57:45 -0700 Subject: [PATCH 033/244] run ci/cd again --- litellm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index d2b5169664..a59484b035 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -2,7 +2,7 @@ import warnings warnings.filterwarnings("ignore", message=".*conflict with protected namespace.*") -### INIT VARIABLES ########## +### INIT VARIABLES ########### import threading import os from typing import Callable, List, Optional, Dict, Union, Any, Literal, get_args From 9aec7c387816aec1de8e7b56c953a07c57afc5a9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 22:08:06 -0700 Subject: [PATCH 034/244] test_create_delete_assistants --- tests/local_testing/test_assistants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/local_testing/test_assistants.py b/tests/local_testing/test_assistants.py index 273972e9dd..544523e4a0 100644 --- a/tests/local_testing/test_assistants.py +++ b/tests/local_testing/test_assistants.py @@ -67,6 +67,7 @@ async def test_get_assistants(provider, sync_mode): @pytest.mark.asyncio() @pytest.mark.flaky(retries=3, delay=1) async def test_create_delete_assistants(provider, sync_mode): + litellm.ssl_verify = False model = "gpt-4-turbo" if provider == "azure": os.environ["AZURE_API_VERSION"] = "2024-05-01-preview" From 24a329ea5b1dd4f20a56061a6e0c9450198f9ba3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 22:29:40 -0700 Subject: [PATCH 035/244] Set DNS --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b93a9d81e8..f8951ef7e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -577,6 +577,10 @@ jobs: steps: - checkout + - run: + name: Set DNS + command: | + ssh remote-docker echo "nameserver x.x.x.x" | ssh remote-docker sudo tee - run: name: Install Dependencies command: | From 61816dfd04feed1846369ea6b228ee985123a9ce Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 22:31:16 -0700 Subject: [PATCH 036/244] litellm_assistants_api_testing bump python --- .circleci/config.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f8951ef7e4..1b94afa102 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -569,7 +569,7 @@ jobs: - litellm_proxy_unit_tests_coverage litellm_assistants_api_testing: # Runs all tests with the "assistants" keyword docker: - - image: cimg/python:3.11 + - image: cimg/python:3.13.1 auth: username: ${DOCKERHUB_USERNAME} password: ${DOCKERHUB_PASSWORD} @@ -577,10 +577,6 @@ jobs: steps: - checkout - - run: - name: Set DNS - command: | - ssh remote-docker echo "nameserver x.x.x.x" | ssh remote-docker sudo tee - run: name: Install Dependencies command: | From f68cc26f1590fb6569b9a1dd1a37d99070dc5ee8 Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Tue, 25 Mar 2025 22:37:28 -0700 Subject: [PATCH 037/244] 8864 Add support for anyOf union type while handling null fields --- .gitignore | 2 +- litellm/llms/vertex_ai/common_utils.py | 43 +++++++----- .../vertex_ai/test_vertex_ai_common_utils.py | 70 +++++++++++++++++++ 3 files changed, 95 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index dab6d4ec81..a9d14852eb 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,4 @@ tests/llm_translation/test_vertex_key.json litellm/proxy/migrations/0_init/migration.sql litellm/proxy/db/migrations/0_init/migration.sql litellm/proxy/db/migrations/* -litellm/proxy/migrations/* \ No newline at end of file +litellm/proxy/migrations/*config.yaml diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index a3f91fbacc..9a01c15233 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -160,7 +160,7 @@ def _build_vertex_schema(parameters: dict): # * https://github.com/pydantic/pydantic/issues/1270 # * https://stackoverflow.com/a/58841311 # * https://github.com/pydantic/pydantic/discussions/4872 - convert_to_nullable(parameters) + convert_anyof_null_to_nullable(parameters) add_object_type(parameters) # Postprocessing # 4. Suppress unnecessary title generation: @@ -211,34 +211,39 @@ def unpack_defs(schema, defs): continue -def convert_to_nullable(schema): - anyof = schema.pop("anyOf", None) +def convert_anyof_null_to_nullable(schema): + """ Converts null objects within anyOf by removing them and adding nullable to all remaining objects """ + anyof = schema.get("anyOf", None) if anyof is not None: - if len(anyof) != 2: + contains_null = False + for atype in anyof: + if atype == {"type": "null"}: + # remove null type + anyof.remove(atype) + contains_null = True + + if len(anyof) == 0: + # Edge case: response schema with only null type present is invalid in Vertex AI raise ValueError( - "Invalid input: Type Unions are not supported, except for `Optional` types. " - "Please provide an `Optional` type or a non-Union type." + "Invalid input: AnyOf schema with only null type is not supported. " + "Please provide a non-null type." ) - a, b = anyof - if a == {"type": "null"}: - schema.update(b) - elif b == {"type": "null"}: - schema.update(a) - else: - raise ValueError( - "Invalid input: Type Unions are not supported, except for `Optional` types. " - "Please provide an `Optional` type or a non-Union type." - ) - schema["nullable"] = True + + + if contains_null: + # set all types to nullable following guidance found here: https://cloud.google.com/vertex-ai/generative-ai/docs/samples/generativeaionvertexai-gemini-controlled-generation-response-schema-3#generativeaionvertexai_gemini_controlled_generation_response_schema_3-python + for atype in anyof: + atype["nullable"] = True + properties = schema.get("properties", None) if properties is not None: for name, value in properties.items(): - convert_to_nullable(value) + convert_anyof_null_to_nullable(value) items = schema.get("items", None) if items is not None: - convert_to_nullable(items) + convert_anyof_null_to_nullable(items) def add_object_type(schema): diff --git a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index e89355443f..1c6ff65ce6 100644 --- a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -12,6 +12,7 @@ import litellm from litellm.llms.vertex_ai.common_utils import ( get_vertex_location_from_url, get_vertex_project_id_from_url, + convert_anyof_null_to_nullable ) @@ -41,3 +42,72 @@ async def test_get_vertex_location_from_url(): url = "https://invalid-url.com" location = get_vertex_location_from_url(url) assert location is None + +def test_basic_anyof_conversion(): + """Test basic conversion of anyOf with 'null'.""" + schema = { + "type": "object", + "properties": { + "example": { + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] + } + } + } + + convert_anyof_null_to_nullable(schema) + + expected = { + "type": "object", + "properties": { + "example": { + "anyOf": [ + {"type": "string", "nullable": True} + ] + } + } + } + assert schema == expected + + +def test_nested_anyof_conversion(): + """Test nested conversion with 'anyOf' inside properties.""" + schema = { + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "inner": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "string"}, + {"type": "null"} + ] + } + } + } + } + } + + convert_anyof_null_to_nullable(schema) + + expected = { + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "inner": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}, "nullable": True}, + {"type": "string", "nullable": True} + ] + } + } + } + } + } + assert schema == expected \ No newline at end of file From 3725ba4f63c0e962796a45cfcbcc664324624ea4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 22:52:31 -0700 Subject: [PATCH 038/244] setup_google_dns --- .circleci/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b94afa102..0013a09b3f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,14 @@ orbs: codecov: codecov/codecov@4.0.1 node: circleci/node@5.1.0 # Add this line to declare the node orb +commands: + setup_google_dns: + steps: + - run: + name: "Configure Google DNS" + command: | + echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf + echo "nameserver 8.8.4.4" | sudo tee -a /etc/resolv.conf jobs: local_testing: @@ -577,6 +585,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | From c473e2b1c2f7bf04bde0f83cad733098381297f7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:02:02 -0700 Subject: [PATCH 039/244] setup_google_dns --- .circleci/config.yml | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0013a09b3f..304f96bdbb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,7 +23,7 @@ jobs: steps: - checkout - + - setup_google_dns - run: name: Show git commit hash command: | @@ -142,7 +142,7 @@ jobs: steps: - checkout - + - setup_google_dns - run: name: Show git commit hash command: | @@ -242,7 +242,7 @@ jobs: steps: - checkout - + - setup_google_dns - run: name: Show git commit hash command: | @@ -342,6 +342,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -396,6 +397,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -437,6 +439,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Show git commit hash command: | @@ -487,7 +490,7 @@ jobs: working_directory: ~/project steps: - checkout - + - setup_google_dns - run: name: Show git commit hash command: | @@ -627,6 +630,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -663,6 +667,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -705,6 +710,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -749,6 +755,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -791,6 +798,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -837,6 +845,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -881,6 +890,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -927,6 +937,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -969,6 +980,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -1011,6 +1023,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -1057,6 +1070,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -1089,6 +1103,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -1113,6 +1128,7 @@ jobs: steps: - checkout + - setup_google_dns # Install Helm - run: name: Install Helm @@ -1182,6 +1198,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | @@ -1218,6 +1235,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Python 3.9 command: | @@ -1292,6 +1310,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -1427,6 +1446,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -1551,6 +1571,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -1713,6 +1734,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -1824,6 +1846,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -1906,6 +1929,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns # Remove Docker CLI installation since it's already available in machine executor - run: name: Install Python 3.13 @@ -2003,6 +2027,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Install Docker CLI (In case it's not already installed) command: | @@ -2262,6 +2287,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Build UI command: | @@ -2376,6 +2402,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Build Docker image command: | @@ -2398,6 +2425,7 @@ jobs: working_directory: ~/project steps: - checkout + - setup_google_dns - run: name: Build Docker image command: | From 24b3e80eba18168d5b5134c969f5eb802ad63b48 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Mar 2025 19:30:18 -0700 Subject: [PATCH 040/244] ci: update github action --- .github/workflows/publish-migrations.yml | 129 ++++++++++++++++++++--- 1 file changed, 116 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-migrations.yml b/.github/workflows/publish-migrations.yml index 098bb59287..8e5a67bcf8 100644 --- a/.github/workflows/publish-migrations.yml +++ b/.github/workflows/publish-migrations.yml @@ -1,5 +1,9 @@ name: Publish Prisma Migrations +permissions: + contents: write + pull-requests: write + on: push: paths: @@ -24,6 +28,21 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + + # Add shadow database service + postgres_shadow: + image: postgres:14 + env: + POSTGRES_DB: shadow_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5433:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - uses: actions/checkout@v3 @@ -38,37 +57,86 @@ jobs: pip install prisma pip install python-dotenv - - name: Compare and Generate Migration + - name: Generate Initial Migration if None Exists env: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" DIRECT_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + SHADOW_DATABASE_URL: "postgresql://postgres:postgres@localhost:5433/shadow_db" + run: | + mkdir -p deploy/migrations + echo 'provider = "postgresql"' > deploy/migrations/migration_lock.toml + + if [ -z "$(ls -A deploy/migrations/2* 2>/dev/null)" ]; then + echo "No existing migrations found, creating baseline..." + VERSION=$(date +%Y%m%d%H%M%S) + mkdir -p deploy/migrations/${VERSION}_initial + + echo "Generating initial migration..." + # Save raw output for debugging + prisma migrate diff \ + --from-empty \ + --to-schema-datamodel schema.prisma \ + --shadow-database-url "${SHADOW_DATABASE_URL}" \ + --script > deploy/migrations/${VERSION}_initial/raw_migration.sql + + echo "Raw migration file content:" + cat deploy/migrations/${VERSION}_initial/raw_migration.sql + + echo "Cleaning migration file..." + # Clean the file + sed '/^Installing/d' deploy/migrations/${VERSION}_initial/raw_migration.sql > deploy/migrations/${VERSION}_initial/migration.sql + + # Verify the migration file + if [ ! -s deploy/migrations/${VERSION}_initial/migration.sql ]; then + echo "ERROR: Migration file is empty after cleaning" + echo "Original content was:" + cat deploy/migrations/${VERSION}_initial/raw_migration.sql + exit 1 + fi + + echo "Final migration file content:" + cat deploy/migrations/${VERSION}_initial/migration.sql + + # Verify it starts with SQL + if ! head -n 1 deploy/migrations/${VERSION}_initial/migration.sql | grep -q "^--\|^CREATE\|^ALTER"; then + echo "ERROR: Migration file does not start with SQL command or comment" + echo "First line is:" + head -n 1 deploy/migrations/${VERSION}_initial/migration.sql + echo "Full content is:" + cat deploy/migrations/${VERSION}_initial/migration.sql + exit 1 + fi + + echo "Initial migration generated at $(date -u)" > deploy/migrations/${VERSION}_initial/README.md + fi + + - name: Compare and Generate Migration + if: success() + env: + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + DIRECT_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + SHADOW_DATABASE_URL: "postgresql://postgres:postgres@localhost:5433/shadow_db" run: | # Create temporary migration workspace mkdir -p temp_migrations - # Copy existing migrations to temp workspace - cp -r deploy/migrations/* temp_migrations/ + # Copy existing migrations (will not fail if directory is empty) + cp -r deploy/migrations/* temp_migrations/ 2>/dev/null || true - # Generate new migration by comparing against existing state VERSION=$(date +%Y%m%d%H%M%S) - # Use prisma migrate diff to generate the migration + # Generate diff against existing migrations or empty state prisma migrate diff \ --from-migrations temp_migrations \ --to-schema-datamodel schema.prisma \ + --shadow-database-url "${SHADOW_DATABASE_URL}" \ --script > temp_migrations/migration_${VERSION}.sql # Check if there are actual changes if [ -s temp_migrations/migration_${VERSION}.sql ]; then echo "Changes detected, creating new migration" - - # Create new migration directory mkdir -p deploy/migrations/${VERSION}_schema_update - - # Move the migration file mv temp_migrations/migration_${VERSION}.sql deploy/migrations/${VERSION}_schema_update/migration.sql - - # Create README with timestamp echo "Migration generated at $(date -u)" > deploy/migrations/${VERSION}_schema_update/README.md else echo "No schema changes detected" @@ -80,17 +148,44 @@ jobs: env: DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" DIRECT_URL: "postgresql://postgres:postgres@localhost:5432/temp_db" + SHADOW_DATABASE_URL: "postgresql://postgres:postgres@localhost:5433/shadow_db" run: | + # Create test database + psql "${SHADOW_DATABASE_URL}" -c 'CREATE DATABASE migration_test;' + # Apply all migrations in order to verify for migration in deploy/migrations/*/migration.sql; do echo "Applying migration: $migration" - psql $DATABASE_URL -f $migration + psql "${SHADOW_DATABASE_URL}" -f $migration done + # Add this step before create-pull-request to debug permissions + - name: Check Token Permissions + run: | + echo "Checking token permissions..." + curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/BerriAI/litellm/collaborators + + echo "\nChecking if token can create PRs..." + curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/BerriAI/litellm + + # Add this debug step before git push + - name: Debug Changed Files + run: | + echo "Files staged for commit:" + git diff --name-status --staged + + echo "\nAll changed files:" + git status + - name: Create Pull Request if: success() uses: peter-evans/create-pull-request@v5 with: + token: ${{ secrets.GITHUB_TOKEN }} commit-message: "chore: update prisma migrations" title: "Update Prisma Migrations" body: | @@ -100,4 +195,12 @@ jobs: - deploy/migrations/${VERSION}_schema_update/migration.sql - deploy/migrations/${VERSION}_schema_update/README.md branch: feat/prisma-migration-${{ env.VERSION }} - base: main \ No newline at end of file + base: main + delete-branch: true + + - name: Generate and Save Migrations + run: | + # Only add migration files + git add deploy/migrations/ + git status # Debug what's being committed + git commit -m "chore: update prisma migrations" From e0880734d98be93a54f630007afc8e7fef35efe2 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Mar 2025 23:10:55 -0700 Subject: [PATCH 041/244] docs(config_settings.md): cleanup docs --- docs/my-website/docs/proxy/config_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 0e85223d45..0093464d93 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -479,7 +479,7 @@ router_settings: | PROXY_ADMIN_ID | Admin identifier for proxy server | PROXY_BASE_URL | Base URL for proxy service | PROXY_LOGOUT_URL | URL for logging out of the proxy service -| PROXY_MASTER_KEY | Master key for proxy authentication +| LITELLM_MASTER_KEY | Master key for proxy authentication | QDRANT_API_BASE | Base URL for Qdrant API | QDRANT_API_KEY | API key for Qdrant service | QDRANT_URL | Connection URL for Qdrant database From 6e5d2b1ac77ef48fb4f65ef131799c8ca183bcf4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:14:44 -0700 Subject: [PATCH 042/244] handle failed db connections --- tests/proxy_unit_tests/test_auth_checks.py | 33 ---------------------- 1 file changed, 33 deletions(-) diff --git a/tests/proxy_unit_tests/test_auth_checks.py b/tests/proxy_unit_tests/test_auth_checks.py index 0eb1a38755..7695306c87 100644 --- a/tests/proxy_unit_tests/test_auth_checks.py +++ b/tests/proxy_unit_tests/test_auth_checks.py @@ -13,9 +13,6 @@ sys.path.insert( ) # Adds the parent directory to the system path import pytest, litellm import httpx -from litellm.proxy.auth.auth_checks import ( - _handle_failed_db_connection_for_get_key_object, -) from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.auth_checks import get_end_user_object from litellm.caching.caching import DualCache @@ -78,36 +75,6 @@ async def test_get_end_user_object(customer_spend, customer_budget): ) -@pytest.mark.asyncio -async def test_handle_failed_db_connection(): - """ - Test cases: - 1. When allow_requests_on_db_unavailable=True -> return UserAPIKeyAuth - 2. When allow_requests_on_db_unavailable=False -> raise original error - """ - from litellm.proxy.proxy_server import general_settings, litellm_proxy_admin_name - - # Test case 1: allow_requests_on_db_unavailable=True - general_settings["allow_requests_on_db_unavailable"] = True - mock_error = httpx.ConnectError("Failed to connect to DB") - - result = await _handle_failed_db_connection_for_get_key_object(e=mock_error) - - assert isinstance(result, UserAPIKeyAuth) - assert result.key_name == "failed-to-connect-to-db" - assert result.token == "failed-to-connect-to-db" - assert result.user_id == litellm_proxy_admin_name - - # Test case 2: allow_requests_on_db_unavailable=False - general_settings["allow_requests_on_db_unavailable"] = False - - with pytest.raises(httpx.ConnectError) as exc_info: - await _handle_failed_db_connection_for_get_key_object(e=mock_error) - print("_handle_failed_db_connection_for_get_key_object got exception", exc_info) - - assert str(exc_info.value) == "Failed to connect to DB" - - @pytest.mark.parametrize( "model, expect_to_work", [ From 8657816477ebc53e644af0dbe18d7b9e1fb3b777 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:20:43 -0700 Subject: [PATCH 043/244] fix gemini/gemini-2.0-flash-lite on model cost map --- litellm/model_prices_and_context_window_backup.json | 2 +- model_prices_and_context_window.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index a098e8bd7d..f9eb63ccca 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4700,7 +4700,7 @@ "supports_response_schema": true, "supports_audio_output": true, "supports_tool_choice": true, - "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite", + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite" }, "gemini/gemini-2.0-flash-001": { "max_tokens": 8192, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index a098e8bd7d..f9eb63ccca 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4700,7 +4700,7 @@ "supports_response_schema": true, "supports_audio_output": true, "supports_tool_choice": true, - "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite", + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite" }, "gemini/gemini-2.0-flash-001": { "max_tokens": 8192, From 6fd18651d129d606182ff4b980e95768fc43ca3d Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Mar 2025 23:46:20 -0700 Subject: [PATCH 044/244] Support `litellm.api_base` for vertex_ai + gemini/ across completion, embedding, image_generation (#9516) * test(tests): add unit testing for litellm_proxy integration * fix(cost_calculator.py): fix tracking cost in sdk when calling proxy * fix(main.py): respect litellm.api_base on `vertex_ai/` and `gemini/` routes * fix(main.py): consistently support custom api base across gemini + vertexai on embedding + completion * feat(vertex_ai/): test * fix: fix linting error * test: set api base as None before starting loadtest --- litellm/cost_calculator.py | 9 ++- litellm/llms/vertex_ai/common_utils.py | 14 +++- .../image_generation_handler.py | 81 +++++++++++-------- .../vertex_embeddings/embedding_handler.py | 2 +- litellm/main.py | 28 +++++++ litellm/proxy/_new_secret_config.yaml | 2 +- tests/litellm/test_cost_calculator.py | 6 +- .../test_litellm_proxy_provider.py | 77 +++++++++++++++++- .../test_vertex_embeddings_load_test.py | 3 +- .../test_amazing_vertex_completion.py | 44 ++++++++++ 10 files changed, 223 insertions(+), 43 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 55736772af..f5731618a3 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -828,11 +828,14 @@ def get_response_cost_from_hidden_params( _hidden_params_dict = hidden_params additional_headers = _hidden_params_dict.get("additional_headers", {}) - if additional_headers and "x-litellm-response-cost" in additional_headers: - response_cost = additional_headers["x-litellm-response-cost"] + if ( + additional_headers + and "llm_provider-x-litellm-response-cost" in additional_headers + ): + response_cost = additional_headers["llm_provider-x-litellm-response-cost"] if response_cost is None: return None - return float(additional_headers["x-litellm-response-cost"]) + return float(additional_headers["llm_provider-x-litellm-response-cost"]) return None diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index a3f91fbacc..0d792527b4 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -55,7 +55,9 @@ def get_supports_response_schema( from typing import Literal, Optional -all_gemini_url_modes = Literal["chat", "embedding", "batch_embedding"] +all_gemini_url_modes = Literal[ + "chat", "embedding", "batch_embedding", "image_generation" +] def _get_vertex_url( @@ -91,7 +93,11 @@ def _get_vertex_url( if model.isdigit(): # https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/endpoints/$ENDPOINT_ID:predict url = f"https://{vertex_location}-aiplatform.googleapis.com/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}" - + elif mode == "image_generation": + endpoint = "predict" + url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" + if model.isdigit(): + url = f"https://{vertex_location}-aiplatform.googleapis.com/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}" if not url or not endpoint: raise ValueError(f"Unable to get vertex url/endpoint for mode: {mode}") return url, endpoint @@ -127,6 +133,10 @@ def _get_gemini_url( url = "https://generativelanguage.googleapis.com/v1beta/{}:{}?key={}".format( _gemini_model_name, endpoint, gemini_api_key ) + elif mode == "image_generation": + raise ValueError( + "LiteLLM's `gemini/` route does not support image generation yet. Let us know if you need this feature by opening an issue at https://github.com/BerriAI/litellm/issues" + ) return url, endpoint diff --git a/litellm/llms/vertex_ai/image_generation/image_generation_handler.py b/litellm/llms/vertex_ai/image_generation/image_generation_handler.py index 1d5322c08d..e83f4b6f03 100644 --- a/litellm/llms/vertex_ai/image_generation/image_generation_handler.py +++ b/litellm/llms/vertex_ai/image_generation/image_generation_handler.py @@ -43,22 +43,23 @@ class VertexImageGeneration(VertexLLM): def image_generation( self, prompt: str, + api_base: Optional[str], vertex_project: Optional[str], vertex_location: Optional[str], vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], model_response: ImageResponse, logging_obj: Any, - model: Optional[ - str - ] = "imagegeneration", # vertex ai uses imagegeneration as the default model + model: str = "imagegeneration", # vertex ai uses imagegeneration as the default model client: Optional[Any] = None, optional_params: Optional[dict] = None, timeout: Optional[int] = None, aimg_generation=False, + extra_headers: Optional[dict] = None, ) -> ImageResponse: if aimg_generation is True: return self.aimage_generation( # type: ignore prompt=prompt, + api_base=api_base, vertex_project=vertex_project, vertex_location=vertex_location, vertex_credentials=vertex_credentials, @@ -83,13 +84,27 @@ class VertexImageGeneration(VertexLLM): else: sync_handler = client # type: ignore - url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:predict" + # url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:predict" + auth_header: Optional[str] = None auth_header, _ = self._ensure_access_token( credentials=vertex_credentials, project_id=vertex_project, custom_llm_provider="vertex_ai", ) + auth_header, api_base = self._get_token_and_url( + model=model, + gemini_api_key=None, + auth_header=auth_header, + vertex_project=vertex_project, + vertex_location=vertex_location, + vertex_credentials=vertex_credentials, + stream=False, + custom_llm_provider="vertex_ai", + api_base=api_base, + should_use_v1beta1_features=False, + mode="image_generation", + ) optional_params = optional_params or { "sampleCount": 1 } # default optional params @@ -99,31 +114,21 @@ class VertexImageGeneration(VertexLLM): "parameters": optional_params, } - request_str = f"\n curl -X POST \\\n -H \"Authorization: Bearer {auth_header[:10] + 'XXXXXXXXXX'}\" \\\n -H \"Content-Type: application/json; charset=utf-8\" \\\n -d {request_data} \\\n \"{url}\"" - logging_obj.pre_call( - input=prompt, - api_key=None, - additional_args={ - "complete_input_dict": optional_params, - "request_str": request_str, - }, - ) + headers = self.set_headers(auth_header=auth_header, extra_headers=extra_headers) logging_obj.pre_call( input=prompt, - api_key=None, + api_key="", additional_args={ "complete_input_dict": optional_params, - "request_str": request_str, + "api_base": api_base, + "headers": headers, }, ) response = sync_handler.post( - url=url, - headers={ - "Content-Type": "application/json; charset=utf-8", - "Authorization": f"Bearer {auth_header}", - }, + url=api_base, + headers=headers, data=json.dumps(request_data), ) @@ -138,17 +143,17 @@ class VertexImageGeneration(VertexLLM): async def aimage_generation( self, prompt: str, + api_base: Optional[str], vertex_project: Optional[str], vertex_location: Optional[str], vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], model_response: litellm.ImageResponse, logging_obj: Any, - model: Optional[ - str - ] = "imagegeneration", # vertex ai uses imagegeneration as the default model + model: str = "imagegeneration", # vertex ai uses imagegeneration as the default model client: Optional[AsyncHTTPHandler] = None, optional_params: Optional[dict] = None, timeout: Optional[int] = None, + extra_headers: Optional[dict] = None, ): response = None if client is None: @@ -169,7 +174,6 @@ class VertexImageGeneration(VertexLLM): # make POST request to # https://us-central1-aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/us-central1/publishers/google/models/imagegeneration:predict - url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:predict" """ Docs link: https://console.cloud.google.com/vertex-ai/publishers/google/model-garden/imagegeneration?project=adroit-crow-413218 @@ -188,11 +192,25 @@ class VertexImageGeneration(VertexLLM): } \ "https://us-central1-aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/us-central1/publishers/google/models/imagegeneration:predict" """ + auth_header: Optional[str] = None auth_header, _ = self._ensure_access_token( credentials=vertex_credentials, project_id=vertex_project, custom_llm_provider="vertex_ai", ) + auth_header, api_base = self._get_token_and_url( + model=model, + gemini_api_key=None, + auth_header=auth_header, + vertex_project=vertex_project, + vertex_location=vertex_location, + vertex_credentials=vertex_credentials, + stream=False, + custom_llm_provider="vertex_ai", + api_base=api_base, + should_use_v1beta1_features=False, + mode="image_generation", + ) optional_params = optional_params or { "sampleCount": 1 } # default optional params @@ -202,22 +220,21 @@ class VertexImageGeneration(VertexLLM): "parameters": optional_params, } - request_str = f"\n curl -X POST \\\n -H \"Authorization: Bearer {auth_header[:10] + 'XXXXXXXXXX'}\" \\\n -H \"Content-Type: application/json; charset=utf-8\" \\\n -d {request_data} \\\n \"{url}\"" + headers = self.set_headers(auth_header=auth_header, extra_headers=extra_headers) + logging_obj.pre_call( input=prompt, - api_key=None, + api_key="", additional_args={ "complete_input_dict": optional_params, - "request_str": request_str, + "api_base": api_base, + "headers": headers, }, ) response = await self.async_handler.post( - url=url, - headers={ - "Content-Type": "application/json; charset=utf-8", - "Authorization": f"Bearer {auth_header}", - }, + url=api_base, + headers=headers, data=json.dumps(request_data), ) diff --git a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py index 3ef40703e8..2e8051d4d2 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py @@ -111,7 +111,7 @@ class VertexEmbedding(VertexBase): ) try: - response = client.post(api_base, headers=headers, json=vertex_request) # type: ignore + response = client.post(url=api_base, headers=headers, json=vertex_request) # type: ignore response.raise_for_status() except httpx.HTTPStatusError as err: error_code = err.response.status_code diff --git a/litellm/main.py b/litellm/main.py index 3d4152d634..94e19aab0c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2350,6 +2350,8 @@ def completion( # type: ignore # noqa: PLR0915 or litellm.api_key ) + api_base = api_base or litellm.api_base or get_secret("GEMINI_API_BASE") + new_params = deepcopy(optional_params) response = vertex_chat_completion.completion( # type: ignore model=model, @@ -2392,6 +2394,8 @@ def completion( # type: ignore # noqa: PLR0915 or get_secret("VERTEXAI_CREDENTIALS") ) + api_base = api_base or litellm.api_base or get_secret("VERTEXAI_API_BASE") + new_params = deepcopy(optional_params) if ( model.startswith("meta/") @@ -3657,6 +3661,8 @@ def embedding( # noqa: PLR0915 api_key or get_secret_str("GEMINI_API_KEY") or litellm.api_key ) + api_base = api_base or litellm.api_base or get_secret_str("GEMINI_API_BASE") + response = google_batch_embeddings.batch_embeddings( # type: ignore model=model, input=input, @@ -3671,6 +3677,8 @@ def embedding( # noqa: PLR0915 print_verbose=print_verbose, custom_llm_provider="gemini", api_key=gemini_api_key, + api_base=api_base, + client=client, ) elif custom_llm_provider == "vertex_ai": @@ -3695,6 +3703,13 @@ def embedding( # noqa: PLR0915 or get_secret_str("VERTEX_CREDENTIALS") ) + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERTEXAI_API_BASE") + or get_secret_str("VERTEX_API_BASE") + ) + if ( "image" in optional_params or "video" in optional_params @@ -3716,6 +3731,7 @@ def embedding( # noqa: PLR0915 print_verbose=print_verbose, custom_llm_provider="vertex_ai", client=client, + api_base=api_base, ) else: response = vertex_embedding.embedding( @@ -3733,6 +3749,8 @@ def embedding( # noqa: PLR0915 aembedding=aembedding, print_verbose=print_verbose, api_key=api_key, + api_base=api_base, + client=client, ) elif custom_llm_provider == "oobabooga": response = oobabooga.embedding( @@ -4695,6 +4713,14 @@ def image_generation( # noqa: PLR0915 or optional_params.pop("vertex_ai_credentials", None) or get_secret_str("VERTEXAI_CREDENTIALS") ) + + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERTEXAI_API_BASE") + or get_secret_str("VERTEX_API_BASE") + ) + model_response = vertex_image_generation.image_generation( model=model, prompt=prompt, @@ -4706,6 +4732,8 @@ def image_generation( # noqa: PLR0915 vertex_location=vertex_ai_location, vertex_credentials=vertex_credentials, aimg_generation=aimg_generation, + api_base=api_base, + client=client, ) elif ( custom_llm_provider in litellm._custom_providers diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index cf09749d81..09db9f10ee 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,5 +1,5 @@ model_list: - - model_name: "gpt-3.5-turbo" + - model_name: "gpt-4o" litellm_params: model: azure/chatgpt-v-2 api_key: os.environ/AZURE_API_KEY diff --git a/tests/litellm/test_cost_calculator.py b/tests/litellm/test_cost_calculator.py index 9c9f6d9043..c0073e2c56 100644 --- a/tests/litellm/test_cost_calculator.py +++ b/tests/litellm/test_cost_calculator.py @@ -15,9 +15,11 @@ from pydantic import BaseModel from litellm.cost_calculator import response_cost_calculator -def test_cost_calculator(): +def test_cost_calculator_with_response_cost_in_additional_headers(): class MockResponse(BaseModel): - _hidden_params = {"additional_headers": {"x-litellm-response-cost": 1000}} + _hidden_params = { + "additional_headers": {"llm_provider-x-litellm-response-cost": 1000} + } result = response_cost_calculator( response_object=MockResponse(), diff --git a/tests/llm_translation/test_litellm_proxy_provider.py b/tests/llm_translation/test_litellm_proxy_provider.py index 8484a66dad..c38e386063 100644 --- a/tests/llm_translation/test_litellm_proxy_provider.py +++ b/tests/llm_translation/test_litellm_proxy_provider.py @@ -31,7 +31,7 @@ async def test_litellm_gateway_from_sdk(): openai_client = OpenAI(api_key="fake-key") with patch.object( - openai_client.chat.completions, "create", new=MagicMock() + openai_client.chat.completions.with_raw_response, "create", new=MagicMock() ) as mock_call: try: completion( @@ -374,3 +374,78 @@ async def test_litellm_gateway_from_sdk_rerank(is_async): assert request_body["query"] == "What is machine learning?" assert request_body["model"] == "rerank-english-v2.0" assert len(request_body["documents"]) == 2 + + +def test_litellm_gateway_from_sdk_with_response_cost_in_additional_headers(): + litellm.set_verbose = True + litellm._turn_on_debug() + + from openai import OpenAI + + openai_client = OpenAI(api_key="fake-key") + + # Create mock response object + mock_response = MagicMock() + mock_response.headers = {"x-litellm-response-cost": "120"} + mock_response.parse.return_value = litellm.ModelResponse( + **{ + "id": "chatcmpl-BEkxQvRGp9VAushfAsOZCbhMFLsoy", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "content": "Hello! How can I assist you today?", + "refusal": None, + "role": "assistant", + "annotations": [], + "audio": None, + "function_call": None, + "tool_calls": None, + }, + } + ], + "created": 1742856796, + "model": "gpt-4o-2024-08-06", + "object": "chat.completion", + "service_tier": "default", + "system_fingerprint": "fp_6ec83003ad", + "usage": { + "completion_tokens": 10, + "prompt_tokens": 9, + "total_tokens": 19, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}, + }, + } + ) + + with patch.object( + openai_client.chat.completions.with_raw_response, + "create", + return_value=mock_response, + ) as mock_call: + response = litellm.completion( + model="litellm_proxy/gpt-4o", + messages=[{"role": "user", "content": "Hello world"}], + api_base="http://0.0.0.0:4000", + api_key="sk-PIp1h0RekR", + client=openai_client, + ) + + # Assert the headers were properly passed through + print(f"additional_headers: {response._hidden_params['additional_headers']}") + assert ( + response._hidden_params["additional_headers"][ + "llm_provider-x-litellm-response-cost" + ] + == "120" + ) + + assert response._hidden_params["response_cost"] == 120 diff --git a/tests/load_tests/test_vertex_embeddings_load_test.py b/tests/load_tests/test_vertex_embeddings_load_test.py index eb440c9437..24543e29d0 100644 --- a/tests/load_tests/test_vertex_embeddings_load_test.py +++ b/tests/load_tests/test_vertex_embeddings_load_test.py @@ -109,12 +109,13 @@ def analyze_results(vertex_times): @pytest.mark.asyncio -async def test_embedding_performance(): +async def test_embedding_performance(monkeypatch): """ Run load test on vertex AI embeddings to ensure vertex median response time is less than 300ms 20 RPS for 20 seconds """ + monkeypatch.setattr(litellm, "api_base", None) duration_seconds = 20 requests_per_second = 20 vertex_times = await run_load_test(duration_seconds, requests_per_second) diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 25993d6d5b..5e3ebf6a66 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -31,6 +31,7 @@ from litellm import ( completion, completion_cost, embedding, + image_generation, ) from litellm.llms.vertex_ai.gemini.transformation import ( _gemini_convert_messages_with_history, @@ -3327,3 +3328,46 @@ def test_signed_s3_url_with_format(): json_str = json.dumps(mock_client.call_args.kwargs["json"]) assert "image/jpeg" in json_str assert "image/png" not in json_str + + +@pytest.mark.parametrize("provider", ["vertex_ai", "gemini"]) +@pytest.mark.parametrize("route", ["completion", "embedding", "image_generation"]) +def test_litellm_api_base(monkeypatch, provider, route): + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + + import litellm + + monkeypatch.setattr(litellm, "api_base", "https://litellm.com") + + load_vertex_ai_credentials() + + if route == "image_generation" and provider == "gemini": + pytest.skip("Gemini does not support image generation") + + with patch.object(client, "post", new=MagicMock()) as mock_client: + try: + if route == "completion": + response = completion( + model=f"{provider}/gemini-2.0-flash-001", + messages=[{"role": "user", "content": "Hello, world!"}], + client=client, + ) + elif route == "embedding": + response = embedding( + model=f"{provider}/gemini-2.0-flash-001", + input=["Hello, world!"], + client=client, + ) + elif route == "image_generation": + response = image_generation( + model=f"{provider}/gemini-2.0-flash-001", + prompt="Hello, world!", + client=client, + ) + except Exception as e: + print(e) + + mock_client.assert_called() + assert mock_client.call_args.kwargs["url"].startswith("https://litellm.com") From efce84815adfbf3cd8301855e484c563c8a5e3ea Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:54:06 -0700 Subject: [PATCH 045/244] test_gemini_fine_tuned_model_request_consistency --- .../test_amazing_vertex_completion.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 25993d6d5b..7134c3536e 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -3327,3 +3327,96 @@ def test_signed_s3_url_with_format(): json_str = json.dumps(mock_client.call_args.kwargs["json"]) assert "image/jpeg" in json_str assert "image/png" not in json_str + + +def test_gemini_fine_tuned_model_request_consistency(): + """ + Assert the same transformation is applied to Fine tuned gemini 2.0 flash and gemini 2.0 flash + + - Request 1: Fine tuned: vertex_ai/ft-uuid with base_model: vertex_ai/gemini-2.0-flash-001 + - Request 2: vertex_ai/gemini-2.0-flash-001 + """ + litellm.set_verbose = True + load_vertex_ai_credentials() + from litellm.llms.custom_httpx.http_handler import HTTPHandler + from unittest.mock import patch, MagicMock + + # Set up the messages + messages = [ + { + "role": "system", + "content": "Your name is Litellm Bot, you are a helpful assistant", + }, + { + "role": "user", + "content": "Hello, what is your name and can you tell me the weather?", + }, + ] + + # Define tools + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + } + }, + "required": ["location"], + }, + }, + } + ] + + client = HTTPHandler(concurrent_limit=1) + + # First request + with patch.object(client, "post", new=MagicMock()) as mock_post_1: + try: + response_1 = completion( + model="vertex_ai/ft-uuid", + base_model="vertex_ai/gemini-2.0-flash-001", + messages=messages, + tools=tools, + tool_choice="auto", + client=client, + ) + + except Exception as e: + print(e) + + # Store the request body from the first call + first_request_body = mock_post_1.call_args.kwargs["json"] + print("first_request_body", first_request_body) + + # Second request + with patch.object(client, "post", new=MagicMock()) as mock_post_2: + try: + response_2 = completion( + model="vertex_ai/gemini-2.0-flash-001", + messages=messages, + tools=tools, + tool_choice="auto", + client=client, + ) + except Exception as e: + print(e) + + # Store the request body from the second call + second_request_body = mock_post_2.call_args.kwargs["json"] + print("second_request_body", second_request_body) + + # Get the diff between the two request bodies + # Convert dictionaries to formatted JSON strings + import json + + first_json = json.dumps(first_request_body, indent=2).splitlines() + second_json = json.dumps(second_request_body, indent=2).splitlines() + # Assert there is no difference between the request bodies + assert first_json == second_json, "Request bodies should be identical" From aa8ba9b8f295486b1c0bf744b733188d5f223fad Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:54:29 -0700 Subject: [PATCH 046/244] fix base_model in param mapping --- litellm/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/litellm/utils.py b/litellm/utils.py index dc97c4d898..ad9ba936ab 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2847,11 +2847,16 @@ def get_optional_params( # noqa: PLR0915 additional_drop_params=None, messages: Optional[List[AllMessageValues]] = None, thinking: Optional[AnthropicThinkingParam] = None, + base_model: Optional[str] = None, **kwargs, ): # retrieve all parameters passed to the function passed_params = locals().copy() special_params = passed_params.pop("kwargs") + + # Use `base_model` for paramter mapping if passed in by user + if base_model is not None: + model = base_model for k, v in special_params.items(): if k.startswith("aws_") and ( custom_llm_provider != "bedrock" and custom_llm_provider != "sagemaker" From 830ecbdb8c3485c5a7fbf26072ff8b40284ff262 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Mar 2025 23:54:57 -0700 Subject: [PATCH 047/244] fix get_optional_params --- litellm/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/main.py b/litellm/main.py index 3d4152d634..5eb38a6bd1 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1100,6 +1100,7 @@ def completion( # type: ignore # noqa: PLR0915 user=user, # params to identify the model model=model, + base_model=base_model, custom_llm_provider=custom_llm_provider, response_format=response_format, seed=seed, From 2bef0481af0840de7a2b16e5f4b8120111658da4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 00:05:45 -0700 Subject: [PATCH 048/244] _transform_request_body --- .../litellm_core_utils/llm_request_utils.py | 123 +++++++++++------- .../llms/vertex_ai/gemini/transformation.py | 6 + litellm/utils.py | 14 +- 3 files changed, 88 insertions(+), 55 deletions(-) diff --git a/litellm/litellm_core_utils/llm_request_utils.py b/litellm/litellm_core_utils/llm_request_utils.py index 50dbdc5536..8cfa48e937 100644 --- a/litellm/litellm_core_utils/llm_request_utils.py +++ b/litellm/litellm_core_utils/llm_request_utils.py @@ -3,66 +3,89 @@ from typing import Dict, Optional import litellm -def _ensure_extra_body_is_safe(extra_body: Optional[Dict]) -> Optional[Dict]: - """ - Ensure that the extra_body sent in the request is safe, otherwise users will see this error +class LitellmCoreRequestUtils: - "Object of type TextPromptClient is not JSON serializable + @staticmethod + def _ensure_extra_body_is_safe(extra_body: Optional[Dict]) -> Optional[Dict]: + """ + Ensure that the extra_body sent in the request is safe, otherwise users will see this error + + "Object of type TextPromptClient is not JSON serializable - Relevant Issue: https://github.com/BerriAI/litellm/issues/4140 - """ - if extra_body is None: - return None + Relevant Issue: https://github.com/BerriAI/litellm/issues/4140 + """ + if extra_body is None: + return None + + if not isinstance(extra_body, dict): + return extra_body + + if "metadata" in extra_body and isinstance(extra_body["metadata"], dict): + if "prompt" in extra_body["metadata"]: + _prompt = extra_body["metadata"].get("prompt") + + # users can send Langfuse TextPromptClient objects, so we need to convert them to dicts + # Langfuse TextPromptClients have .__dict__ attribute + if _prompt is not None and hasattr(_prompt, "__dict__"): + extra_body["metadata"]["prompt"] = _prompt.__dict__ - if not isinstance(extra_body, dict): return extra_body - if "metadata" in extra_body and isinstance(extra_body["metadata"], dict): - if "prompt" in extra_body["metadata"]: - _prompt = extra_body["metadata"].get("prompt") + @staticmethod + def pick_cheapest_chat_models_from_llm_provider(custom_llm_provider: str, n=1): + """ + Pick the n cheapest chat models from the LLM provider. - # users can send Langfuse TextPromptClient objects, so we need to convert them to dicts - # Langfuse TextPromptClients have .__dict__ attribute - if _prompt is not None and hasattr(_prompt, "__dict__"): - extra_body["metadata"]["prompt"] = _prompt.__dict__ + Args: + custom_llm_provider (str): The name of the LLM provider. + n (int): The number of cheapest models to return. - return extra_body + Returns: + list[str]: A list of the n cheapest chat models. + """ + if custom_llm_provider not in litellm.models_by_provider: + return [] + known_models = litellm.models_by_provider.get(custom_llm_provider, []) + model_costs = [] -def pick_cheapest_chat_models_from_llm_provider(custom_llm_provider: str, n=1): - """ - Pick the n cheapest chat models from the LLM provider. - - Args: - custom_llm_provider (str): The name of the LLM provider. - n (int): The number of cheapest models to return. - - Returns: - list[str]: A list of the n cheapest chat models. - """ - if custom_llm_provider not in litellm.models_by_provider: - return [] - - known_models = litellm.models_by_provider.get(custom_llm_provider, []) - model_costs = [] - - for model in known_models: - try: - model_info = litellm.get_model_info( - model=model, custom_llm_provider=custom_llm_provider + for model in known_models: + try: + model_info = litellm.get_model_info( + model=model, custom_llm_provider=custom_llm_provider + ) + except Exception: + continue + if model_info.get("mode") != "chat": + continue + _cost = model_info.get("input_cost_per_token", 0) + model_info.get( + "output_cost_per_token", 0 ) - except Exception: - continue - if model_info.get("mode") != "chat": - continue - _cost = model_info.get("input_cost_per_token", 0) + model_info.get( - "output_cost_per_token", 0 - ) - model_costs.append((model, _cost)) + model_costs.append((model, _cost)) - # Sort by cost (ascending) - model_costs.sort(key=lambda x: x[1]) + # Sort by cost (ascending) + model_costs.sort(key=lambda x: x[1]) - # Return the top n cheapest models - return [model for model, _ in model_costs[:n]] + # Return the top n cheapest models + return [model for model, _ in model_costs[:n]] + + @staticmethod + def select_model_for_request_transformation( + model: str, + base_model: Optional[str] = None, + litellm_params: Optional[Dict] = None, + ) -> str: + """ + If `base_model` is passed in by user, use it for the request transformation + + Else, use the model passed in the request + """ + if base_model is not None: + return base_model + elif ( + litellm_params is not None and litellm_params.get("base_model") is not None + ): + return litellm_params["base_model"] + else: + return model diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index d6bafc7c60..5534ca03ce 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -12,6 +12,7 @@ from pydantic import BaseModel import litellm from litellm._logging import verbose_logger +from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils from litellm.litellm_core_utils.prompt_templates.factory import ( convert_to_anthropic_image_obj, convert_to_gemini_tool_call_invoke, @@ -284,6 +285,11 @@ def _transform_request_body( Common transformation logic across sync + async Gemini /generateContent calls. """ # Separate system prompt from rest of message + model = LitellmCoreRequestUtils.select_model_for_request_transformation( + model=model, + litellm_params=litellm_params, + ) + supports_system_message = get_supports_system_message( model=model, custom_llm_provider=custom_llm_provider ) diff --git a/litellm/utils.py b/litellm/utils.py index ad9ba936ab..bcf22e902c 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -84,7 +84,7 @@ from litellm.litellm_core_utils.get_llm_provider_logic import ( from litellm.litellm_core_utils.get_supported_openai_params import ( get_supported_openai_params, ) -from litellm.litellm_core_utils.llm_request_utils import _ensure_extra_body_is_safe +from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( LiteLLMResponseObjectHandler, _handle_invalid_parallel_tool_calls, @@ -2855,8 +2855,10 @@ def get_optional_params( # noqa: PLR0915 special_params = passed_params.pop("kwargs") # Use `base_model` for paramter mapping if passed in by user - if base_model is not None: - model = base_model + model = LitellmCoreRequestUtils.select_model_for_request_transformation( + model=model, + base_model=base_model, + ) for k, v in special_params.items(): if k.startswith("aws_") and ( custom_llm_provider != "bedrock" and custom_llm_provider != "sagemaker" @@ -3738,8 +3740,10 @@ def get_optional_params( # noqa: PLR0915 **extra_body, } - optional_params["extra_body"] = _ensure_extra_body_is_safe( - extra_body=optional_params["extra_body"] + optional_params["extra_body"] = ( + LitellmCoreRequestUtils._ensure_extra_body_is_safe( + extra_body=optional_params["extra_body"] + ) ) else: # if user passed in non-default kwargs for specific providers/models, pass them along From 27c085cc56f91965c7393da0840cdea1492ead60 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 00:08:16 -0700 Subject: [PATCH 049/244] fix util vertex --- litellm/main.py | 10 +++++----- tests/litellm_utils_tests/test_utils.py | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index 5eb38a6bd1..af0dc1f5b6 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -59,9 +59,7 @@ from litellm.litellm_core_utils.health_check_utils import ( _filter_model_params, ) from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.litellm_core_utils.llm_request_utils import ( - pick_cheapest_chat_models_from_llm_provider, -) +from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils from litellm.litellm_core_utils.mock_functions import ( mock_embedding, mock_image_generation, @@ -5424,8 +5422,10 @@ async def ahealth_check_wildcard_models( ) -> dict: # this is a wildcard model, we need to pick a random model from the provider - cheapest_models = pick_cheapest_chat_models_from_llm_provider( - custom_llm_provider=custom_llm_provider, n=3 + cheapest_models = ( + LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( + custom_llm_provider=custom_llm_provider, n=3 + ) ) if len(cheapest_models) == 0: raise Exception( diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index fea225e4a3..ef9bf30c13 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -1532,12 +1532,26 @@ def test_supports_vision_gemini(): def test_pick_cheapest_chat_model_from_llm_provider(): from litellm.litellm_core_utils.llm_request_utils import ( - pick_cheapest_chat_models_from_llm_provider, + LitellmCoreRequestUtils, ) - assert len(pick_cheapest_chat_models_from_llm_provider("openai", n=3)) == 3 + assert ( + len( + LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( + "openai", n=3 + ) + ) + == 3 + ) - assert len(pick_cheapest_chat_models_from_llm_provider("unknown", n=1)) == 0 + assert ( + len( + LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( + "unknown", n=1 + ) + ) + == 0 + ) def test_get_potential_model_names(): From fb31006cd847d921c9487c326c560dfd537e0f8d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 07:12:25 -0700 Subject: [PATCH 050/244] select_model_for_request_transformation --- litellm/main.py | 6 ++++-- litellm/utils.py | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index af0dc1f5b6..8ebf66fd06 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1097,8 +1097,10 @@ def completion( # type: ignore # noqa: PLR0915 logit_bias=logit_bias, user=user, # params to identify the model - model=model, - base_model=base_model, + model=LitellmCoreRequestUtils.select_model_for_request_transformation( + model=model, + base_model=base_model, + ), custom_llm_provider=custom_llm_provider, response_format=response_format, seed=seed, diff --git a/litellm/utils.py b/litellm/utils.py index bcf22e902c..45fa9fa1fd 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2847,18 +2847,12 @@ def get_optional_params( # noqa: PLR0915 additional_drop_params=None, messages: Optional[List[AllMessageValues]] = None, thinking: Optional[AnthropicThinkingParam] = None, - base_model: Optional[str] = None, **kwargs, ): # retrieve all parameters passed to the function passed_params = locals().copy() special_params = passed_params.pop("kwargs") - # Use `base_model` for paramter mapping if passed in by user - model = LitellmCoreRequestUtils.select_model_for_request_transformation( - model=model, - base_model=base_model, - ) for k, v in special_params.items(): if k.startswith("aws_") and ( custom_llm_provider != "bedrock" and custom_llm_provider != "sagemaker" From e7a8b5a8092668671c25a46a94990c279b279203 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 08:12:51 -0700 Subject: [PATCH 051/244] run ci/cd again --- tests/local_testing/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local_testing/test_completion.py b/tests/local_testing/test_completion.py index 59f5a38f08..dd8b26141c 100644 --- a/tests/local_testing/test_completion.py +++ b/tests/local_testing/test_completion.py @@ -11,7 +11,7 @@ import os sys.path.insert( 0, os.path.abspath("../..") -) # Adds the parent directory to the system-path +) # Adds the parent directory to the system path import os From 22dfc4dea998b1327e187b60aaf64f4d3612cb2b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 08:36:11 -0700 Subject: [PATCH 052/244] fix user_api_key_auth example config --- litellm/proxy/example_config_yaml/custom_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/example_config_yaml/custom_auth.py b/litellm/proxy/example_config_yaml/custom_auth.py index 7d797623c5..e59db22719 100644 --- a/litellm/proxy/example_config_yaml/custom_auth.py +++ b/litellm/proxy/example_config_yaml/custom_auth.py @@ -7,7 +7,7 @@ from litellm.proxy._types import GenerateKeyRequest, UserAPIKeyAuth async def user_api_key_auth(request: Request, api_key: str) -> UserAPIKeyAuth: try: - modified_master_key = f"{os.getenv('PROXY_MASTER_KEY')}-1234" + modified_master_key = f"{os.getenv('LITELLM_MASTER_KEY')}-1234" if api_key == modified_master_key: return UserAPIKeyAuth(api_key=api_key) raise Exception From 7123b69ccaae63f4bb4c463f5c5da06f65f622da Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 08:42:55 -0700 Subject: [PATCH 053/244] fix caching unit tests --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 304f96bdbb..6a87d8b1b2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -242,7 +242,6 @@ jobs: steps: - checkout - - setup_google_dns - run: name: Show git commit hash command: | From e5a5dca777b7efacdaaa50238031ca7bba46fb0c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 08:46:23 -0700 Subject: [PATCH 054/244] fix load_testing --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a87d8b1b2..38614e657e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -629,7 +629,6 @@ jobs: steps: - checkout - - setup_google_dns - run: name: Install Dependencies command: | From c3dc4a2ecdd3cc4415a7dd2688d274f87806bbe1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 08:55:18 -0700 Subject: [PATCH 055/244] fix DNS resolution --- .circleci/config.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38614e657e..67af43f8f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,11 @@ commands: - run: name: "Configure Google DNS" command: | - echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf + # Backup original resolv.conf + sudo cp /etc/resolv.conf /etc/resolv.conf.backup + # Add both local and Google DNS servers + echo "nameserver 127.0.0.11" | sudo tee /etc/resolv.conf + echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf echo "nameserver 8.8.4.4" | sudo tee -a /etc/resolv.conf jobs: @@ -242,6 +246,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Show git commit hash command: | From a97d0563b4bed0bbc686303f6d9e5011eeb2e457 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 08:58:35 -0700 Subject: [PATCH 056/244] build(model_prices_and_context_window.json): add commercial rate limits for gemini 2.0 flash lite --- litellm/model_prices_and_context_window_backup.json | 2 ++ model_prices_and_context_window.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f9eb63ccca..06f7238c01 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4694,6 +4694,8 @@ "output_cost_per_token": 0.0000003, "litellm_provider": "gemini", "mode": "chat", + "tpm": 4000000, + "rpm": 4000, "supports_system_messages": true, "supports_function_calling": true, "supports_vision": true, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f9eb63ccca..06f7238c01 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4694,6 +4694,8 @@ "output_cost_per_token": 0.0000003, "litellm_provider": "gemini", "mode": "chat", + "tpm": 4000000, + "rpm": 4000, "supports_system_messages": true, "supports_function_calling": true, "supports_vision": true, From 68665ea0289367cc40dc2f8fbe8bd4af325f2bd9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 09:25:08 -0700 Subject: [PATCH 057/244] DNS lookup for Redis host --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 67af43f8f8..b2c83e3845 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ orbs: node: circleci/node@5.1.0 # Add this line to declare the node orb commands: - setup_google_dns: + c: steps: - run: name: "Configure Google DNS" @@ -247,6 +247,9 @@ jobs: steps: - checkout - setup_google_dns + - run: + name: DNS lookup for Redis host + command: dig redis-19899.c239.us-east-1-2.ec2.redns.redis-cloud.com +short - run: name: Show git commit hash command: | From d7884114d0d5893acb6110bd6e1181bdf7989fcb Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 09:26:33 -0700 Subject: [PATCH 058/244] setup_google_dns --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b2c83e3845..4faafdbd3d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ orbs: node: circleci/node@5.1.0 # Add this line to declare the node orb commands: - c: + setup_google_dns: steps: - run: name: "Configure Google DNS" From 5f51228b727174d282b7568cb562b90afb4a35b0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 09:28:59 -0700 Subject: [PATCH 059/244] DNS lookup for Redis host --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4faafdbd3d..3b4005baab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -249,7 +249,10 @@ jobs: - setup_google_dns - run: name: DNS lookup for Redis host - command: dig redis-19899.c239.us-east-1-2.ec2.redns.redis-cloud.com +short + command: | + sudo apt-get update + sudo apt-get install -y dnsutils + dig redis-19899.c239.us-east-1-2.ec2.redns.redis-cloud.com +short - run: name: Show git commit hash command: | From 8c845847cd36db9d944833d65edf9520470633c2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 10:25:50 -0700 Subject: [PATCH 060/244] fix vertex embedding perf test --- tests/load_tests/test_vertex_embeddings_load_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/load_tests/test_vertex_embeddings_load_test.py b/tests/load_tests/test_vertex_embeddings_load_test.py index 24543e29d0..7c593fb549 100644 --- a/tests/load_tests/test_vertex_embeddings_load_test.py +++ b/tests/load_tests/test_vertex_embeddings_load_test.py @@ -59,7 +59,7 @@ def load_vertex_ai_credentials(): async def create_async_vertex_embedding_task(): load_vertex_ai_credentials() - base_url = "https://exampleopenaiendpoint-production.up.railway.app/v1/projects/pathrise-convert-1606954137718/locations/us-central1/publishers/google/models/embedding-gecko-001:predict" + base_url = "https://exampleopenaiendpoint-production.up.railway.app/v1/projects/pathrise-convert-1606954137718/locations/us-central1/publishers/google/models/textembedding-gecko@001" embedding_args = { "model": "vertex_ai/textembedding-gecko", "input": "This is a test sentence for embedding.", From bbe69a47a9fafc89db9b85a113fc3da9b73a5c4c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 10:53:23 -0700 Subject: [PATCH 061/244] _is_model_gemini_gemini_spec_model --- litellm/llms/vertex_ai/common_utils.py | 5 +++++ .../gemini/vertex_and_google_ai_studio_gemini.py | 13 ++++++++++++- litellm/main.py | 5 +---- .../local_testing/test_amazing_vertex_completion.py | 3 +-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index a3f91fbacc..d5ad330484 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -3,6 +3,7 @@ from typing import Dict, List, Literal, Optional, Tuple, Union import httpx +import litellm from litellm import supports_response_schema, supports_system_messages, verbose_logger from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.types.llms.vertex_ai import PartType @@ -28,6 +29,10 @@ def get_supports_system_message( supports_system_message = supports_system_messages( model=model, custom_llm_provider=_custom_llm_provider ) + + # Vertex Models called in the `/gemini` request/response format also support system messages + if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + supports_system_message = True except Exception as e: verbose_logger.warning( "Unable to identify if system message supported. Defaulting to 'False'. Received error message - {}\nAdd it here - https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json".format( diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 73215b4048..cbbbae5b34 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -207,7 +207,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "extra_headers", "seed", "logprobs", - "top_logprobs" # Added this to list of supported openAI params + "top_logprobs", # Added this to list of supported openAI params ] def map_tool_choice_values( @@ -419,6 +419,17 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "europe-west9", ] + @staticmethod + def _is_model_gemini_gemini_spec_model(model: Optional[str]) -> bool: + """ + Returns true if user is trying to call custom model in `/gemini` request/response format + """ + if model is None: + return False + if "gemini/" in model: + return True + return False + def get_flagged_finish_reasons(self) -> Dict[str, str]: """ Return Dictionary of finish reasons which indicate response was flagged diff --git a/litellm/main.py b/litellm/main.py index 8ebf66fd06..b96a054118 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1097,10 +1097,7 @@ def completion( # type: ignore # noqa: PLR0915 logit_bias=logit_bias, user=user, # params to identify the model - model=LitellmCoreRequestUtils.select_model_for_request_transformation( - model=model, - base_model=base_model, - ), + model=model, custom_llm_provider=custom_llm_provider, response_format=response_format, seed=seed, diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 7134c3536e..3687212e84 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -3380,8 +3380,7 @@ def test_gemini_fine_tuned_model_request_consistency(): with patch.object(client, "post", new=MagicMock()) as mock_post_1: try: response_1 = completion( - model="vertex_ai/ft-uuid", - base_model="vertex_ai/gemini-2.0-flash-001", + model="vertex_ai/gemini/ft-uuid", messages=messages, tools=tools, tool_choice="auto", From 3ee7962f9c24249bc1c3d9d8d5c24ee3de2f5108 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 10:55:56 -0700 Subject: [PATCH 062/244] fix llm request utils --- .../litellm_core_utils/llm_request_utils.py | 123 +++++++----------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/litellm/litellm_core_utils/llm_request_utils.py b/litellm/litellm_core_utils/llm_request_utils.py index 8cfa48e937..50dbdc5536 100644 --- a/litellm/litellm_core_utils/llm_request_utils.py +++ b/litellm/litellm_core_utils/llm_request_utils.py @@ -3,89 +3,66 @@ from typing import Dict, Optional import litellm -class LitellmCoreRequestUtils: +def _ensure_extra_body_is_safe(extra_body: Optional[Dict]) -> Optional[Dict]: + """ + Ensure that the extra_body sent in the request is safe, otherwise users will see this error - @staticmethod - def _ensure_extra_body_is_safe(extra_body: Optional[Dict]) -> Optional[Dict]: - """ - Ensure that the extra_body sent in the request is safe, otherwise users will see this error - - "Object of type TextPromptClient is not JSON serializable + "Object of type TextPromptClient is not JSON serializable - Relevant Issue: https://github.com/BerriAI/litellm/issues/4140 - """ - if extra_body is None: - return None - - if not isinstance(extra_body, dict): - return extra_body - - if "metadata" in extra_body and isinstance(extra_body["metadata"], dict): - if "prompt" in extra_body["metadata"]: - _prompt = extra_body["metadata"].get("prompt") - - # users can send Langfuse TextPromptClient objects, so we need to convert them to dicts - # Langfuse TextPromptClients have .__dict__ attribute - if _prompt is not None and hasattr(_prompt, "__dict__"): - extra_body["metadata"]["prompt"] = _prompt.__dict__ + Relevant Issue: https://github.com/BerriAI/litellm/issues/4140 + """ + if extra_body is None: + return None + if not isinstance(extra_body, dict): return extra_body - @staticmethod - def pick_cheapest_chat_models_from_llm_provider(custom_llm_provider: str, n=1): - """ - Pick the n cheapest chat models from the LLM provider. + if "metadata" in extra_body and isinstance(extra_body["metadata"], dict): + if "prompt" in extra_body["metadata"]: + _prompt = extra_body["metadata"].get("prompt") - Args: - custom_llm_provider (str): The name of the LLM provider. - n (int): The number of cheapest models to return. + # users can send Langfuse TextPromptClient objects, so we need to convert them to dicts + # Langfuse TextPromptClients have .__dict__ attribute + if _prompt is not None and hasattr(_prompt, "__dict__"): + extra_body["metadata"]["prompt"] = _prompt.__dict__ - Returns: - list[str]: A list of the n cheapest chat models. - """ - if custom_llm_provider not in litellm.models_by_provider: - return [] + return extra_body - known_models = litellm.models_by_provider.get(custom_llm_provider, []) - model_costs = [] - for model in known_models: - try: - model_info = litellm.get_model_info( - model=model, custom_llm_provider=custom_llm_provider - ) - except Exception: - continue - if model_info.get("mode") != "chat": - continue - _cost = model_info.get("input_cost_per_token", 0) + model_info.get( - "output_cost_per_token", 0 +def pick_cheapest_chat_models_from_llm_provider(custom_llm_provider: str, n=1): + """ + Pick the n cheapest chat models from the LLM provider. + + Args: + custom_llm_provider (str): The name of the LLM provider. + n (int): The number of cheapest models to return. + + Returns: + list[str]: A list of the n cheapest chat models. + """ + if custom_llm_provider not in litellm.models_by_provider: + return [] + + known_models = litellm.models_by_provider.get(custom_llm_provider, []) + model_costs = [] + + for model in known_models: + try: + model_info = litellm.get_model_info( + model=model, custom_llm_provider=custom_llm_provider ) - model_costs.append((model, _cost)) + except Exception: + continue + if model_info.get("mode") != "chat": + continue + _cost = model_info.get("input_cost_per_token", 0) + model_info.get( + "output_cost_per_token", 0 + ) + model_costs.append((model, _cost)) - # Sort by cost (ascending) - model_costs.sort(key=lambda x: x[1]) + # Sort by cost (ascending) + model_costs.sort(key=lambda x: x[1]) - # Return the top n cheapest models - return [model for model, _ in model_costs[:n]] - - @staticmethod - def select_model_for_request_transformation( - model: str, - base_model: Optional[str] = None, - litellm_params: Optional[Dict] = None, - ) -> str: - """ - If `base_model` is passed in by user, use it for the request transformation - - Else, use the model passed in the request - """ - if base_model is not None: - return base_model - elif ( - litellm_params is not None and litellm_params.get("base_model") is not None - ): - return litellm_params["base_model"] - else: - return model + # Return the top n cheapest models + return [model for model, _ in model_costs[:n]] From 8a72b67b18a9cd41599df07eb6ef0685a1dbaaf0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 10:57:08 -0700 Subject: [PATCH 063/244] undo code changes --- litellm/llms/vertex_ai/gemini/transformation.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 5534ca03ce..d6bafc7c60 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -12,7 +12,6 @@ from pydantic import BaseModel import litellm from litellm._logging import verbose_logger -from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils from litellm.litellm_core_utils.prompt_templates.factory import ( convert_to_anthropic_image_obj, convert_to_gemini_tool_call_invoke, @@ -285,11 +284,6 @@ def _transform_request_body( Common transformation logic across sync + async Gemini /generateContent calls. """ # Separate system prompt from rest of message - model = LitellmCoreRequestUtils.select_model_for_request_transformation( - model=model, - litellm_params=litellm_params, - ) - supports_system_message = get_supports_system_message( model=model, custom_llm_provider=custom_llm_provider ) From fee20250a60aca317850321cfb0fd26ea8e940d1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 10:59:02 -0700 Subject: [PATCH 064/244] pick_cheapest_chat_models_from_llm_provider --- litellm/main.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index b96a054118..5e628975ef 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -59,7 +59,9 @@ from litellm.litellm_core_utils.health_check_utils import ( _filter_model_params, ) from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils +from litellm.litellm_core_utils.llm_request_utils import ( + pick_cheapest_chat_models_from_llm_provider, +) from litellm.litellm_core_utils.mock_functions import ( mock_embedding, mock_image_generation, @@ -2348,6 +2350,8 @@ def completion( # type: ignore # noqa: PLR0915 or litellm.api_key ) + api_base = api_base or litellm.api_base or get_secret("GEMINI_API_BASE") + new_params = deepcopy(optional_params) response = vertex_chat_completion.completion( # type: ignore model=model, @@ -2390,6 +2394,8 @@ def completion( # type: ignore # noqa: PLR0915 or get_secret("VERTEXAI_CREDENTIALS") ) + api_base = api_base or litellm.api_base or get_secret("VERTEXAI_API_BASE") + new_params = deepcopy(optional_params) if ( model.startswith("meta/") @@ -3655,6 +3661,8 @@ def embedding( # noqa: PLR0915 api_key or get_secret_str("GEMINI_API_KEY") or litellm.api_key ) + api_base = api_base or litellm.api_base or get_secret_str("GEMINI_API_BASE") + response = google_batch_embeddings.batch_embeddings( # type: ignore model=model, input=input, @@ -3669,6 +3677,8 @@ def embedding( # noqa: PLR0915 print_verbose=print_verbose, custom_llm_provider="gemini", api_key=gemini_api_key, + api_base=api_base, + client=client, ) elif custom_llm_provider == "vertex_ai": @@ -3693,6 +3703,13 @@ def embedding( # noqa: PLR0915 or get_secret_str("VERTEX_CREDENTIALS") ) + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERTEXAI_API_BASE") + or get_secret_str("VERTEX_API_BASE") + ) + if ( "image" in optional_params or "video" in optional_params @@ -3714,6 +3731,7 @@ def embedding( # noqa: PLR0915 print_verbose=print_verbose, custom_llm_provider="vertex_ai", client=client, + api_base=api_base, ) else: response = vertex_embedding.embedding( @@ -3731,6 +3749,8 @@ def embedding( # noqa: PLR0915 aembedding=aembedding, print_verbose=print_verbose, api_key=api_key, + api_base=api_base, + client=client, ) elif custom_llm_provider == "oobabooga": response = oobabooga.embedding( @@ -4693,6 +4713,14 @@ def image_generation( # noqa: PLR0915 or optional_params.pop("vertex_ai_credentials", None) or get_secret_str("VERTEXAI_CREDENTIALS") ) + + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERTEXAI_API_BASE") + or get_secret_str("VERTEX_API_BASE") + ) + model_response = vertex_image_generation.image_generation( model=model, prompt=prompt, @@ -4704,6 +4732,7 @@ def image_generation( # noqa: PLR0915 vertex_location=vertex_ai_location, vertex_credentials=vertex_credentials, aimg_generation=aimg_generation, + client=client, ) elif ( custom_llm_provider in litellm._custom_providers @@ -5421,10 +5450,8 @@ async def ahealth_check_wildcard_models( ) -> dict: # this is a wildcard model, we need to pick a random model from the provider - cheapest_models = ( - LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( - custom_llm_provider=custom_llm_provider, n=3 - ) + cheapest_models = pick_cheapest_chat_models_from_llm_provider( + custom_llm_provider=custom_llm_provider, n=3 ) if len(cheapest_models) == 0: raise Exception( From a2ae9be6a266b4f854c96983ec2d529aa52b8efe Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 11:09:29 -0700 Subject: [PATCH 065/244] undo changes to utils --- litellm/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 45fa9fa1fd..dc97c4d898 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -84,7 +84,7 @@ from litellm.litellm_core_utils.get_llm_provider_logic import ( from litellm.litellm_core_utils.get_supported_openai_params import ( get_supported_openai_params, ) -from litellm.litellm_core_utils.llm_request_utils import LitellmCoreRequestUtils +from litellm.litellm_core_utils.llm_request_utils import _ensure_extra_body_is_safe from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( LiteLLMResponseObjectHandler, _handle_invalid_parallel_tool_calls, @@ -2852,7 +2852,6 @@ def get_optional_params( # noqa: PLR0915 # retrieve all parameters passed to the function passed_params = locals().copy() special_params = passed_params.pop("kwargs") - for k, v in special_params.items(): if k.startswith("aws_") and ( custom_llm_provider != "bedrock" and custom_llm_provider != "sagemaker" @@ -3734,10 +3733,8 @@ def get_optional_params( # noqa: PLR0915 **extra_body, } - optional_params["extra_body"] = ( - LitellmCoreRequestUtils._ensure_extra_body_is_safe( - extra_body=optional_params["extra_body"] - ) + optional_params["extra_body"] = _ensure_extra_body_is_safe( + extra_body=optional_params["extra_body"] ) else: # if user passed in non-default kwargs for specific providers/models, pass them along From 1822c45c0ec3d0ac200e273618cd2b95fa3c5962 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 11:13:21 -0700 Subject: [PATCH 066/244] test fix test_pick_cheapest_chat_model_from_llm_provider --- tests/litellm_utils_tests/test_utils.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index ef9bf30c13..fea225e4a3 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -1532,26 +1532,12 @@ def test_supports_vision_gemini(): def test_pick_cheapest_chat_model_from_llm_provider(): from litellm.litellm_core_utils.llm_request_utils import ( - LitellmCoreRequestUtils, + pick_cheapest_chat_models_from_llm_provider, ) - assert ( - len( - LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( - "openai", n=3 - ) - ) - == 3 - ) + assert len(pick_cheapest_chat_models_from_llm_provider("openai", n=3)) == 3 - assert ( - len( - LitellmCoreRequestUtils.pick_cheapest_chat_models_from_llm_provider( - "unknown", n=1 - ) - ) - == 0 - ) + assert len(pick_cheapest_chat_models_from_llm_provider("unknown", n=1)) == 0 def test_get_potential_model_names(): From 793a920caa8b8b3dcd7074be350a5e724bbab20e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 11:14:51 -0700 Subject: [PATCH 067/244] rename _is_model_gemini_spec_model --- litellm/llms/vertex_ai/common_utils.py | 2 +- .../llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index fb9f595fa8..bbaab31401 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -31,7 +31,7 @@ def get_supports_system_message( ) # Vertex Models called in the `/gemini` request/response format also support system messages - if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + if litellm.VertexGeminiConfig._is_model_gemini_spec_model(model): supports_system_message = True except Exception as e: verbose_logger.warning( diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index cbbbae5b34..fdb8c0e558 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -420,7 +420,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ] @staticmethod - def _is_model_gemini_gemini_spec_model(model: Optional[str]) -> bool: + def _is_model_gemini_spec_model(model: Optional[str]) -> bool: """ Returns true if user is trying to call custom model in `/gemini` request/response format """ From 801ecb65176b14b62ac7cc51e158a9f4b4ed81e0 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 11:28:20 -0700 Subject: [PATCH 068/244] Nova Canvas complete image generation tasks (#9177) (#9525) * Nova Canvas complete image generation tasks (#9177) * add initial support for Amazon Nova Canvas model Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * adjust name to AmazonNovaCanvas and map function variables to config Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * tighten model name check Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * fix quality mapping Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add premium quality in config Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * support all Amazon Nova Canvas tasks * remove unused import Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add tests for image generation tasks and fix payload Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add missing util file Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * update model prices backup file Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * remove image tasks other than text->image Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add color guided generation task for Nova Canvas Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * fix merge Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add nova canvas image generation documentation Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> * add nova canvas unit tests Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> --------- Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> Co-authored-by: Krish Dholakia * ci(config.yml): bump ci config * test: fix test --------- Signed-off-by: omrishiv <327609+omrishiv@users.noreply.github.com> Co-authored-by: omrishiv <327609+omrishiv@users.noreply.github.com> --- .circleci/config.yml | 1 + docs/my-website/docs/providers/bedrock.md | 39 +++++- .../amazon_nova_canvas_transformation.py | 10 +- litellm/types/llms/bedrock.py | 23 +++ .../test_bedrock_image_gen_unit_tests.py | 131 ++++++++++++++++++ .../image_gen_tests/test_image_generation.py | 14 ++ 6 files changed, 216 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b4005baab..5a058144f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -640,6 +640,7 @@ jobs: steps: - checkout + - setup_google_dns - run: name: Install Dependencies command: | diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 58b44b04f2..4f88fdb39b 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -1917,12 +1917,49 @@ model_list: +Text to Image : +```bash +curl -L -X POST 'http://0.0.0.0:4000/v1/images/generations' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_VIRTUAL_KEY' \ +-d '{ + "model": "amazon.nova-canvas-v1:0", + "prompt": "A cute baby sea otter" +}' +``` +Color Guided Generation: +```bash +curl -L -X POST 'http://0.0.0.0:4000/v1/images/generations' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_VIRTUAL_KEY' \ +-d '{ + "model": "amazon.nova-canvas-v1:0", + "prompt": "A cute baby sea otter", + "taskType": "COLOR_GUIDED_GENERATION", + "colorGuidedGenerationParams":{"colors":["#FFFFFF"]} +}' +``` + + + + +| Model Name | Function Call | +|-------------------------|---------------------------------------------| +| Stable Diffusion 3 - v0 | `image_generation(model="bedrock/stability.stability.sd3-large-v1:0", prompt=prompt)` | +| Stable Diffusion - v0 | `image_generation(model="bedrock/stability.stable-diffusion-xl-v0", prompt=prompt)` | +| Stable Diffusion - v1 | `image_generation(model="bedrock/stability.stable-diffusion-xl-v1", prompt=prompt)` | +| Amazon Nova Canvas - v0 | `image_generation(model="bedrock/amazon.nova-canvas-v1:0", prompt=prompt)` | + + ### Passing an external BedrockRuntime.Client as a parameter - Completion() + +This is a deprecated flow. Boto3 is not async. And boto3.client does not let us make the http call through httpx. Pass in your aws params through the method above 👆. [See Auth Code](https://github.com/BerriAI/litellm/blob/55a20c7cce99a93d36a82bf3ae90ba3baf9a7f89/litellm/llms/bedrock_httpx.py#L284) [Add new auth flow](https://github.com/BerriAI/litellm/issues) :::warning -This is a deprecated flow. Boto3 is not async. And boto3.client does not let us make the http call through httpx. Pass in your aws params through the method above 👆. [See Auth Code](https://github.com/BerriAI/litellm/blob/55a20c7cce99a93d36a82bf3ae90ba3baf9a7f89/litellm/llms/bedrock_httpx.py#L284) [Add new auth flow](https://github.com/BerriAI/litellm/issues) + + Experimental - 2024-Jun-23: diff --git a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py index de46edb923..33249f9af8 100644 --- a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py +++ b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py @@ -5,7 +5,8 @@ from openai.types.image import Image from litellm.types.llms.bedrock import ( AmazonNovaCanvasTextToImageRequest, AmazonNovaCanvasTextToImageResponse, - AmazonNovaCanvasTextToImageParams, AmazonNovaCanvasRequestBase, + AmazonNovaCanvasTextToImageParams, AmazonNovaCanvasRequestBase, AmazonNovaCanvasColorGuidedGenerationParams, + AmazonNovaCanvasColorGuidedRequest, ) from litellm.types.utils import ImageResponse @@ -69,6 +70,13 @@ class AmazonNovaCanvasConfig: text_to_image_params = AmazonNovaCanvasTextToImageParams(**text_to_image_params) return AmazonNovaCanvasTextToImageRequest(textToImageParams=text_to_image_params, taskType=task_type, imageGenerationConfig=image_generation_config) + if task_type == "COLOR_GUIDED_GENERATION": + color_guided_generation_params = image_generation_config.pop("colorGuidedGenerationParams", {}) + color_guided_generation_params = {"text": text, **color_guided_generation_params} + color_guided_generation_params = AmazonNovaCanvasColorGuidedGenerationParams(**color_guided_generation_params) + return AmazonNovaCanvasColorGuidedRequest(taskType=task_type, + colorGuidedGenerationParams=color_guided_generation_params, + imageGenerationConfig=image_generation_config) raise NotImplementedError(f"Task type {task_type} is not supported") @classmethod diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index 57fb04c8a9..08738459f4 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -418,6 +418,29 @@ class AmazonNovaCanvasTextToImageRequest( imageGenerationConfig: AmazonNovaCanvasImageGenerationConfig +class AmazonNovaCanvasColorGuidedGenerationParams(TypedDict, total=False): + """ + Params for Amazon Nova Canvas Color Guided Generation API + """ + + colors: List[str] + referenceImage: str + text: str + negativeText: str + + +class AmazonNovaCanvasColorGuidedRequest(AmazonNovaCanvasRequestBase, TypedDict, total=False): + """ + Request for Amazon Nova Canvas Color Guided Generation API + + Ref: https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html + """ + + taskType: Literal["COLOR_GUIDED_GENERATION"] + colorGuidedGenerationParams: AmazonNovaCanvasColorGuidedGenerationParams + imageGenerationConfig: AmazonNovaCanvasImageGenerationConfig + + class AmazonNovaCanvasTextToImageResponse(TypedDict, total=False): """ Response for Amazon Nova Canvas Text to Image API diff --git a/tests/image_gen_tests/test_bedrock_image_gen_unit_tests.py b/tests/image_gen_tests/test_bedrock_image_gen_unit_tests.py index fa5fbeda7f..ed1cdddf29 100644 --- a/tests/image_gen_tests/test_bedrock_image_gen_unit_tests.py +++ b/tests/image_gen_tests/test_bedrock_image_gen_unit_tests.py @@ -6,6 +6,14 @@ import traceback from dotenv import load_dotenv from openai.types.image import Image +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +from litellm.llms.bedrock.image.amazon_nova_canvas_transformation import ( + AmazonNovaCanvasConfig, +) + logging.basicConfig(level=logging.DEBUG) load_dotenv() import asyncio @@ -54,6 +62,25 @@ def test_is_stability_3_model(model, expected): assert result == expected +@pytest.mark.parametrize( + "model,expected", + [ + ("amazon.nova-canvas", True), + ("sd3-large", False), + ("sd3-large-turbo", False), + ("sd3-medium", False), + ("sd3.5-large", False), + ("sd3.5-large-turbo", False), + ("gpt-4", False), + (None, False), + ("other-model", False), + ], +) +def test_is_nova_canvas_model(model, expected): + result = AmazonNovaCanvasConfig._is_nova_model(model) + assert result == expected + + def test_transform_request_body(): prompt = "A beautiful sunset" optional_params = {"size": "1024x1024"} @@ -161,6 +188,110 @@ def test_get_request_body_stability(): assert result["cfg_scale"] == 7 +def test_transform_request_body_nova_canvas(): + prompt = "A beautiful sunset" + optional_params = {"size": "1024x1024"} + + result = AmazonNovaCanvasConfig.transform_request_body(prompt, optional_params) + + assert result["taskType"] == "TEXT_IMAGE" + assert result["textToImageParams"]["text"] == prompt + assert result["imageGenerationConfig"]["size"] == "1024x1024" + + +def test_map_openai_params_nova_canvas(): + non_default_params = {"n": 2, "size": "1024x1024"} + optional_params = {"cfg_scale": 7} + + result = AmazonNovaCanvasConfig.map_openai_params( + non_default_params, optional_params + ) + + assert result == optional_params + assert "n" not in result # OpenAI params should not be included + + +def test_transform_response_dict_to_openai_response_nova_canvas(): + # Create a mock response + response_dict = {"images": ["base64_encoded_image_1", "base64_encoded_image_2"]} + model_response = ImageResponse() + + result = AmazonNovaCanvasConfig.transform_response_dict_to_openai_response( + model_response, response_dict + ) + + assert isinstance(result, ImageResponse) + assert len(result.data) == 2 + assert all(hasattr(img, "b64_json") for img in result.data) + assert [img.b64_json for img in result.data] == response_dict["images"] + + +def test_amazon_nova_canvas_get_supported_openai_params(): + result = AmazonNovaCanvasConfig.get_supported_openai_params() + assert result == ["n", "size", "quality"] + + +def test_get_request_body_nova_canvas_default(): + handler = BedrockImageGeneration() + prompt = "A beautiful sunset" + optional_params = {"cfg_scale": 7} + model = "amazon.nova-canvas-v1" + + result = handler._get_request_body( + model=model, prompt=prompt, optional_params=optional_params + ) + + assert result["taskType"] == "TEXT_IMAGE" + assert result["textToImageParams"]["text"] == prompt + assert result["imageGenerationConfig"]["cfg_scale"] == 7 + + +def test_get_request_body_nova_canvas_text_image(): + handler = BedrockImageGeneration() + prompt = "A beautiful sunset" + optional_params = {"cfg_scale": 7, "taskType": "TEXT_IMAGE"} + model = "amazon.nova-canvas-v1" + + result = handler._get_request_body( + model=model, prompt=prompt, optional_params=optional_params + ) + + assert result["taskType"] == "TEXT_IMAGE" + assert result["textToImageParams"]["text"] == prompt + assert result["imageGenerationConfig"]["cfg_scale"] == 7 + + +def test_get_request_body_nova_canvas_color_guided_generation(): + handler = BedrockImageGeneration() + prompt = "A beautiful sunset" + optional_params = { + "cfg_scale": 7, + "taskType": "COLOR_GUIDED_GENERATION", + "colorGuidedGenerationParams": {"colors": ["#FF0000"]}, + } + model = "amazon.nova-canvas-v1" + + result = handler._get_request_body( + model=model, prompt=prompt, optional_params=optional_params + ) + + assert result["taskType"] == "COLOR_GUIDED_GENERATION" + assert result["colorGuidedGenerationParams"]["text"] == prompt + assert result["colorGuidedGenerationParams"]["colors"] == ["#FF0000"] + assert result["imageGenerationConfig"]["cfg_scale"] == 7 + + +def test_transform_request_body_with_invalid_task_type(): + text = "An image of a otter" + optional_params = {"taskType": "INVALID_TASK"} + + with pytest.raises(NotImplementedError) as exc_info: + AmazonNovaCanvasConfig.transform_request_body( + text=text, optional_params=optional_params + ) + assert "Task type INVALID_TASK is not supported" in str(exc_info.value) + + def test_transform_response_dict_to_openai_response_stability3(): handler = BedrockImageGeneration() model_response = ImageResponse() diff --git a/tests/image_gen_tests/test_image_generation.py b/tests/image_gen_tests/test_image_generation.py index 96928f9030..1f2c563f26 100644 --- a/tests/image_gen_tests/test_image_generation.py +++ b/tests/image_gen_tests/test_image_generation.py @@ -143,6 +143,20 @@ class TestBedrockNovaCanvasTextToImage(BaseImageGenTest): } +class TestBedrockNovaCanvasColorGuidedGeneration(BaseImageGenTest): + def get_base_image_generation_call_args(self) -> dict: + litellm.in_memory_llm_clients_cache = InMemoryCache() + return { + "model": "bedrock/amazon.nova-canvas-v1:0", + "n": 1, + "size": "320x320", + "imageGenerationConfig": {"cfgScale":6.5,"seed":12}, + "taskType": "COLOR_GUIDED_GENERATION", + "colorGuidedGenerationParams":{"colors":["#FFFFFF"]}, + "aws_region_name": "us-east-1", + } + + class TestOpenAIDalle3(BaseImageGenTest): def get_base_image_generation_call_args(self) -> dict: return {"model": "dall-e-3"} From 93daf5cbac2af4cc632b47acd6f3c90cd8275b60 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 12:16:18 -0700 Subject: [PATCH 069/244] _get_model_name_from_gemini_spec_model --- litellm/llms/vertex_ai/common_utils.py | 4 +++- .../gemini/vertex_and_google_ai_studio_gemini.py | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index bbaab31401..b5bad6b858 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -31,7 +31,7 @@ def get_supports_system_message( ) # Vertex Models called in the `/gemini` request/response format also support system messages - if litellm.VertexGeminiConfig._is_model_gemini_spec_model(model): + if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): supports_system_message = True except Exception as e: verbose_logger.warning( @@ -75,6 +75,8 @@ def _get_vertex_url( ) -> Tuple[str, str]: url: Optional[str] = None endpoint: Optional[str] = None + if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + model = litellm.VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) if mode == "chat": ### SET RUNTIME ENDPOINT ### endpoint = "generateContent" diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index fdb8c0e558..0e48c690ba 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -420,7 +420,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ] @staticmethod - def _is_model_gemini_spec_model(model: Optional[str]) -> bool: + def _is_model_gemini_gemini_spec_model(model: Optional[str]) -> bool: """ Returns true if user is trying to call custom model in `/gemini` request/response format """ @@ -430,6 +430,19 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): return True return False + @staticmethod + def _get_model_name_from_gemini_spec_model(model: str) -> str: + """ + Returns the model name if model="vertex_ai/gemini/" + + Example: + - model = "gemini/1234567890" + - returns "1234567890" + """ + if "gemini/" in model: + return model.split("/")[-1] + return model + def get_flagged_finish_reasons(self) -> Dict[str, str]: """ Return Dictionary of finish reasons which indicate response was flagged From 12eb77d02d0b494ad288928d6ad070955dd322ac Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 12:24:49 -0700 Subject: [PATCH 070/244] docs litellm vertex ai ft models --- docs/my-website/docs/providers/vertex.md | 126 ++++++++++++----------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 10ac13ecaf..b8633adc5e 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1369,6 +1369,71 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ +## Gemini Pro +| Model Name | Function Call | +|------------------|--------------------------------------| +| gemini-pro | `completion('gemini-pro', messages)`, `completion('vertex_ai/gemini-pro', messages)` | + +## Fine-tuned Models + +Call fine-tuned Vertex AI Gemini models through LiteLLM. If you want to use LiteLLM to call a model in the `/gemini` request/response format, you can do so by setting `model="vertex_ai/gemini/{MODEL_ID}"`. This tells litellm that the request/response format follows the `gemini` model family format. + +| Property | Details | +|----------|---------| +| Provider Route | `vertex_ai/gemini/{MODEL_ID}` | +| Vertex Documentation | [Vertex AI - Fine-tuned Gemini Models](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini-use-supervised-tuning#test_the_tuned_model_with_a_prompt)| +| Supported Operations | `/chat/completions`, `/completions`, `/embeddings`, `/images` | + + + + +```python showLineNumbers +import litellm +import os + +## set ENV variables +os.environ["VERTEXAI_PROJECT"] = "hardy-device-38811" +os.environ["VERTEXAI_LOCATION"] = "us-central1" + +response = litellm.completion( + model="vertex_ai/gemini/", # e.g. vertex_ai/4965075652664360960 + messages=[{ "content": "Hello, how are you?","role": "user"}], +) +``` + + + + +1. Add Vertex Credentials to your env + +```bash +!gcloud auth application-default login +``` + +2. Setup config.yaml + +```yaml +- model_name: finetuned-gemini + litellm_params: + model: vertex_ai/gemini/ + vertex_project: + vertex_location: +``` + +3. Test it! + +```bash +curl --location 'https://0.0.0.0:4000/v1/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: ' \ +--data '{"model": "finetuned-gemini" ,"messages":[{"role": "user", "content":[{"type": "text", "text": "hi"}]}]}' +``` + + + + + + ## Model Garden :::tip @@ -1479,67 +1544,6 @@ response = completion( -## Gemini Pro -| Model Name | Function Call | -|------------------|--------------------------------------| -| gemini-pro | `completion('gemini-pro', messages)`, `completion('vertex_ai/gemini-pro', messages)` | - -## Fine-tuned Models - -Fine tuned models on vertex have a numerical model/endpoint id. - - - - -```python -from litellm import completion -import os - -## set ENV variables -os.environ["VERTEXAI_PROJECT"] = "hardy-device-38811" -os.environ["VERTEXAI_LOCATION"] = "us-central1" - -response = completion( - model="vertex_ai/", # e.g. vertex_ai/4965075652664360960 - messages=[{ "content": "Hello, how are you?","role": "user"}], - base_model="vertex_ai/gemini-1.5-pro" # the base model - used for routing -) -``` - - - - -1. Add Vertex Credentials to your env - -```bash -!gcloud auth application-default login -``` - -2. Setup config.yaml - -```yaml -- model_name: finetuned-gemini - litellm_params: - model: vertex_ai/ - vertex_project: - vertex_location: - model_info: - base_model: vertex_ai/gemini-1.5-pro # IMPORTANT -``` - -3. Test it! - -```bash -curl --location 'https://0.0.0.0:4000/v1/chat/completions' \ ---header 'Content-Type: application/json' \ ---header 'Authorization: ' \ ---data '{"model": "finetuned-gemini" ,"messages":[{"role": "user", "content":[{"type": "text", "text": "hi"}]}]}' -``` - - - - - ## Gemini Pro Vision | Model Name | Function Call | From 82016eba0a02ffc3e6115d287651cdcec0b140b0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 13:55:38 -0700 Subject: [PATCH 071/244] docs vertex ft models --- docs/my-website/docs/providers/vertex.md | 34 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index b8633adc5e..ede3ae88d8 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1385,7 +1385,7 @@ Call fine-tuned Vertex AI Gemini models through LiteLLM. If you want to use Lite | Supported Operations | `/chat/completions`, `/completions`, `/embeddings`, `/images` | - + ```python showLineNumbers import litellm @@ -1402,7 +1402,7 @@ response = litellm.completion( ``` - + 1. Add Vertex Credentials to your env @@ -1412,7 +1412,7 @@ response = litellm.completion( 2. Setup config.yaml -```yaml +```yaml showLineNumbers - model_name: finetuned-gemini litellm_params: model: vertex_ai/gemini/ @@ -1422,7 +1422,30 @@ response = litellm.completion( 3. Test it! -```bash + + + +```python showLineNumbers +from openai import OpenAI + +client = OpenAI( + api_key="your-litellm-key", + base_url="http://0.0.0.0:4000" +) + +response = client.chat.completions.create( + model="finetuned-gemini", + messages=[ + {"role": "user", "content": "hi"} + ] +) +print(response) +``` + + + + +```bash showLineNumbers curl --location 'https://0.0.0.0:4000/v1/chat/completions' \ --header 'Content-Type: application/json' \ --header 'Authorization: ' \ @@ -1432,6 +1455,9 @@ curl --location 'https://0.0.0.0:4000/v1/chat/completions' \ + + + ## Model Garden From e24a60189722fcefaee257ec158ca2b0d6a86681 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:03:42 -0700 Subject: [PATCH 072/244] docs verte ft models --- docs/my-website/docs/providers/vertex.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index ede3ae88d8..15c30d1222 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1376,7 +1376,8 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ ## Fine-tuned Models -Call fine-tuned Vertex AI Gemini models through LiteLLM. If you want to use LiteLLM to call a model in the `/gemini` request/response format, you can do so by setting `model="vertex_ai/gemini/{MODEL_ID}"`. This tells litellm that the request/response format follows the `gemini` model family format. +You can call fine-tuned Vertex AI Gemini models through LiteLLM +``` | Property | Details | |----------|---------| @@ -1384,10 +1385,16 @@ Call fine-tuned Vertex AI Gemini models through LiteLLM. If you want to use Lite | Vertex Documentation | [Vertex AI - Fine-tuned Gemini Models](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini-use-supervised-tuning#test_the_tuned_model_with_a_prompt)| | Supported Operations | `/chat/completions`, `/completions`, `/embeddings`, `/images` | +To use a model that follows the `/gemini` request/response format, simply set the model parameter as + +```python title="Model parameter for calling fine-tuned gemini models" +model="vertex_ai/gemini/" +``` + -```python showLineNumbers +```python showLineNumbers title="Example" import litellm import os @@ -1406,13 +1413,13 @@ response = litellm.completion( 1. Add Vertex Credentials to your env -```bash +```bash title="Authenticate to Vertex AI" !gcloud auth application-default login ``` 2. Setup config.yaml -```yaml showLineNumbers +```yaml showLineNumbers title="Add to litellm config" - model_name: finetuned-gemini litellm_params: model: vertex_ai/gemini/ @@ -1425,7 +1432,7 @@ response = litellm.completion( -```python showLineNumbers +```python showLineNumbers title="Example request" from openai import OpenAI client = OpenAI( @@ -1445,7 +1452,7 @@ print(response) -```bash showLineNumbers +```bash showLineNumbers title="Example request" curl --location 'https://0.0.0.0:4000/v1/chat/completions' \ --header 'Content-Type: application/json' \ --header 'Authorization: ' \ From 28ab8fdcccae5ece69b3addf6e41e22e3042f6ac Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:08:19 -0700 Subject: [PATCH 073/244] docs vertex ft model --- docs/my-website/docs/providers/vertex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 15c30d1222..38c18c7ad8 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1403,7 +1403,7 @@ os.environ["VERTEXAI_PROJECT"] = "hardy-device-38811" os.environ["VERTEXAI_LOCATION"] = "us-central1" response = litellm.completion( - model="vertex_ai/gemini/", # e.g. vertex_ai/4965075652664360960 + model="vertex_ai/gemini/", # e.g. vertex_ai/gemini/4965075652664360960 messages=[{ "content": "Hello, how are you?","role": "user"}], ) ``` From 82faa49668ea674452365841041810c89de63221 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:09:27 -0700 Subject: [PATCH 074/244] doc fix Fine-tuned Models --- docs/my-website/docs/providers/vertex.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 38c18c7ad8..f2489de769 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1377,7 +1377,6 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ ## Fine-tuned Models You can call fine-tuned Vertex AI Gemini models through LiteLLM -``` | Property | Details | |----------|---------| From da9d84934821905ef5562add771e70021da128b8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:10:32 -0700 Subject: [PATCH 075/244] test_gemini_fine_tuned_model_request_consistency --- tests/local_testing/test_amazing_vertex_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 86f7e83dac..5d99ecb5cd 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -3334,7 +3334,7 @@ def test_gemini_fine_tuned_model_request_consistency(): """ Assert the same transformation is applied to Fine tuned gemini 2.0 flash and gemini 2.0 flash - - Request 1: Fine tuned: vertex_ai/ft-uuid with base_model: vertex_ai/gemini-2.0-flash-001 + - Request 1: Fine tuned: vertex_ai/gemini/ft-uuid - Request 2: vertex_ai/gemini-2.0-flash-001 """ litellm.set_verbose = True From 8eaf4c55c0d0f05d5533b9112a767410c9e2d6ac Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:18:11 -0700 Subject: [PATCH 076/244] test_gemini_fine_tuned_model_request_consistency --- litellm/llms/vertex_ai/common_utils.py | 4 ++-- .../vertex_and_google_ai_studio_gemini.py | 19 +++++++++++++++++++ .../test_amazing_vertex_completion.py | 8 ++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index b5bad6b858..2361939d81 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -75,8 +75,8 @@ def _get_vertex_url( ) -> Tuple[str, str]: url: Optional[str] = None endpoint: Optional[str] = None - if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): - model = litellm.VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) + + model = litellm.VertexGeminiConfig.get_model_for_vertex_ai_url(model=model) if mode == "chat": ### SET RUNTIME ENDPOINT ### endpoint = "generateContent" diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 0e48c690ba..8aecfffd86 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -419,6 +419,25 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "europe-west9", ] + @staticmethod + def get_model_for_vertex_ai_url(model: str) -> str: + """ + Returns the model name to use in the request to Vertex AI + + Handles 2 cases: + 1. User passed `model="vertex_ai/gemini/ft-uuid"`, we need to return `ft-uuid` for the request to Vertex AI + 2. User passed `model="vertex_ai/gemini-2.0-flash-001"`, we need to return `gemini-2.0-flash-001` for the request to Vertex AI + + Args: + model (str): The model name to use in the request to Vertex AI + + Returns: + str: The model name to use in the request to Vertex AI + """ + if VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + return VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) + return model + @staticmethod def _is_model_gemini_gemini_spec_model(model: Optional[str]) -> bool: """ diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 5d99ecb5cd..83c99d766b 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -3395,6 +3395,14 @@ def test_gemini_fine_tuned_model_request_consistency(): first_request_body = mock_post_1.call_args.kwargs["json"] print("first_request_body", first_request_body) + # Validate correct `model` is added to the request to Vertex AI + print("final URL=", mock_post_1.call_args.kwargs["url"]) + # Validate the request url + assert ( + "publishers/google/models/ft-uuid:generateContent" + in mock_post_1.call_args.kwargs["url"] + ) + # Second request with patch.object(client, "post", new=MagicMock()) as mock_post_2: try: From 72f08bc6eae994092479f155790db2e59c4903e6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:21:35 -0700 Subject: [PATCH 077/244] unit tests for VertexGeminiConfig --- .../test_vertex_and_google_ai_studio.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py index 5e870b5af3..9fc9be7e0f 100644 --- a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py +++ b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py @@ -31,3 +31,41 @@ def test_top_logprobs(): ) assert v["responseLogprobs"] is non_default_params["logprobs"] assert v["logprobs"] is non_default_params["top_logprobs"] + + +def test_get_model_for_vertex_ai_url(): + # Test case 1: Regular model name + model = "gemini-pro" + result = VertexGeminiConfig.get_model_for_vertex_ai_url(model) + assert result == "gemini-pro" + + # Test case 2: Gemini spec model with UUID + model = "gemini/ft-uuid-123" + result = VertexGeminiConfig.get_model_for_vertex_ai_url(model) + assert result == "ft-uuid-123" + + +def test_is_model_gemini_gemini_spec_model(): + # Test case 1: None input + assert VertexGeminiConfig._is_model_gemini_gemini_spec_model(None) == False + + # Test case 2: Regular model name + assert VertexGeminiConfig._is_model_gemini_gemini_spec_model("gemini-pro") == False + + # Test case 3: Gemini spec model + assert ( + VertexGeminiConfig._is_model_gemini_gemini_spec_model("gemini/custom-model") + == True + ) + + +def test_get_model_name_from_gemini_spec_model(): + # Test case 1: Regular model name + model = "gemini-pro" + result = VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) + assert result == "gemini-pro" + + # Test case 2: Gemini spec model + model = "gemini/ft-uuid-123" + result = VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) + assert result == "ft-uuid-123" From c38b41f65b69cc6059dfe665e58324bef38508e8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:26:08 -0700 Subject: [PATCH 078/244] test_get_supports_system_message --- .../vertex_ai/test_vertex_ai_common_utils.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index e89355443f..b94d7495cb 100644 --- a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -41,3 +41,21 @@ async def test_get_vertex_location_from_url(): url = "https://invalid-url.com" location = get_vertex_location_from_url(url) assert location is None + + +@pytest.mark.asyncio +async def test_get_supports_system_message(): + """Test get_supports_system_message with different models""" + from litellm.llms.vertex_ai.common_utils import get_supports_system_message + + # fine-tuned vertex gemini models will specifiy they are in the /gemini spec format + result = get_supports_system_message( + model="gemini/1234567890", custom_llm_provider="vertex_ai" + ) + assert result == True + + # non-fine-tuned vertex gemini models will not specifiy they are in the /gemini spec format + result = get_supports_system_message( + model="random-model-name", custom_llm_provider="vertex_ai" + ) + assert result == False From 0aae9aa24ada456e3013c146414305c66e3db8d4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:28:26 -0700 Subject: [PATCH 079/244] rename _is_model_gemini_spec_model --- litellm/llms/vertex_ai/common_utils.py | 2 +- .../gemini/vertex_and_google_ai_studio_gemini.py | 4 ++-- .../gemini/test_vertex_and_google_ai_studio.py | 11 ++++------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 2361939d81..9c9db2f047 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -31,7 +31,7 @@ def get_supports_system_message( ) # Vertex Models called in the `/gemini` request/response format also support system messages - if litellm.VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + if litellm.VertexGeminiConfig._is_model_gemini_spec_model(model): supports_system_message = True except Exception as e: verbose_logger.warning( diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 8aecfffd86..2931b1b3a8 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -434,12 +434,12 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): Returns: str: The model name to use in the request to Vertex AI """ - if VertexGeminiConfig._is_model_gemini_gemini_spec_model(model): + if VertexGeminiConfig._is_model_gemini_spec_model(model): return VertexGeminiConfig._get_model_name_from_gemini_spec_model(model) return model @staticmethod - def _is_model_gemini_gemini_spec_model(model: Optional[str]) -> bool: + def _is_model_gemini_spec_model(model: Optional[str]) -> bool: """ Returns true if user is trying to call custom model in `/gemini` request/response format """ diff --git a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py index 9fc9be7e0f..18d965200c 100644 --- a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py +++ b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio.py @@ -45,18 +45,15 @@ def test_get_model_for_vertex_ai_url(): assert result == "ft-uuid-123" -def test_is_model_gemini_gemini_spec_model(): +def test_is_model_gemini_spec_model(): # Test case 1: None input - assert VertexGeminiConfig._is_model_gemini_gemini_spec_model(None) == False + assert VertexGeminiConfig._is_model_gemini_spec_model(None) == False # Test case 2: Regular model name - assert VertexGeminiConfig._is_model_gemini_gemini_spec_model("gemini-pro") == False + assert VertexGeminiConfig._is_model_gemini_spec_model("gemini-pro") == False # Test case 3: Gemini spec model - assert ( - VertexGeminiConfig._is_model_gemini_gemini_spec_model("gemini/custom-model") - == True - ) + assert VertexGeminiConfig._is_model_gemini_spec_model("gemini/custom-model") == True def test_get_model_name_from_gemini_spec_model(): From 1812ce4a54e5c15c1206667b812c7fde924ed0d2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:43:03 -0700 Subject: [PATCH 080/244] undo config.yml changes --- .circleci/config.yml | 49 +------------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 43de7e69e8..5a058144f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -710,44 +710,6 @@ jobs: paths: - llm_translation_coverage.xml - llm_translation_coverage - litellm_proxy_reliability_tests: - docker: - - image: cimg/python:3.11 - auth: - username: ${DOCKERHUB_USERNAME} - password: ${DOCKERHUB_PASSWORD} - working_directory: ~/project - - steps: - - checkout - - run: - name: Install Dependencies - command: | - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - pip install "pytest==7.3.1" - pip install "pytest-retry==1.6.3" - pip install "pytest-cov==5.0.0" - pip install "pytest-asyncio==0.21.1" - pip install "respx==0.21.1" - # Run pytest and generate JUnit XML report - - run: - name: Setup Toxiproxy - command: | - python tests/proxy_reliability_tests/setup_toxi_proxy.py & - sleep 5 - - run: - name: Run tests - command: | - pwd - ls - python -m pytest -vv tests/proxy_reliability_tests --cov=litellm --cov-report=xml -x -s -v --junitxml=test-results/junit.xml --durations=5 - no_output_timeout: 120m - - run: - name: Rename the coverage files - command: | - mv coverage.xml litellm_proxy_reliability_tests_coverage.xml - mv .coverage litellm_proxy_reliability_tests_coverage mcp_testing: docker: - image: cimg/python:3.11 @@ -1968,7 +1930,7 @@ jobs: no_output_timeout: 120m # Clean up first container - + proxy_build_from_pip_tests: # Change from docker to machine executor machine: @@ -2536,13 +2498,6 @@ workflows: only: - main - /litellm_.*/ - - litellm_proxy_reliability_tests: - filters: - branches: - only: - - main - - /litellm_stability_.*/ - - litellm_stable_release_branch - litellm_assistants_api_testing: filters: branches: @@ -2684,7 +2639,6 @@ workflows: - caching_unit_tests - litellm_proxy_unit_testing - litellm_proxy_security_tests - - litellm_proxy_reliability_tests - langfuse_logging_unit_tests - local_testing - litellm_assistants_api_testing @@ -2750,7 +2704,6 @@ workflows: - e2e_ui_testing - litellm_proxy_unit_testing - litellm_proxy_security_tests - - litellm_proxy_reliability_tests - installing_litellm_on_python - installing_litellm_on_python_3_13 - proxy_logging_guardrails_model_info_tests From 4948673e3572b16f95057e881d219b598a6a9d04 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:51:33 -0700 Subject: [PATCH 081/244] fix test changes --- .../setup_toxi_proxy.py | 96 ------------------- .../test_configs/test_config.yaml | 9 -- .../test_using_litellm_db_outage.py | 76 --------------- 3 files changed, 181 deletions(-) delete mode 100644 tests/proxy_reliability_tests/setup_toxi_proxy.py delete mode 100644 tests/proxy_reliability_tests/test_configs/test_config.yaml delete mode 100644 tests/proxy_reliability_tests/test_using_litellm_db_outage.py diff --git a/tests/proxy_reliability_tests/setup_toxi_proxy.py b/tests/proxy_reliability_tests/setup_toxi_proxy.py deleted file mode 100644 index b44298aca5..0000000000 --- a/tests/proxy_reliability_tests/setup_toxi_proxy.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import platform -import subprocess -import time -import requests -import stat - - -def download_toxiproxy(): - # Determine system architecture and OS - system = platform.system().lower() - machine = platform.machine().lower() - - # Map architecture names - arch_map = { - "x86_64": "amd64", - "amd64": "amd64", - "arm64": "arm64", - "aarch64": "arm64", - } - - arch = arch_map.get(machine, machine) - - # Construct download URL (using latest version 2.5.0) - base_url = "https://github.com/Shopify/toxiproxy/releases/download/v2.5.0/" - if system == "linux": - filename = f"toxiproxy-server-linux-{arch}" - cli_filename = f"toxiproxy-cli-linux-{arch}" - elif system == "darwin": - filename = f"toxiproxy-server-darwin-{arch}" - cli_filename = f"toxiproxy-cli-darwin-{arch}" - else: - raise Exception("Unsupported operating system") - - # Download server - response = requests.get(f"{base_url}{filename}") - with open("toxiproxy-server", "wb") as f: - f.write(response.content) - - # Download CLI - response = requests.get(f"{base_url}{cli_filename}") - with open("toxiproxy-cli", "wb") as f: - f.write(response.content) - - # Make files executable - os.chmod("toxiproxy-server", stat.S_IRWXU) - os.chmod("toxiproxy-cli", stat.S_IRWXU) - - -def setup_toxiproxy(): - # Start toxiproxy-server - server_process = subprocess.Popen(["./toxiproxy-server"]) - - # Wait for server to start - time.sleep(2) - - # Create proxy - subprocess.run( - [ - "./toxiproxy-cli", - "create", - "-l", - "0.0.0.0:6666", - "-u", - "ep-dry-paper-a69g2y1q-pooler.us-west-2.aws.neon.tech:5432", - "postgres_proxy", - ] - ) - - return server_process - - -def main(): - try: - # Download ToxiProxy binaries - download_toxiproxy() - - # Setup ToxiProxy - server_process = setup_toxiproxy() - - print("ToxiProxy setup completed successfully!") - print("Proxy 'postgres_proxy' created and listening on 127.0.0.1:6666") - - # Keep the script running to maintain the proxy - try: - server_process.wait() - except KeyboardInterrupt: - server_process.terminate() - print("\nToxiProxy server stopped") - - except Exception as e: - print(f"Error: {e}") - - -if __name__ == "__main__": - main() diff --git a/tests/proxy_reliability_tests/test_configs/test_config.yaml b/tests/proxy_reliability_tests/test_configs/test_config.yaml deleted file mode 100644 index 4912a35f89..0000000000 --- a/tests/proxy_reliability_tests/test_configs/test_config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -model_list: - - model_name: fake-openai-endpoint - litellm_params: - model: openai/fake - api_key: fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - -general_settings: - allow_requests_on_db_unavailable: True \ No newline at end of file diff --git a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py b/tests/proxy_reliability_tests/test_using_litellm_db_outage.py deleted file mode 100644 index ea9c7e3f3a..0000000000 --- a/tests/proxy_reliability_tests/test_using_litellm_db_outage.py +++ /dev/null @@ -1,76 +0,0 @@ -import asyncio -import os -import subprocess -import sys -import time -import traceback -import pytest - -sys.path.insert( - 0, os.path.abspath("../..") -) # Adds the parent directory to the system path - -import litellm - -import os -import subprocess -import time - -import pytest -import requests - -TEST_MASTER_KEY = "sk-1234" - - -def test_litellm_proxy_server_config_no_general_settings(): - # Install the litellm[proxy] package - # Start the server - try: - subprocess.run(["pip", "install", "litellm[proxy]"]) - filepath = os.path.dirname(os.path.abspath(__file__)) - config_fp = f"{filepath}/test_configs/test_config.yaml" - - # Set DATABASE_URL environment variable - os.environ["DATABASE_URL"] = os.getenv("TOXI_PROXY_DATABASE_URL") - os.environ["LITELLM_MASTER_KEY"] = TEST_MASTER_KEY - - server_process = subprocess.Popen( - [ - "python", - "-m", - "litellm.proxy.proxy_cli", - "--config", - config_fp, - ] - ) - - # Allow some time for the server to start - time.sleep(60) # Adjust the sleep time if necessary - - # Send a request to the /health/liveliness endpoint - response = requests.get("http://localhost:4000/health/liveliness") - - # Check if the response is successful - assert response.status_code == 200 - assert response.json() == "I'm alive!" - - # Test /chat/completions - response = requests.post( - "http://localhost:4000/chat/completions", - headers={"Authorization": "Bearer 1234567890"}, - json={ - "model": "test_openai_models", - "messages": [{"role": "user", "content": "Hello, how are you?"}], - }, - ) - - assert response.status_code == 200 - - except ImportError: - pytest.fail("Failed to import litellm.proxy_server") - except requests.ConnectionError: - pytest.fail("Failed to connect to the server") - finally: - # Shut down the server - server_process.terminate() - server_process.wait() From f8caebc54b28dda058ec42ea638d344a5662a784 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 14:55:40 -0700 Subject: [PATCH 082/244] is_database_connection_error --- litellm/proxy/_types.py | 4 ++++ litellm/proxy/auth/auth_exception_handler.py | 10 +++++++--- litellm/proxy/auth/user_api_key_auth.py | 9 ++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index c0fb0750eb..104d1e7338 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2070,6 +2070,10 @@ class ProxyErrorTypes(str, enum.Enum): """ Object was over budget """ + no_db_connection = "no_db_connection" + """ + No database connection + """ token_not_found_in_db = "token_not_found_in_db" """ diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py index 5b314ef931..c1a546b569 100644 --- a/litellm/proxy/auth/auth_exception_handler.py +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -144,6 +144,10 @@ class UserAPIKeyAuthExceptionHandler: """ import prisma - return isinstance(e, DB_CONNECTION_ERROR_TYPES) or isinstance( - e, prisma.errors.PrismaError - ) + if isinstance(e, DB_CONNECTION_ERROR_TYPES): + return True + if isinstance(e, prisma.errors.PrismaError): + return True + if isinstance(e, ProxyException) and e.type == ProxyErrorTypes.no_db_connection: + return True + return False diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 06fa560866..85ee76ff31 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -675,7 +675,12 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 if ( prisma_client is None ): # if both master key + user key submitted, and user key != master key, and no db connected, raise an error - raise Exception("No connected db.") + raise ProxyException( + message="No connected db.", + type=ProxyErrorTypes.no_db_connection, + code=400, + param=None, + ) ## check for cache hit (In-Memory Cache) _user_role = None @@ -1001,8 +1006,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 route=route, start_time=start_time, ) - else: - raise Exception() except Exception as e: return await UserAPIKeyAuthExceptionHandler._handle_authentication_error( e=e, From 8bd2081dec15c2255e295302687a1e1c4ce413e3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 15:41:40 -0700 Subject: [PATCH 083/244] fix get_key_object --- litellm/proxy/auth/auth_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 9e71147706..dc0ec116f5 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -996,7 +996,7 @@ async def get_key_object( if _valid_token is None: raise ProxyException( - message="Key doesn't exist in db. key={}. Create key via `/key/generate` call.".format( + message="Invalid proxy server token passed. key={}, not found in db. Create key via `/key/generate` call.".format( hashed_token ), type=ProxyErrorTypes.token_not_found_in_db, From ff33ed020cc72fd4fa5541f58d4974f731b11006 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 15:45:58 -0700 Subject: [PATCH 084/244] fix auth checks --- litellm/proxy/auth/auth_checks.py | 3 --- litellm/proxy/auth/user_api_key_auth.py | 1 - 2 files changed, 4 deletions(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index dc0ec116f5..7f75272bbd 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -11,7 +11,6 @@ Run checks for: import asyncio import re import time -import traceback from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, cast from fastapi import Request, status @@ -23,7 +22,6 @@ from litellm.caching.caching import DualCache from litellm.caching.dual_cache import LimitedSizeOrderedDict from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.proxy._types import ( - DB_CONNECTION_ERROR_TYPES, RBAC_ROLES, CallInfo, LiteLLM_EndUserTable, @@ -45,7 +43,6 @@ from litellm.proxy.auth.route_checks import RouteChecks from litellm.proxy.route_llm_request import route_request from litellm.proxy.utils import PrismaClient, ProxyLogging, log_db_metrics from litellm.router import Router -from litellm.types.services import ServiceTypes from .auth_checks_organization import organization_role_based_access_check diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 85ee76ff31..b58353bf05 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -39,7 +39,6 @@ from litellm.proxy.auth.auth_checks import ( ) from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler from litellm.proxy.auth.auth_utils import ( - _get_request_ip_address, get_end_user_id_from_request_body, get_request_route, is_pass_through_provider_route, From 23aa7f81b562b4cd96079e05733acb56b7c0bd1b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 15:53:33 -0700 Subject: [PATCH 085/244] fix ProxyException --- litellm/proxy/auth/auth_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 7f75272bbd..efbfe8d90c 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -993,7 +993,7 @@ async def get_key_object( if _valid_token is None: raise ProxyException( - message="Invalid proxy server token passed. key={}, not found in db. Create key via `/key/generate` call.".format( + message="Authentication Error, Invalid proxy server token passed. key={}, not found in db. Create key via `/key/generate` call.".format( hashed_token ), type=ProxyErrorTypes.token_not_found_in_db, From 132d3f7baa528020fad5b2e869f990558a00c8b6 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 16:22:56 -0700 Subject: [PATCH 086/244] feat(prisma-migrations): add baseline db migration file (#9565) adds initial baseline db migration file enables future schema changes to be documented via .sql files --- ci_cd/baseline_db.py | 61 +++ .../20250326162113_baseline/migration.sql | 360 ++++++++++++++++++ deploy/migrations/migration_lock.toml | 1 + .../test_db_schema_migration.py | 62 +++ 4 files changed, 484 insertions(+) create mode 100644 ci_cd/baseline_db.py create mode 100644 deploy/migrations/20250326162113_baseline/migration.sql create mode 100644 deploy/migrations/migration_lock.toml create mode 100644 tests/proxy_unit_tests/test_db_schema_migration.py diff --git a/ci_cd/baseline_db.py b/ci_cd/baseline_db.py new file mode 100644 index 0000000000..52aa5430f5 --- /dev/null +++ b/ci_cd/baseline_db.py @@ -0,0 +1,61 @@ +import os +import subprocess +from pathlib import Path +from datetime import datetime + + +def create_baseline(): + """Create baseline migration in deploy/migrations""" + try: + # Get paths + root_dir = Path(__file__).parent.parent + deploy_dir = root_dir / "deploy" + migrations_dir = deploy_dir / "migrations" + schema_path = root_dir / "schema.prisma" + + # Create migrations directory + migrations_dir.mkdir(parents=True, exist_ok=True) + + # Create migration_lock.toml if it doesn't exist + lock_file = migrations_dir / "migration_lock.toml" + if not lock_file.exists(): + lock_file.write_text('provider = "postgresql"\n') + + # Create timestamp-based migration directory + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + migration_dir = migrations_dir / f"{timestamp}_baseline" + migration_dir.mkdir(parents=True, exist_ok=True) + + # Generate migration SQL + result = subprocess.run( + [ + "prisma", + "migrate", + "diff", + "--from-empty", + "--to-schema-datamodel", + str(schema_path), + "--script", + ], + capture_output=True, + text=True, + check=True, + ) + + # Write the SQL to migration.sql + migration_file = migration_dir / "migration.sql" + migration_file.write_text(result.stdout) + + print(f"Created baseline migration in {migration_dir}") + return True + + except subprocess.CalledProcessError as e: + print(f"Error running prisma command: {e.stderr}") + return False + except Exception as e: + print(f"Error creating baseline migration: {str(e)}") + return False + + +if __name__ == "__main__": + create_baseline() diff --git a/deploy/migrations/20250326162113_baseline/migration.sql b/deploy/migrations/20250326162113_baseline/migration.sql new file mode 100644 index 0000000000..fb8a44814f --- /dev/null +++ b/deploy/migrations/20250326162113_baseline/migration.sql @@ -0,0 +1,360 @@ +-- CreateTable +CREATE TABLE "LiteLLM_BudgetTable" ( + "budget_id" TEXT NOT NULL, + "max_budget" DOUBLE PRECISION, + "soft_budget" DOUBLE PRECISION, + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "model_max_budget" JSONB, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_BudgetTable_pkey" PRIMARY KEY ("budget_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_CredentialsTable" ( + "credential_id" TEXT NOT NULL, + "credential_name" TEXT NOT NULL, + "credential_values" JSONB NOT NULL, + "credential_info" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_CredentialsTable_pkey" PRIMARY KEY ("credential_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ProxyModelTable" ( + "model_id" TEXT NOT NULL, + "model_name" TEXT NOT NULL, + "litellm_params" JSONB NOT NULL, + "model_info" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_ProxyModelTable_pkey" PRIMARY KEY ("model_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_OrganizationTable" ( + "organization_id" TEXT NOT NULL, + "organization_alias" TEXT NOT NULL, + "budget_id" TEXT NOT NULL, + "metadata" JSONB NOT NULL DEFAULT '{}', + "models" TEXT[], + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "model_spend" JSONB NOT NULL DEFAULT '{}', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_OrganizationTable_pkey" PRIMARY KEY ("organization_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ModelTable" ( + "id" SERIAL NOT NULL, + "aliases" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_ModelTable_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_TeamTable" ( + "team_id" TEXT NOT NULL, + "team_alias" TEXT, + "organization_id" TEXT, + "admins" TEXT[], + "members" TEXT[], + "members_with_roles" JSONB NOT NULL DEFAULT '{}', + "metadata" JSONB NOT NULL DEFAULT '{}', + "max_budget" DOUBLE PRECISION, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "models" TEXT[], + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "blocked" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "model_id" INTEGER, + + CONSTRAINT "LiteLLM_TeamTable_pkey" PRIMARY KEY ("team_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_UserTable" ( + "user_id" TEXT NOT NULL, + "user_alias" TEXT, + "team_id" TEXT, + "sso_user_id" TEXT, + "organization_id" TEXT, + "password" TEXT, + "teams" TEXT[] DEFAULT ARRAY[]::TEXT[], + "user_role" TEXT, + "max_budget" DOUBLE PRECISION, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "user_email" TEXT, + "models" TEXT[], + "metadata" JSONB NOT NULL DEFAULT '{}', + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "LiteLLM_UserTable_pkey" PRIMARY KEY ("user_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_VerificationToken" ( + "token" TEXT NOT NULL, + "key_name" TEXT, + "key_alias" TEXT, + "soft_budget_cooldown" BOOLEAN NOT NULL DEFAULT false, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "expires" TIMESTAMP(3), + "models" TEXT[], + "aliases" JSONB NOT NULL DEFAULT '{}', + "config" JSONB NOT NULL DEFAULT '{}', + "user_id" TEXT, + "team_id" TEXT, + "permissions" JSONB NOT NULL DEFAULT '{}', + "max_parallel_requests" INTEGER, + "metadata" JSONB NOT NULL DEFAULT '{}', + "blocked" BOOLEAN, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "max_budget" DOUBLE PRECISION, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "budget_id" TEXT, + "organization_id" TEXT, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT, + + CONSTRAINT "LiteLLM_VerificationToken_pkey" PRIMARY KEY ("token") +); + +-- CreateTable +CREATE TABLE "LiteLLM_EndUserTable" ( + "user_id" TEXT NOT NULL, + "alias" TEXT, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "allowed_model_region" TEXT, + "default_model" TEXT, + "budget_id" TEXT, + "blocked" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "LiteLLM_EndUserTable_pkey" PRIMARY KEY ("user_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_Config" ( + "param_name" TEXT NOT NULL, + "param_value" JSONB, + + CONSTRAINT "LiteLLM_Config_pkey" PRIMARY KEY ("param_name") +); + +-- CreateTable +CREATE TABLE "LiteLLM_SpendLogs" ( + "request_id" TEXT NOT NULL, + "call_type" TEXT NOT NULL, + "api_key" TEXT NOT NULL DEFAULT '', + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "total_tokens" INTEGER NOT NULL DEFAULT 0, + "prompt_tokens" INTEGER NOT NULL DEFAULT 0, + "completion_tokens" INTEGER NOT NULL DEFAULT 0, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "completionStartTime" TIMESTAMP(3), + "model" TEXT NOT NULL DEFAULT '', + "model_id" TEXT DEFAULT '', + "model_group" TEXT DEFAULT '', + "custom_llm_provider" TEXT DEFAULT '', + "api_base" TEXT DEFAULT '', + "user" TEXT DEFAULT '', + "metadata" JSONB DEFAULT '{}', + "cache_hit" TEXT DEFAULT '', + "cache_key" TEXT DEFAULT '', + "request_tags" JSONB DEFAULT '[]', + "team_id" TEXT, + "end_user" TEXT, + "requester_ip_address" TEXT, + "messages" JSONB DEFAULT '{}', + "response" JSONB DEFAULT '{}', + + CONSTRAINT "LiteLLM_SpendLogs_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ErrorLogs" ( + "request_id" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "api_base" TEXT NOT NULL DEFAULT '', + "model_group" TEXT NOT NULL DEFAULT '', + "litellm_model_name" TEXT NOT NULL DEFAULT '', + "model_id" TEXT NOT NULL DEFAULT '', + "request_kwargs" JSONB NOT NULL DEFAULT '{}', + "exception_type" TEXT NOT NULL DEFAULT '', + "exception_string" TEXT NOT NULL DEFAULT '', + "status_code" TEXT NOT NULL DEFAULT '', + + CONSTRAINT "LiteLLM_ErrorLogs_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_UserNotifications" ( + "request_id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "models" TEXT[], + "justification" TEXT NOT NULL, + "status" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_UserNotifications_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_TeamMembership" ( + "user_id" TEXT NOT NULL, + "team_id" TEXT NOT NULL, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "budget_id" TEXT, + + CONSTRAINT "LiteLLM_TeamMembership_pkey" PRIMARY KEY ("user_id","team_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_OrganizationMembership" ( + "user_id" TEXT NOT NULL, + "organization_id" TEXT NOT NULL, + "user_role" TEXT, + "spend" DOUBLE PRECISION DEFAULT 0.0, + "budget_id" TEXT, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "LiteLLM_OrganizationMembership_pkey" PRIMARY KEY ("user_id","organization_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_InvitationLink" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "is_accepted" BOOLEAN NOT NULL DEFAULT false, + "accepted_at" TIMESTAMP(3), + "expires_at" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_InvitationLink_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_AuditLog" ( + "id" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "changed_by" TEXT NOT NULL DEFAULT '', + "changed_by_api_key" TEXT NOT NULL DEFAULT '', + "action" TEXT NOT NULL, + "table_name" TEXT NOT NULL, + "object_id" TEXT NOT NULL, + "before_value" JSONB, + "updated_values" JSONB, + + CONSTRAINT "LiteLLM_AuditLog_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_CredentialsTable_credential_name_key" ON "LiteLLM_CredentialsTable"("credential_name"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_TeamTable_model_id_key" ON "LiteLLM_TeamTable"("model_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_UserTable_sso_user_id_key" ON "LiteLLM_UserTable"("sso_user_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_SpendLogs_startTime_idx" ON "LiteLLM_SpendLogs"("startTime"); + +-- CreateIndex +CREATE INDEX "LiteLLM_SpendLogs_end_user_idx" ON "LiteLLM_SpendLogs"("end_user"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_OrganizationMembership_user_id_organization_id_key" ON "LiteLLM_OrganizationMembership"("user_id", "organization_id"); + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationTable" ADD CONSTRAINT "LiteLLM_OrganizationTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_model_id_fkey" FOREIGN KEY ("model_id") REFERENCES "LiteLLM_ModelTable"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_UserTable" ADD CONSTRAINT "LiteLLM_UserTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_EndUserTable" ADD CONSTRAINT "LiteLLM_EndUserTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamMembership" ADD CONSTRAINT "LiteLLM_TeamMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + diff --git a/deploy/migrations/migration_lock.toml b/deploy/migrations/migration_lock.toml new file mode 100644 index 0000000000..2fe25d87cc --- /dev/null +++ b/deploy/migrations/migration_lock.toml @@ -0,0 +1 @@ +provider = "postgresql" diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py new file mode 100644 index 0000000000..ea50688548 --- /dev/null +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -0,0 +1,62 @@ +import pytest +import os +import subprocess +from pathlib import Path +from pytest_postgresql import factories + +# Create postgresql fixture +postgresql_my_proc = factories.postgresql_proc(port=None) +postgresql_my = factories.postgresql("postgresql_my_proc") + + +@pytest.fixture(scope="function") +def schema_setup(postgresql_my): + """Fixture to provide a test postgres database""" + return postgresql_my + + +def test_schema_migration_check(schema_setup): + """Test to check if schema requires migration""" + # Set test database URL + test_db_url = f"postgresql://{schema_setup.info.user}:@{schema_setup.info.host}:{schema_setup.info.port}/{schema_setup.info.dbname}" + os.environ["DATABASE_URL"] = test_db_url + + deploy_dir = Path("./deploy") + migrations_dir = deploy_dir / "migrations" + + print(migrations_dir) + if not migrations_dir.exists() or not any(migrations_dir.iterdir()): + print("No existing migrations found - first migration needed") + pytest.fail("No existing migrations found - first migration needed") + + # If migrations exist, check for changes + result = subprocess.run( + ["prisma", "migrate", "status"], capture_output=True, text=True + ) + + status_output = result.stdout.lower() + needs_migration = any( + state in status_output for state in ["drift detected", "pending"] + ) + + if needs_migration: + print("Schema changes detected. New migration needed.") + # Show the differences + diff_result = subprocess.run( + [ + "prisma", + "migrate", + "diff", + "--from-migrations", + "--to-schema-datamodel", + "schema.prisma", + ], + capture_output=True, + text=True, + ) + print("Schema differences:") + print(diff_result.stdout) + else: + print("No schema changes detected. Migration not needed.") + + assert not needs_migration, "Schema changes detected - new migration required" From fb9abd40f30c72c777974d30674e0ecb9ae38218 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 16:25:08 -0700 Subject: [PATCH 087/244] ci(config.yml): add pytest-postgres to ci/cd --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a058144f6..91636c5625 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -555,6 +555,7 @@ jobs: pip install "diskcache==5.6.1" pip install "Pillow==10.3.0" pip install "jsonschema==4.22.0" + pip install "pytest-postgresql==7.0.1" - save_cache: paths: - ./venv From 37265c8b1c1bb88f7f7de2afe07474aa0b5c7c03 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 16:36:36 -0700 Subject: [PATCH 088/244] Add Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows (#9538) * ci: update github action * build(schema.prisma): enable daily user spend table allows storing aggregate view of user's daily spend * build(schema.prisma): add new daily user spend table * feat: working daily user spend tracking maintains an aggregate view for easier querying in high traffic * setup_google_dns * ci: update ci yaml --------- Co-authored-by: Ishaan Jaff --- litellm/proxy/_types.py | 12 ++ litellm/proxy/proxy_server.py | 3 + litellm/proxy/schema.prisma | 23 ++++ litellm/proxy/utils.py | 231 +++++++++++++++++++++++++++++++++- schema.prisma | 22 ++++ 5 files changed, 290 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 220a0d5ddb..ebe7d1e955 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2646,3 +2646,15 @@ class DefaultInternalUserParams(LiteLLMPydanticObjectBase): models: Optional[List[str]] = Field( default=None, description="Default list of models that new users can access" ) + + +class DailyUserSpendTransaction(TypedDict): + user_id: str + date: str + api_key: str + model: str + model_group: Optional[str] + custom_llm_provider: Optional[str] + prompt_tokens: int + completion_tokens: int + spend: float diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 568253735c..eefc0bdd06 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -558,6 +558,7 @@ async def proxy_startup_event(app: FastAPI): proxy_batch_write_at=proxy_batch_write_at, proxy_logging_obj=proxy_logging_obj, ) + ## [Optional] Initialize dd tracer ProxyStartupEvent._init_dd_tracer() @@ -914,6 +915,8 @@ def _set_spend_logs_payload( prisma_client.spend_log_transactions.append(payload) elif prisma_client is not None: prisma_client.spend_log_transactions.append(payload) + + prisma_client.add_spend_log_transaction_to_daily_user_transaction(payload.copy()) return prisma_client diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index e453e74b46..9269e89014 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -313,3 +313,26 @@ model LiteLLM_AuditLog { before_value Json? // value of the row updated_values Json? // value of the row after change } + + +// Track daily user spend metrics per model and key +model LiteLLM_DailyUserSpend { + id String @id @default(uuid()) + user_id String + date String + api_key String // Hashed API Token + model String // The specific model used + model_group String? // public model_name / model_group + custom_llm_provider String? // The LLM provider (e.g., "openai", "anthropic") + prompt_tokens Int @default(0) + completion_tokens Int @default(0) + spend Float @default(0.0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@unique([user_id, date, api_key, model, custom_llm_provider]) + @@index([date]) + @@index([user_id]) + @@index([api_key]) + @@index([model]) +} diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 0e7ae45596..7f1ac814a8 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -10,13 +10,15 @@ import traceback from datetime import datetime, timedelta from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from typing import TYPE_CHECKING, Any, List, Literal, Optional, Union, overload +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, overload from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, CommonProxyErrors, + DailyUserSpendTransaction, ProxyErrorTypes, ProxyException, + SpendLogsPayload, ) from litellm.types.guardrails import GuardrailEventHooks @@ -1103,6 +1105,7 @@ class PrismaClient: team_member_list_transactons: dict = {} # key is ["team_id" + "user_id"] org_list_transactons: dict = {} spend_log_transactions: List = [] + daily_user_spend_transactions: Dict[str, DailyUserSpendTransaction] = {} def __init__( self, @@ -1140,6 +1143,61 @@ class PrismaClient: ) # Client to connect to Prisma db verbose_proxy_logger.debug("Success - Created Prisma Client") + def add_spend_log_transaction_to_daily_user_transaction( + self, payload: Union[dict, SpendLogsPayload] + ): + """ + Add a spend log transaction to the daily user transaction list + + Key = @@unique([user_id, date, api_key, model, custom_llm_provider]) ) + + If key exists, update the transaction with the new spend and usage + """ + expected_keys = ["user", "startTime", "api_key", "model", "custom_llm_provider"] + if not all(key in payload for key in expected_keys): + verbose_proxy_logger.debug( + f"Missing expected keys: {expected_keys}, in payload, skipping from daily_user_spend_transactions" + ) + return + + if isinstance(payload["startTime"], datetime): + start_time = payload["startTime"].isoformat() + date = start_time.split("T")[0] + elif isinstance(payload["startTime"], str): + date = payload["startTime"].split("T")[0] + else: + verbose_proxy_logger.debug( + f"Invalid start time: {payload['startTime']}, skipping from daily_user_spend_transactions" + ) + return + try: + daily_transaction_key = f"{payload['user']}_{date}_{payload['api_key']}_{payload['model']}_{payload['custom_llm_provider']}" + if daily_transaction_key in self.daily_user_spend_transactions: + daily_transaction = self.daily_user_spend_transactions[ + daily_transaction_key + ] + daily_transaction["spend"] += payload["spend"] + daily_transaction["prompt_tokens"] += payload["prompt_tokens"] + daily_transaction["completion_tokens"] += payload["completion_tokens"] + else: + daily_transaction = DailyUserSpendTransaction( + user_id=payload["user"], + date=date, + api_key=payload["api_key"], + model=payload["model"], + model_group=payload["model_group"], + custom_llm_provider=payload["custom_llm_provider"], + prompt_tokens=payload["prompt_tokens"], + completion_tokens=payload["completion_tokens"], + spend=payload["spend"], + ) + + self.daily_user_spend_transactions[daily_transaction_key] = ( + daily_transaction + ) + except Exception as e: + raise e + def hash_token(self, token: str): # Hash the string using SHA-256 hashed_token = hashlib.sha256(token.encode()).hexdigest() @@ -2476,6 +2534,116 @@ class ProxyUpdateSpend: e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj ) + @staticmethod + async def update_daily_user_spend( + n_retry_times: int, + prisma_client: PrismaClient, + proxy_logging_obj: ProxyLogging, + ): + """ + Batch job to update LiteLLM_DailyUserSpend table using in-memory daily_spend_transactions + """ + BATCH_SIZE = ( + 100 # Number of aggregated records to update in each database operation + ) + start_time = time.time() + + try: + for i in range(n_retry_times + 1): + try: + # Get transactions to process + transactions_to_process = dict( + list(prisma_client.daily_user_spend_transactions.items())[ + :BATCH_SIZE + ] + ) + + if len(transactions_to_process) == 0: + verbose_proxy_logger.debug( + "No new transactions to process for daily spend update" + ) + break + + # Update DailyUserSpend table in batches + async with prisma_client.db.batch_() as batcher: + for _, transaction in transactions_to_process.items(): + user_id = transaction.get("user_id") + if not user_id: # Skip if no user_id + continue + + batcher.litellm_dailyuserspend.upsert( + where={ + "user_id_date_api_key_model_custom_llm_provider": { + "user_id": user_id, + "date": transaction["date"], + "api_key": transaction["api_key"], + "model": transaction["model"], + "custom_llm_provider": transaction.get( + "custom_llm_provider" + ), + } + }, + data={ + "create": { + "user_id": user_id, + "date": transaction["date"], + "api_key": transaction["api_key"], + "model": transaction["model"], + "model_group": transaction.get("model_group"), + "custom_llm_provider": transaction.get( + "custom_llm_provider" + ), + "prompt_tokens": transaction["prompt_tokens"], + "completion_tokens": transaction[ + "completion_tokens" + ], + "spend": transaction["spend"], + }, + "update": { + "prompt_tokens": { + "increment": transaction["prompt_tokens"] + }, + "completion_tokens": { + "increment": transaction[ + "completion_tokens" + ] + }, + "spend": {"increment": transaction["spend"]}, + }, + }, + ) + + verbose_proxy_logger.info( + f"Processed {len(transactions_to_process)} daily spend transactions in {time.time() - start_time:.2f}s" + ) + + # Remove processed transactions + for key in transactions_to_process.keys(): + prisma_client.daily_user_spend_transactions.pop(key, None) + + verbose_proxy_logger.debug( + f"Processed {len(transactions_to_process)} daily spend transactions in {time.time() - start_time:.2f}s" + ) + break + + except DB_CONNECTION_ERROR_TYPES as e: + if i >= n_retry_times: + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + await asyncio.sleep(2**i) # Exponential backoff + + except Exception as e: + # Remove processed transactions even if there was an error + if "transactions_to_process" in locals(): + for key in transactions_to_process.keys(): # type: ignore + prisma_client.daily_user_spend_transactions.pop(key, None) + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) + @staticmethod def disable_spend_updates() -> bool: """ @@ -2716,6 +2884,20 @@ async def update_spend( # noqa: PLR0915 db_writer_client=db_writer_client, ) + ### UPDATE DAILY USER SPEND ### + verbose_proxy_logger.debug( + "Daily User Spend transactions: {}".format( + len(prisma_client.daily_user_spend_transactions) + ) + ) + + if len(prisma_client.daily_user_spend_transactions) > 0: + await ProxyUpdateSpend.update_daily_user_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + ) + def _raise_failed_update_spend_exception( e: Exception, start_time: float, proxy_logging_obj: ProxyLogging @@ -2911,3 +3093,50 @@ def _premium_user_check(): "error": f"This feature is only available for LiteLLM Enterprise users. {CommonProxyErrors.not_premium_user.value}" }, ) + + +async def _update_daily_spend_batch(prisma_client, spend_aggregates): + """Helper function to update daily spend in batches""" + async with prisma_client.db.batch_() as batcher: + for ( + user_id, + date, + api_key, + model, + model_group, + provider, + ), metrics in spend_aggregates.items(): + if not user_id: # Skip if no user_id + continue + + batcher.litellm_dailyuserspend.upsert( + where={ + "user_id_date_api_key_model_custom_llm_provider": { + "user_id": user_id, + "date": date, + "api_key": api_key, + "model": model, + "custom_llm_provider": provider, + } + }, + data={ + "create": { + "user_id": user_id, + "date": date, + "api_key": api_key, + "model": model, + "model_group": model_group, + "custom_llm_provider": provider, + "prompt_tokens": metrics["prompt_tokens"], + "completion_tokens": metrics["completion_tokens"], + "spend": metrics["spend"], + }, + "update": { + "prompt_tokens": {"increment": metrics["prompt_tokens"]}, + "completion_tokens": { + "increment": metrics["completion_tokens"] + }, + "spend": {"increment": metrics["spend"]}, + }, + }, + ) diff --git a/schema.prisma b/schema.prisma index e453e74b46..3312b26354 100644 --- a/schema.prisma +++ b/schema.prisma @@ -313,3 +313,25 @@ model LiteLLM_AuditLog { before_value Json? // value of the row updated_values Json? // value of the row after change } + +// Track daily user spend metrics per model and key +model LiteLLM_DailyUserSpend { + id String @id @default(uuid()) + user_id String + date String + api_key String + model String + model_group String? + custom_llm_provider String? + prompt_tokens Int @default(0) + completion_tokens Int @default(0) + spend Float @default(0.0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@unique([user_id, date, api_key, model, custom_llm_provider]) + @@index([date]) + @@index([user_id]) + @@index([api_key]) + @@index([model]) +} From d4adc9764bea476cd7fb45a0fbcc92aabf5e77e6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 16:59:50 -0700 Subject: [PATCH 089/244] test(test_db_schema_migration.py): ci/cd test to enforce schema migrations are documented in .sql files --- .../test_db_schema_migration.py | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py index ea50688548..77052124fb 100644 --- a/tests/proxy_unit_tests/test_db_schema_migration.py +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -3,6 +3,8 @@ import os import subprocess from pathlib import Path from pytest_postgresql import factories +import shutil +import tempfile # Create postgresql fixture postgresql_my_proc = factories.postgresql_proc(port=None) @@ -19,44 +21,63 @@ def test_schema_migration_check(schema_setup): """Test to check if schema requires migration""" # Set test database URL test_db_url = f"postgresql://{schema_setup.info.user}:@{schema_setup.info.host}:{schema_setup.info.port}/{schema_setup.info.dbname}" + # test_db_url = "postgresql://neondb_owner:npg_JiZPS0DAhRn4@ep-delicate-wave-a55cvbuc.us-east-2.aws.neon.tech/neondb?sslmode=require" os.environ["DATABASE_URL"] = test_db_url - deploy_dir = Path("./deploy") - migrations_dir = deploy_dir / "migrations" + deploy_dir = Path("../../deploy") + source_migrations_dir = deploy_dir / "migrations" + schema_path = Path("../../schema.prisma") - print(migrations_dir) - if not migrations_dir.exists() or not any(migrations_dir.iterdir()): - print("No existing migrations found - first migration needed") - pytest.fail("No existing migrations found - first migration needed") + # Create temporary migrations directory next to schema.prisma + temp_migrations_dir = schema_path.parent / "migrations" - # If migrations exist, check for changes - result = subprocess.run( - ["prisma", "migrate", "status"], capture_output=True, text=True - ) + try: + # Copy migrations to correct location + if temp_migrations_dir.exists(): + shutil.rmtree(temp_migrations_dir) + shutil.copytree(source_migrations_dir, temp_migrations_dir) - status_output = result.stdout.lower() - needs_migration = any( - state in status_output for state in ["drift detected", "pending"] - ) + if not temp_migrations_dir.exists() or not any(temp_migrations_dir.iterdir()): + print("No existing migrations found - first migration needed") + pytest.fail("No existing migrations found - first migration needed") - if needs_migration: - print("Schema changes detected. New migration needed.") - # Show the differences + # Apply all existing migrations + subprocess.run( + ["prisma", "migrate", "deploy", "--schema", str(schema_path)], check=True + ) + + # Compare current database state against schema diff_result = subprocess.run( [ "prisma", "migrate", "diff", - "--from-migrations", + "--from-url", + test_db_url, "--to-schema-datamodel", - "schema.prisma", + str(schema_path), + "--script", # Show the SQL diff + "--exit-code", # Return exit code 2 if there are differences ], capture_output=True, text=True, ) - print("Schema differences:") - print(diff_result.stdout) - else: - print("No schema changes detected. Migration not needed.") - assert not needs_migration, "Schema changes detected - new migration required" + print("Exit code:", diff_result.returncode) + print("Stdout:", diff_result.stdout) + print("Stderr:", diff_result.stderr) + + if diff_result.returncode == 2: + print("Schema changes detected. New migration needed.") + print("Schema differences:") + print(diff_result.stdout) + pytest.fail( + "Schema changes detected - new migration required. Run 'prisma migrate dev' to create new migration." + ) + else: + print("No schema changes detected. Migration not needed.") + + finally: + # Clean up: remove temporary migrations directory + if temp_migrations_dir.exists(): + shutil.rmtree(temp_migrations_dir) From 72c0ad419fb24fbafac9b095b2dcbd2475b43ffe Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 17:11:25 -0700 Subject: [PATCH 090/244] build(migration.sql): add migration file for new dailyusertable documents prisma db changes --- ci_cd/run_migration.py | 96 +++++++++++++++++++ .../migration.sql | 33 +++++++ 2 files changed, 129 insertions(+) create mode 100644 ci_cd/run_migration.py create mode 100644 deploy/migrations/20250326171002_add_daily_user_table/migration.sql diff --git a/ci_cd/run_migration.py b/ci_cd/run_migration.py new file mode 100644 index 0000000000..69b4308e57 --- /dev/null +++ b/ci_cd/run_migration.py @@ -0,0 +1,96 @@ +import os +import subprocess +from pathlib import Path +from datetime import datetime +import testing.postgresql +import shutil + + +def create_migration(migration_name: str = None): + """ + Create a new migration SQL file in deploy/migrations directory by comparing + current database state with schema + + Args: + migration_name (str): Name for the migration + """ + try: + # Get paths + root_dir = Path(__file__).parent.parent + deploy_dir = root_dir / "deploy" + migrations_dir = deploy_dir / "migrations" + schema_path = root_dir / "schema.prisma" + + # Create temporary PostgreSQL database + with testing.postgresql.Postgresql() as postgresql: + db_url = postgresql.url() + + # Create temporary migrations directory next to schema.prisma + temp_migrations_dir = schema_path.parent / "migrations" + + try: + # Copy existing migrations to temp directory + if temp_migrations_dir.exists(): + shutil.rmtree(temp_migrations_dir) + shutil.copytree(migrations_dir, temp_migrations_dir) + + # Apply existing migrations to temp database + os.environ["DATABASE_URL"] = db_url + subprocess.run( + ["prisma", "migrate", "deploy", "--schema", str(schema_path)], + check=True, + ) + + # Generate diff between current database and schema + result = subprocess.run( + [ + "prisma", + "migrate", + "diff", + "--from-url", + db_url, + "--to-schema-datamodel", + str(schema_path), + "--script", + ], + capture_output=True, + text=True, + check=True, + ) + + if result.stdout.strip(): + # Generate timestamp and create migration directory + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + migration_name = migration_name or "unnamed_migration" + migration_dir = migrations_dir / f"{timestamp}_{migration_name}" + migration_dir.mkdir(parents=True, exist_ok=True) + + # Write the SQL to migration.sql + migration_file = migration_dir / "migration.sql" + migration_file.write_text(result.stdout) + + print(f"Created migration in {migration_dir}") + return True + else: + print("No schema changes detected. Migration not needed.") + return False + + finally: + # Clean up: remove temporary migrations directory + if temp_migrations_dir.exists(): + shutil.rmtree(temp_migrations_dir) + + except subprocess.CalledProcessError as e: + print(f"Error generating migration: {e.stderr}") + return False + except Exception as e: + print(f"Error creating migration: {str(e)}") + return False + + +if __name__ == "__main__": + # If running directly, can optionally pass migration name as argument + import sys + + migration_name = sys.argv[1] if len(sys.argv) > 1 else None + create_migration(migration_name) diff --git a/deploy/migrations/20250326171002_add_daily_user_table/migration.sql b/deploy/migrations/20250326171002_add_daily_user_table/migration.sql new file mode 100644 index 0000000000..3379d8e9fd --- /dev/null +++ b/deploy/migrations/20250326171002_add_daily_user_table/migration.sql @@ -0,0 +1,33 @@ +-- CreateTable +CREATE TABLE "LiteLLM_DailyUserSpend" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "date" TEXT NOT NULL, + "api_key" TEXT NOT NULL, + "model" TEXT NOT NULL, + "model_group" TEXT, + "custom_llm_provider" TEXT, + "prompt_tokens" INTEGER NOT NULL DEFAULT 0, + "completion_tokens" INTEGER NOT NULL DEFAULT 0, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_DailyUserSpend_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_date_idx" ON "LiteLLM_DailyUserSpend"("date"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_user_id_idx" ON "LiteLLM_DailyUserSpend"("user_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_api_key_idx" ON "LiteLLM_DailyUserSpend"("api_key"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_model_idx" ON "LiteLLM_DailyUserSpend"("model"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyUserSpend_user_id_date_api_key_model_custom_ll_key" ON "LiteLLM_DailyUserSpend"("user_id", "date", "api_key", "model", "custom_llm_provider"); + From 04490c99d71d3ff948300bbcac85c1e8e43c9f4a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 17:12:09 -0700 Subject: [PATCH 091/244] test: fix test --- tests/proxy_unit_tests/test_db_schema_migration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py index 77052124fb..59367aa9cb 100644 --- a/tests/proxy_unit_tests/test_db_schema_migration.py +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -24,9 +24,9 @@ def test_schema_migration_check(schema_setup): # test_db_url = "postgresql://neondb_owner:npg_JiZPS0DAhRn4@ep-delicate-wave-a55cvbuc.us-east-2.aws.neon.tech/neondb?sslmode=require" os.environ["DATABASE_URL"] = test_db_url - deploy_dir = Path("../../deploy") + deploy_dir = Path("./deploy") source_migrations_dir = deploy_dir / "migrations" - schema_path = Path("../../schema.prisma") + schema_path = Path("./schema.prisma") # Create temporary migrations directory next to schema.prisma temp_migrations_dir = schema_path.parent / "migrations" From 4351c77253a8355fdee17844e91eed13ed7effc4 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 17:26:25 -0700 Subject: [PATCH 092/244] Support Gemini audio token cost tracking + fix openai audio input token cost tracking (#9535) * fix(vertex_and_google_ai_studio_gemini.py): log gemini audio tokens in usage object enables accurate cost tracking * refactor(vertex_ai/cost_calculator.py): refactor 128k+ token cost calculation to only run if model info has it Google has moved away from this for gemini-2.0 models * refactor(vertex_ai/cost_calculator.py): migrate to usage object for more flexible data passthrough * fix(llm_cost_calc/utils.py): support audio token cost tracking in generic cost per token enables vertex ai cost tracking to work with audio tokens * fix(llm_cost_calc/utils.py): default to total prompt tokens if text tokens field not set * refactor(llm_cost_calc/utils.py): move openai cost tracking to generic cost per token more consistent behaviour across providers * test: add unit test for gemini audio token cost calculation * ci: bump ci config * test: fix test --- litellm/cost_calculator.py | 6 +- .../litellm_core_utils/llm_cost_calc/utils.py | 81 ++++++++++-- litellm/llms/openai/cost_calculation.py | 86 ++++++------- litellm/llms/vertex_ai/cost_calculator.py | 115 +++++++++++------- .../vertex_and_google_ai_studio_gemini.py | 18 ++- litellm/types/llms/vertex_ai.py | 6 + tests/litellm/test_cost_calculator.py | 39 ++++++ tests/local_testing/test_completion_cost.py | 19 +-- 8 files changed, 253 insertions(+), 117 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index f5731618a3..7b95c45ac7 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -275,15 +275,13 @@ def cost_per_token( # noqa: PLR0915 custom_llm_provider=custom_llm_provider, prompt_characters=prompt_characters, completion_characters=completion_characters, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage_block, ) elif cost_router == "cost_per_token": return google_cost_per_token( model=model_without_prefix, custom_llm_provider=custom_llm_provider, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage_block, ) elif custom_llm_provider == "anthropic": return anthropic_cost_per_token(model=model, usage=usage_block) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 7af3a26d2e..6b778344d7 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -1,7 +1,7 @@ # What is this? ## Helper utilities for cost_per_token() -from typing import Optional, Tuple +from typing import Optional, Tuple, cast import litellm from litellm import verbose_logger @@ -143,26 +143,50 @@ def generic_cost_per_token( ### Cost of processing (non-cache hit + cache hit) + Cost of cache-writing (cache writing) prompt_cost = 0.0 ### PROCESSING COST - non_cache_hit_tokens = usage.prompt_tokens + text_tokens = usage.prompt_tokens cache_hit_tokens = 0 - if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens: - cache_hit_tokens = usage.prompt_tokens_details.cached_tokens - non_cache_hit_tokens = non_cache_hit_tokens - cache_hit_tokens + audio_tokens = 0 + if usage.prompt_tokens_details: + cache_hit_tokens = ( + cast( + Optional[int], getattr(usage.prompt_tokens_details, "cached_tokens", 0) + ) + or 0 + ) + text_tokens = ( + cast( + Optional[int], getattr(usage.prompt_tokens_details, "text_tokens", None) + ) + or 0 # default to prompt tokens, if this field is not set + ) + audio_tokens = ( + cast(Optional[int], getattr(usage.prompt_tokens_details, "audio_tokens", 0)) + or 0 + ) + + ## EDGE CASE - text tokens not set inside PromptTokensDetails + if text_tokens == 0: + text_tokens = usage.prompt_tokens - cache_hit_tokens - audio_tokens prompt_base_cost = _get_prompt_token_base_cost(model_info=model_info, usage=usage) - prompt_cost = float(non_cache_hit_tokens) * prompt_base_cost + prompt_cost = float(text_tokens) * prompt_base_cost _cache_read_input_token_cost = model_info.get("cache_read_input_token_cost") + + ### CACHE READ COST if ( _cache_read_input_token_cost is not None - and usage.prompt_tokens_details - and usage.prompt_tokens_details.cached_tokens + and cache_hit_tokens is not None + and cache_hit_tokens > 0 ): - prompt_cost += ( - float(usage.prompt_tokens_details.cached_tokens) - * _cache_read_input_token_cost - ) + prompt_cost += float(cache_hit_tokens) * _cache_read_input_token_cost + + ### AUDIO COST + + audio_token_cost = model_info.get("input_cost_per_audio_token") + if audio_token_cost is not None and audio_tokens is not None and audio_tokens > 0: + prompt_cost += float(audio_tokens) * audio_token_cost ### CACHE WRITING COST _cache_creation_input_token_cost = model_info.get("cache_creation_input_token_cost") @@ -175,6 +199,37 @@ def generic_cost_per_token( completion_base_cost = _get_completion_token_base_cost( model_info=model_info, usage=usage ) - completion_cost = usage["completion_tokens"] * completion_base_cost + text_tokens = usage.completion_tokens + audio_tokens = 0 + if usage.completion_tokens_details is not None: + audio_tokens = ( + cast( + Optional[int], + getattr(usage.completion_tokens_details, "audio_tokens", 0), + ) + or 0 + ) + text_tokens = ( + cast( + Optional[int], + getattr(usage.completion_tokens_details, "text_tokens", None), + ) + or usage.completion_tokens # default to completion tokens, if this field is not set + ) + + ## TEXT COST + completion_cost = float(text_tokens) * completion_base_cost + + _output_cost_per_audio_token: Optional[float] = model_info.get( + "output_cost_per_audio_token" + ) + + ## AUDIO COST + if ( + _output_cost_per_audio_token is not None + and audio_tokens is not None + and audio_tokens > 0 + ): + completion_cost += float(audio_tokens) * _output_cost_per_audio_token return prompt_cost, completion_cost diff --git a/litellm/llms/openai/cost_calculation.py b/litellm/llms/openai/cost_calculation.py index 0c26fd7448..304c444e37 100644 --- a/litellm/llms/openai/cost_calculation.py +++ b/litellm/llms/openai/cost_calculation.py @@ -6,6 +6,7 @@ Helper util for handling openai-specific cost calculation from typing import Literal, Optional, Tuple from litellm._logging import verbose_logger +from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token from litellm.types.utils import CallTypes, Usage from litellm.utils import get_model_info @@ -28,52 +29,53 @@ def cost_per_token(model: str, usage: Usage) -> Tuple[float, float]: Returns: Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd """ - ## GET MODEL INFO - model_info = get_model_info(model=model, custom_llm_provider="openai") ## CALCULATE INPUT COST - ### Non-cached text tokens - non_cached_text_tokens = usage.prompt_tokens - cached_tokens: Optional[int] = None - if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens: - cached_tokens = usage.prompt_tokens_details.cached_tokens - non_cached_text_tokens = non_cached_text_tokens - cached_tokens - prompt_cost: float = non_cached_text_tokens * model_info["input_cost_per_token"] - ## Prompt Caching cost calculation - if model_info.get("cache_read_input_token_cost") is not None and cached_tokens: - # Note: We read ._cache_read_input_tokens from the Usage - since cost_calculator.py standardizes the cache read tokens on usage._cache_read_input_tokens - prompt_cost += cached_tokens * ( - model_info.get("cache_read_input_token_cost", 0) or 0 - ) + return generic_cost_per_token( + model=model, usage=usage, custom_llm_provider="openai" + ) + # ### Non-cached text tokens + # non_cached_text_tokens = usage.prompt_tokens + # cached_tokens: Optional[int] = None + # if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens: + # cached_tokens = usage.prompt_tokens_details.cached_tokens + # non_cached_text_tokens = non_cached_text_tokens - cached_tokens + # prompt_cost: float = non_cached_text_tokens * model_info["input_cost_per_token"] + # ## Prompt Caching cost calculation + # if model_info.get("cache_read_input_token_cost") is not None and cached_tokens: + # # Note: We read ._cache_read_input_tokens from the Usage - since cost_calculator.py standardizes the cache read tokens on usage._cache_read_input_tokens + # prompt_cost += cached_tokens * ( + # model_info.get("cache_read_input_token_cost", 0) or 0 + # ) - _audio_tokens: Optional[int] = ( - usage.prompt_tokens_details.audio_tokens - if usage.prompt_tokens_details is not None - else None - ) - _audio_cost_per_token: Optional[float] = model_info.get( - "input_cost_per_audio_token" - ) - if _audio_tokens is not None and _audio_cost_per_token is not None: - audio_cost: float = _audio_tokens * _audio_cost_per_token - prompt_cost += audio_cost + # _audio_tokens: Optional[int] = ( + # usage.prompt_tokens_details.audio_tokens + # if usage.prompt_tokens_details is not None + # else None + # ) + # _audio_cost_per_token: Optional[float] = model_info.get( + # "input_cost_per_audio_token" + # ) + # if _audio_tokens is not None and _audio_cost_per_token is not None: + # audio_cost: float = _audio_tokens * _audio_cost_per_token + # prompt_cost += audio_cost - ## CALCULATE OUTPUT COST - completion_cost: float = ( - usage["completion_tokens"] * model_info["output_cost_per_token"] - ) - _output_cost_per_audio_token: Optional[float] = model_info.get( - "output_cost_per_audio_token" - ) - _output_audio_tokens: Optional[int] = ( - usage.completion_tokens_details.audio_tokens - if usage.completion_tokens_details is not None - else None - ) - if _output_cost_per_audio_token is not None and _output_audio_tokens is not None: - audio_cost = _output_audio_tokens * _output_cost_per_audio_token - completion_cost += audio_cost + # ## CALCULATE OUTPUT COST + # completion_cost: float = ( + # usage["completion_tokens"] * model_info["output_cost_per_token"] + # ) + # _output_cost_per_audio_token: Optional[float] = model_info.get( + # "output_cost_per_audio_token" + # ) + # _output_audio_tokens: Optional[int] = ( + # usage.completion_tokens_details.audio_tokens + # if usage.completion_tokens_details is not None + # else None + # ) + # if _output_cost_per_audio_token is not None and _output_audio_tokens is not None: + # audio_cost = _output_audio_tokens * _output_cost_per_audio_token + # completion_cost += audio_cost - return prompt_cost, completion_cost + # return prompt_cost, completion_cost def cost_per_second( diff --git a/litellm/llms/vertex_ai/cost_calculator.py b/litellm/llms/vertex_ai/cost_calculator.py index fd23886045..54efc62589 100644 --- a/litellm/llms/vertex_ai/cost_calculator.py +++ b/litellm/llms/vertex_ai/cost_calculator.py @@ -4,7 +4,11 @@ from typing import Literal, Optional, Tuple, Union import litellm from litellm import verbose_logger -from litellm.litellm_core_utils.llm_cost_calc.utils import _is_above_128k +from litellm.litellm_core_utils.llm_cost_calc.utils import ( + _is_above_128k, + generic_cost_per_token, +) +from litellm.types.utils import ModelInfo, Usage """ Gemini pricing covers: @@ -20,7 +24,7 @@ Vertex AI -> character based pricing Google AI Studio -> token based pricing """ -models_without_dynamic_pricing = ["gemini-1.0-pro", "gemini-pro"] +models_without_dynamic_pricing = ["gemini-1.0-pro", "gemini-pro", "gemini-2"] def cost_router( @@ -46,14 +50,15 @@ def cost_router( call_type == "embedding" or call_type == "aembedding" ): return "cost_per_token" + elif custom_llm_provider == "vertex_ai" and ("gemini-2" in model): + return "cost_per_token" return "cost_per_character" def cost_per_character( model: str, custom_llm_provider: str, - prompt_tokens: float, - completion_tokens: float, + usage: Usage, prompt_characters: Optional[float] = None, completion_characters: Optional[float] = None, ) -> Tuple[float, float]: @@ -86,8 +91,7 @@ def cost_per_character( prompt_cost, _ = cost_per_token( model=model, custom_llm_provider=custom_llm_provider, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage, ) else: try: @@ -124,8 +128,7 @@ def cost_per_character( prompt_cost, _ = cost_per_token( model=model, custom_llm_provider=custom_llm_provider, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage, ) ## CALCULATE OUTPUT COST @@ -133,10 +136,10 @@ def cost_per_character( _, completion_cost = cost_per_token( model=model, custom_llm_provider=custom_llm_provider, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage, ) else: + completion_tokens = usage.completion_tokens try: if ( _is_above_128k(tokens=completion_characters * 4) # 1 token = 4 char @@ -172,18 +175,54 @@ def cost_per_character( _, completion_cost = cost_per_token( model=model, custom_llm_provider=custom_llm_provider, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, + usage=usage, ) return prompt_cost, completion_cost +def _handle_128k_pricing( + model_info: ModelInfo, + usage: Usage, +) -> Tuple[float, float]: + ## CALCULATE INPUT COST + input_cost_per_token_above_128k_tokens = model_info.get( + "input_cost_per_token_above_128k_tokens" + ) + output_cost_per_token_above_128k_tokens = model_info.get( + "output_cost_per_token_above_128k_tokens" + ) + + prompt_tokens = usage.prompt_tokens + completion_tokens = usage.completion_tokens + + if ( + _is_above_128k(tokens=prompt_tokens) + and input_cost_per_token_above_128k_tokens is not None + ): + prompt_cost = prompt_tokens * input_cost_per_token_above_128k_tokens + else: + prompt_cost = prompt_tokens * model_info["input_cost_per_token"] + + ## CALCULATE OUTPUT COST + output_cost_per_token_above_128k_tokens = model_info.get( + "output_cost_per_token_above_128k_tokens" + ) + if ( + _is_above_128k(tokens=completion_tokens) + and output_cost_per_token_above_128k_tokens is not None + ): + completion_cost = completion_tokens * output_cost_per_token_above_128k_tokens + else: + completion_cost = completion_tokens * model_info["output_cost_per_token"] + + return prompt_cost, completion_cost + + def cost_per_token( model: str, custom_llm_provider: str, - prompt_tokens: float, - completion_tokens: float, + usage: Usage, ) -> Tuple[float, float]: """ Calculates the cost per token for a given model, prompt tokens, and completion tokens. @@ -205,38 +244,24 @@ def cost_per_token( model=model, custom_llm_provider=custom_llm_provider ) - ## CALCULATE INPUT COST + ## HANDLE 128k+ PRICING + input_cost_per_token_above_128k_tokens = model_info.get( + "input_cost_per_token_above_128k_tokens" + ) + output_cost_per_token_above_128k_tokens = model_info.get( + "output_cost_per_token_above_128k_tokens" + ) if ( - _is_above_128k(tokens=prompt_tokens) - and model not in models_without_dynamic_pricing + input_cost_per_token_above_128k_tokens is not None + or output_cost_per_token_above_128k_tokens is not None ): - assert ( - "input_cost_per_token_above_128k_tokens" in model_info - and model_info["input_cost_per_token_above_128k_tokens"] is not None - ), "model info for model={} does not have pricing for > 128k tokens\nmodel_info={}".format( - model, model_info + return _handle_128k_pricing( + model_info=model_info, + usage=usage, ) - prompt_cost = ( - prompt_tokens * model_info["input_cost_per_token_above_128k_tokens"] - ) - else: - prompt_cost = prompt_tokens * model_info["input_cost_per_token"] - ## CALCULATE OUTPUT COST - if ( - _is_above_128k(tokens=completion_tokens) - and model not in models_without_dynamic_pricing - ): - assert ( - "output_cost_per_token_above_128k_tokens" in model_info - and model_info["output_cost_per_token_above_128k_tokens"] is not None - ), "model info for model={} does not have pricing for > 128k tokens\nmodel_info={}".format( - model, model_info - ) - completion_cost = ( - completion_tokens * model_info["output_cost_per_token_above_128k_tokens"] - ) - else: - completion_cost = completion_tokens * model_info["output_cost_per_token"] - - return prompt_cost, completion_cost + return generic_cost_per_token( + model=model, + custom_llm_provider=custom_llm_provider, + usage=usage, + ) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 2931b1b3a8..90c66f69a3 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -643,16 +643,25 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): completion_response: GenerateContentResponseBody, ) -> Usage: cached_tokens: Optional[int] = None + audio_tokens: Optional[int] = None + text_tokens: Optional[int] = None prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None if "cachedContentTokenCount" in completion_response["usageMetadata"]: cached_tokens = completion_response["usageMetadata"][ "cachedContentTokenCount" ] + if "promptTokensDetails" in completion_response["usageMetadata"]: + for detail in completion_response["usageMetadata"]["promptTokensDetails"]: + if detail["modality"] == "AUDIO": + audio_tokens = detail["tokenCount"] + elif detail["modality"] == "TEXT": + text_tokens = detail["tokenCount"] - if cached_tokens is not None: - prompt_tokens_details = PromptTokensDetailsWrapper( - cached_tokens=cached_tokens, - ) + prompt_tokens_details = PromptTokensDetailsWrapper( + cached_tokens=cached_tokens, + audio_tokens=audio_tokens, + text_tokens=text_tokens, + ) ## GET USAGE ## usage = Usage( prompt_tokens=completion_response["usageMetadata"].get( @@ -791,6 +800,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): model_response.choices.append(choice) usage = self._calculate_usage(completion_response=completion_response) + setattr(model_response, "usage", usage) ## ADD GROUNDING METADATA ## diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 7024909a34..ed6bcdb346 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -179,11 +179,17 @@ class TTL(TypedDict, total=False): nano: float +class PromptTokensDetails(TypedDict): + modality: Literal["TEXT", "AUDIO", "IMAGE", "VIDEO"] + tokenCount: int + + class UsageMetadata(TypedDict, total=False): promptTokenCount: int totalTokenCount: int candidatesTokenCount: int cachedContentTokenCount: int + promptTokensDetails: List[PromptTokensDetails] class CachedContent(TypedDict, total=False): diff --git a/tests/litellm/test_cost_calculator.py b/tests/litellm/test_cost_calculator.py index c0073e2c56..d4322f1e5e 100644 --- a/tests/litellm/test_cost_calculator.py +++ b/tests/litellm/test_cost_calculator.py @@ -12,7 +12,9 @@ from unittest.mock import MagicMock, patch from pydantic import BaseModel +import litellm from litellm.cost_calculator import response_cost_calculator +from litellm.types.utils import ModelResponse, PromptTokensDetailsWrapper, Usage def test_cost_calculator_with_response_cost_in_additional_headers(): @@ -32,3 +34,40 @@ def test_cost_calculator_with_response_cost_in_additional_headers(): ) assert result == 1000 + + +def test_cost_calculator_with_usage(): + from litellm import get_model_info + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + usage = Usage( + prompt_tokens=100, + completion_tokens=100, + prompt_tokens_details=PromptTokensDetailsWrapper( + text_tokens=10, audio_tokens=90 + ), + ) + mr = ModelResponse(usage=usage, model="gemini-2.0-flash-001") + + result = response_cost_calculator( + response_object=mr, + model="", + custom_llm_provider="vertex_ai", + call_type="acompletion", + optional_params={}, + cache_hit=None, + base_model=None, + ) + + model_info = litellm.model_cost["gemini-2.0-flash-001"] + + expected_cost = ( + usage.prompt_tokens_details.audio_tokens + * model_info["input_cost_per_audio_token"] + + usage.prompt_tokens_details.text_tokens * model_info["input_cost_per_token"] + + usage.completion_tokens * model_info["output_cost_per_token"] + ) + + assert result == expected_cost, f"Got {result}, Expected {expected_cost}" diff --git a/tests/local_testing/test_completion_cost.py b/tests/local_testing/test_completion_cost.py index d4efade9e3..0548984f45 100644 --- a/tests/local_testing/test_completion_cost.py +++ b/tests/local_testing/test_completion_cost.py @@ -2454,6 +2454,14 @@ def test_completion_cost_params_gemini_3(): os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") + usage = Usage( + completion_tokens=2, + prompt_tokens=3771, + total_tokens=3773, + completion_tokens_details=None, + prompt_tokens_details=None, + ) + response = ModelResponse( id="chatcmpl-61043504-4439-48be-9996-e29bdee24dc3", choices=[ @@ -2472,13 +2480,7 @@ def test_completion_cost_params_gemini_3(): model="gemini-1.5-flash", object="chat.completion", system_fingerprint=None, - usage=Usage( - completion_tokens=2, - prompt_tokens=3771, - total_tokens=3773, - completion_tokens_details=None, - prompt_tokens_details=None, - ), + usage=usage, vertex_ai_grounding_metadata=[], vertex_ai_safety_results=[ [ @@ -2501,10 +2503,9 @@ def test_completion_cost_params_gemini_3(): **{ "model": "gemini-1.5-flash", "custom_llm_provider": "vertex_ai", - "prompt_tokens": 3771, - "completion_tokens": 2, "prompt_characters": None, "completion_characters": 3, + "usage": usage, } ) From 15b1a8afb0c85191c0cc2229c2b2cd8be71cabef Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 18:27:39 -0700 Subject: [PATCH 093/244] test_is_database_connection_error_prisma_errors --- .../proxy/auth/test_auth_exception_handler.py | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 tests/litellm/proxy/auth/test_auth_exception_handler.py diff --git a/tests/litellm/proxy/auth/test_auth_exception_handler.py b/tests/litellm/proxy/auth/test_auth_exception_handler.py new file mode 100644 index 0000000000..ca0e8c88c4 --- /dev/null +++ b/tests/litellm/proxy/auth/test_auth_exception_handler.py @@ -0,0 +1,161 @@ +import asyncio +import json +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest +from fastapi import HTTPException, Request, status +from prisma import errors as prisma_errors +from prisma.errors import ( + ClientNotConnectedError, + DataError, + ForeignKeyViolationError, + HTTPClientClosedError, + MissingRequiredValueError, + PrismaError, + RawQueryError, + RecordNotFoundError, + TableNotFoundError, + UniqueViolationError, +) + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ProxyErrorTypes, ProxyException +from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler + + +# Test is_database_connection_error method +@pytest.mark.parametrize( + "prisma_error", + [ + PrismaError(), + DataError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + UniqueViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + ForeignKeyViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + MissingRequiredValueError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RawQueryError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + TableNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RecordNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + HTTPClientClosedError(), + ClientNotConnectedError(), + ], +) +def test_is_database_connection_error_prisma_errors(prisma_error): + """ + Test that all Prisma errors are considered database connection errors + """ + handler = UserAPIKeyAuthExceptionHandler() + assert handler.is_database_connection_error(prisma_error) == True + + +def test_is_database_connection_generic_errors(): + """ + Test non-Prisma error cases for database connection checking + """ + handler = UserAPIKeyAuthExceptionHandler() + + # Test with ProxyException (DB connection) + db_proxy_exception = ProxyException( + message="DB Connection Error", + type=ProxyErrorTypes.no_db_connection, + param="test-param", + ) + assert handler.is_database_connection_error(db_proxy_exception) == True + + # Test with non-DB error + regular_exception = Exception("Regular error") + assert handler.is_database_connection_error(regular_exception) == False + + +# Test should_allow_request_on_db_unavailable method +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": True}, +) +def test_should_allow_request_on_db_unavailable_true(): + handler = UserAPIKeyAuthExceptionHandler() + assert handler.should_allow_request_on_db_unavailable() == True + + +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": False}, +) +def test_should_allow_request_on_db_unavailable_false(): + handler = UserAPIKeyAuthExceptionHandler() + assert handler.should_allow_request_on_db_unavailable() == False + + +# Test _handle_authentication_error method +@pytest.mark.asyncio +async def test_handle_authentication_error_db_unavailable(): + handler = UserAPIKeyAuthExceptionHandler() + + # Mock request and other dependencies + mock_request = MagicMock() + mock_request_data = {} + mock_route = "/test" + mock_span = None + mock_api_key = "test-key" + + # Test with DB connection error when requests are allowed + with patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": True}, + ): + db_error = prisma_errors.PrismaError() + result = await handler._handle_authentication_error( + db_error, + mock_request, + mock_request_data, + mock_route, + mock_span, + mock_api_key, + ) + assert result.key_name == "failed-to-connect-to-db" + assert result.token == "failed-to-connect-to-db" + + +@pytest.mark.asyncio +async def test_handle_authentication_error_budget_exceeded(): + handler = UserAPIKeyAuthExceptionHandler() + + # Mock request and other dependencies + mock_request = MagicMock() + mock_request_data = {} + mock_route = "/test" + mock_span = None + mock_api_key = "test-key" + + # Test with budget exceeded error + with pytest.raises(ProxyException) as exc_info: + from litellm.exceptions import BudgetExceededError + + budget_error = BudgetExceededError( + message="Budget exceeded", current_cost=100, max_budget=100 + ) + await handler._handle_authentication_error( + budget_error, + mock_request, + mock_request_data, + mock_route, + mock_span, + mock_api_key, + ) + + assert exc_info.value.type == ProxyErrorTypes.budget_exceeded From 5242c5fbabd149c8812c8152ab2e64929c4778e8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 18:28:28 -0700 Subject: [PATCH 094/244] test - auth exception handler --- .../proxy/auth/test_auth_exception_handler.py | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/litellm/proxy/auth/test_auth_exception_handler.py b/tests/litellm/proxy/auth/test_auth_exception_handler.py index ca0e8c88c4..b44de86e05 100644 --- a/tests/litellm/proxy/auth/test_auth_exception_handler.py +++ b/tests/litellm/proxy/auth/test_auth_exception_handler.py @@ -101,9 +101,33 @@ def test_should_allow_request_on_db_unavailable_false(): assert handler.should_allow_request_on_db_unavailable() == False -# Test _handle_authentication_error method @pytest.mark.asyncio -async def test_handle_authentication_error_db_unavailable(): +@pytest.mark.parametrize( + "prisma_error", + [ + PrismaError(), + DataError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + UniqueViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + ForeignKeyViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + MissingRequiredValueError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RawQueryError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + TableNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RecordNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + HTTPClientClosedError(), + ClientNotConnectedError(), + ], +) +async def test_handle_authentication_error_db_unavailable(prisma_error): handler = UserAPIKeyAuthExceptionHandler() # Mock request and other dependencies @@ -118,9 +142,8 @@ async def test_handle_authentication_error_db_unavailable(): "litellm.proxy.proxy_server.general_settings", {"allow_requests_on_db_unavailable": True}, ): - db_error = prisma_errors.PrismaError() result = await handler._handle_authentication_error( - db_error, + prisma_error, mock_request, mock_request_data, mock_route, From 763f853a9f9f6e678d966e08df91ccec3a76009d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 18:32:58 -0700 Subject: [PATCH 095/244] docs fix --- docs/my-website/docs/providers/bedrock.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 4f88fdb39b..ed65e14b8b 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -1776,6 +1776,7 @@ response = completion( ) ``` + 1. Setup config.yaml @@ -1820,11 +1821,13 @@ curl -X POST 'http://0.0.0.0:4000/chat/completions' \ ``` + ### SSO Login (AWS Profile) - Set `AWS_PROFILE` environment variable - Make bedrock completion call + ```python import os from litellm import completion @@ -1940,9 +1943,6 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/images/generations' \ "colorGuidedGenerationParams":{"colors":["#FFFFFF"]} }' ``` - - - | Model Name | Function Call | |-------------------------|---------------------------------------------| From 485aa87e6505e3749d0a15a0c335233af5c02136 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 18:48:18 -0700 Subject: [PATCH 096/244] allow_requests_on_db_unavailable --- docs/my-website/docs/proxy/config_settings.md | 2 +- docs/my-website/docs/proxy/prod.md | 110 +++--------------- 2 files changed, 18 insertions(+), 94 deletions(-) diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 0093464d93..4a62184df7 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -160,7 +160,7 @@ general_settings: | database_url | string | The URL for the database connection [Set up Virtual Keys](virtual_keys) | | database_connection_pool_limit | integer | The limit for database connection pool [Setting DB Connection Pool limit](#configure-db-pool-limits--connection-timeouts) | | database_connection_timeout | integer | The timeout for database connections in seconds [Setting DB Connection Pool limit, timeout](#configure-db-pool-limits--connection-timeouts) | -| allow_requests_on_db_unavailable | boolean | If true, allows requests to succeed even if DB is unreachable. **Only use this if running LiteLLM in your VPC** This will allow requests to work even when LiteLLM cannot connect to the DB to verify a Virtual Key | +| allow_requests_on_db_unavailable | boolean | If true, allows requests to succeed even if DB is unreachable. **Only use this if running LiteLLM in your VPC** This will allow requests to work even when LiteLLM cannot connect to the DB to verify a Virtual Key [Doc on graceful db unavailability](prod#5-if-running-litellm-on-vpc-gracefully-handle-db-unavailability) | | custom_auth | string | Write your own custom authentication logic [Doc Custom Auth](virtual_keys#custom-auth) | | max_parallel_requests | integer | The max parallel requests allowed per deployment | | global_max_parallel_requests | integer | The max parallel requests allowed on the proxy overall | diff --git a/docs/my-website/docs/proxy/prod.md b/docs/my-website/docs/proxy/prod.md index d3ba2d6224..314300f2a0 100644 --- a/docs/my-website/docs/proxy/prod.md +++ b/docs/my-website/docs/proxy/prod.md @@ -94,15 +94,29 @@ This disables the load_dotenv() functionality, which will automatically load you ## 5. If running LiteLLM on VPC, gracefully handle DB unavailability -This will allow LiteLLM to continue to process requests even if the DB is unavailable. This is better handling for DB unavailability. +When running LiteLLM on a VPC (and inaccessible from the public internet), you can enable graceful degradation so that request processing continues even if the database is temporarily unavailable. + **WARNING: Only do this if you're running LiteLLM on VPC, that cannot be accessed from the public internet.** -```yaml +#### Configuration + +```yaml showLineNumbers title="litellm config.yaml" general_settings: allow_requests_on_db_unavailable: True ``` +#### Expected Behavior + +When `allow_requests_on_db_unavailable` is set to `true`, LiteLLM will handle errors as follows: + +| Type of Error | Expected Behavior | Details | +|---------------|-------------------|----------------| +| Prisma Errors | ✅ Request will be allowed | Covers issues like DB connection resets or rejections from the DB via Prisma, the ORM used by LiteLLM. | +| Httpx Errors | ✅ Request will be allowed | Occurs when the database is unreachable, allowing the request to proceed despite the DB outage. | +| LiteLLM Budget Errors or Model Errors | ❌ Request will be blocked | Triggered when the DB is reachable but the authentication token is invalid, lacks access, or exceeds budget limits. | + + ## 6. Disable spend_logs & error_logs if not using the LiteLLM UI By default, LiteLLM writes several types of logs to the database: @@ -182,94 +196,4 @@ You should only see the following level of details in logs on the proxy server # INFO: 192.168.2.205:11774 - "POST /chat/completions HTTP/1.1" 200 OK # INFO: 192.168.2.205:34717 - "POST /chat/completions HTTP/1.1" 200 OK # INFO: 192.168.2.205:29734 - "POST /chat/completions HTTP/1.1" 200 OK -``` - - -### Machine Specifications to Deploy LiteLLM - -| Service | Spec | CPUs | Memory | Architecture | Version| -| --- | --- | --- | --- | --- | --- | -| Server | `t2.small`. | `1vCPUs` | `8GB` | `x86` | -| Redis Cache | - | - | - | - | 7.0+ Redis Engine| - - -### Reference Kubernetes Deployment YAML - -Reference Kubernetes `deployment.yaml` that was load tested by us - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: litellm-deployment -spec: - replicas: 3 - selector: - matchLabels: - app: litellm - template: - metadata: - labels: - app: litellm - spec: - containers: - - name: litellm-container - image: ghcr.io/berriai/litellm:main-latest - imagePullPolicy: Always - env: - - name: AZURE_API_KEY - value: "d6******" - - name: AZURE_API_BASE - value: "https://ope******" - - name: LITELLM_MASTER_KEY - value: "sk-1234" - - name: DATABASE_URL - value: "po**********" - args: - - "--config" - - "/app/proxy_config.yaml" # Update the path to mount the config file - volumeMounts: # Define volume mount for proxy_config.yaml - - name: config-volume - mountPath: /app - readOnly: true - livenessProbe: - httpGet: - path: /health/liveliness - port: 4000 - initialDelaySeconds: 120 - periodSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - timeoutSeconds: 10 - readinessProbe: - httpGet: - path: /health/readiness - port: 4000 - initialDelaySeconds: 120 - periodSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - timeoutSeconds: 10 - volumes: # Define volume to mount proxy_config.yaml - - name: config-volume - configMap: - name: litellm-config - -``` - - -Reference Kubernetes `service.yaml` that was load tested by us -```yaml -apiVersion: v1 -kind: Service -metadata: - name: litellm-service -spec: - selector: - app: litellm - ports: - - protocol: TCP - port: 4000 - targetPort: 4000 - type: LoadBalancer -``` +``` \ No newline at end of file From 7142b0b61060292b5a353da65eb95185d281fb4d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 19:22:24 -0700 Subject: [PATCH 097/244] refactor PrismaDBExceptionHandler --- litellm/proxy/auth/auth_exception_handler.py | 38 +++----------------- litellm/proxy/db/exception_handler.py | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 34 deletions(-) create mode 100644 litellm/proxy/db/exception_handler.py diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py index c1a546b569..05797381c6 100644 --- a/litellm/proxy/auth/auth_exception_handler.py +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -9,13 +9,9 @@ from fastapi import HTTPException, Request, status import litellm from litellm._logging import verbose_proxy_logger -from litellm.proxy._types import ( - DB_CONNECTION_ERROR_TYPES, - ProxyErrorTypes, - ProxyException, - UserAPIKeyAuth, -) +from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth from litellm.proxy.auth.auth_utils import _get_request_ip_address +from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler from litellm.types.services import ServiceTypes if TYPE_CHECKING: @@ -58,8 +54,8 @@ class UserAPIKeyAuthExceptionHandler: ) if ( - UserAPIKeyAuthExceptionHandler.should_allow_request_on_db_unavailable() - and UserAPIKeyAuthExceptionHandler.is_database_connection_error(e) + PrismaDBExceptionHandler.should_allow_request_on_db_unavailable() + and PrismaDBExceptionHandler.is_database_connection_error(e) ): # log this as a DB failure on prometheus proxy_logging_obj.service_logging_obj.service_failure_hook( @@ -125,29 +121,3 @@ class UserAPIKeyAuthExceptionHandler: param=getattr(e, "param", "None"), code=status.HTTP_401_UNAUTHORIZED, ) - - @staticmethod - def should_allow_request_on_db_unavailable() -> bool: - """ - Returns True if the request should be allowed to proceed despite the DB connection error - """ - from litellm.proxy.proxy_server import general_settings - - if general_settings.get("allow_requests_on_db_unavailable", False) is True: - return True - return False - - @staticmethod - def is_database_connection_error(e: Exception) -> bool: - """ - Returns True if the exception is from a database outage / connection error - """ - import prisma - - if isinstance(e, DB_CONNECTION_ERROR_TYPES): - return True - if isinstance(e, prisma.errors.PrismaError): - return True - if isinstance(e, ProxyException) and e.type == ProxyErrorTypes.no_db_connection: - return True - return False diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py new file mode 100644 index 0000000000..315811103d --- /dev/null +++ b/litellm/proxy/db/exception_handler.py @@ -0,0 +1,37 @@ +from litellm.proxy._types import ( + DB_CONNECTION_ERROR_TYPES, + ProxyErrorTypes, + ProxyException, +) + + +class PrismaDBExceptionHandler: + """ + Class to handle DB Exceptions or Connection Errors + """ + + @staticmethod + def should_allow_request_on_db_unavailable() -> bool: + """ + Returns True if the request should be allowed to proceed despite the DB connection error + """ + from litellm.proxy.proxy_server import general_settings + + if general_settings.get("allow_requests_on_db_unavailable", False) is True: + return True + return False + + @staticmethod + def is_database_connection_error(e: Exception) -> bool: + """ + Returns True if the exception is from a database outage / connection error + """ + import prisma + + if isinstance(e, DB_CONNECTION_ERROR_TYPES): + return True + if isinstance(e, prisma.errors.PrismaError): + return True + if isinstance(e, ProxyException) and e.type == ProxyErrorTypes.no_db_connection: + return True + return False From 497570b2a6e26905d7a7564dbb6bf92b6fc0b343 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 19:49:51 -0700 Subject: [PATCH 098/244] bug fix - allow pods to startup when DB is unavailable --- litellm/proxy/db/exception_handler.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index 315811103d..db73f9e9c9 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -1,8 +1,11 @@ +from typing import Union + from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, ProxyErrorTypes, ProxyException, ) +from litellm.secret_managers.main import str_to_bool class PrismaDBExceptionHandler: @@ -17,7 +20,12 @@ class PrismaDBExceptionHandler: """ from litellm.proxy.proxy_server import general_settings - if general_settings.get("allow_requests_on_db_unavailable", False) is True: + _allow_requests_on_db_unavailable: Union[bool, str] = general_settings.get( + "allow_requests_on_db_unavailable", False + ) + if isinstance(_allow_requests_on_db_unavailable, bool): + return _allow_requests_on_db_unavailable + if str_to_bool(_allow_requests_on_db_unavailable) is True: return True return False @@ -35,3 +43,19 @@ class PrismaDBExceptionHandler: if isinstance(e, ProxyException) and e.type == ProxyErrorTypes.no_db_connection: return True return False + + @staticmethod + def handle_db_exception(e: Exception): + """ + Primary handler for `allow_requests_on_db_unavailable` flag. Decides whether to raise a DB Exception or not based on the flag. + + - If exception is a DB Connection Error, and `allow_requests_on_db_unavailable` is True, + - Do not raise an exception, return None + - Else, raise the exception + """ + if ( + PrismaDBExceptionHandler.is_database_connection_error(e) + and PrismaDBExceptionHandler.should_allow_request_on_db_unavailable() + ): + return None + raise e From 88ef97b9d1d9b2ecad5b9258ff980930ff694033 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 19:50:57 -0700 Subject: [PATCH 099/244] allow proxy to startup on DB unavailable --- .../health_endpoints/_health_endpoints.py | 24 ++++--- litellm/proxy/proxy_server.py | 68 ++++++++++--------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index 34e7d34bbf..9de845397a 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -20,6 +20,7 @@ from litellm.proxy._types import ( WebhookEvent, ) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler from litellm.proxy.health_check import ( _clean_endpoint_data, _update_litellm_params_for_health_check, @@ -381,20 +382,23 @@ async def _db_health_readiness_check(): global db_health_cache # Note - Intentionally don't try/except this so it raises an exception when it fails + try: + # if timedelta is less than 2 minutes return DB Status + time_diff = datetime.now() - db_health_cache["last_updated"] + if db_health_cache["status"] != "unknown" and time_diff < timedelta(minutes=2): + return db_health_cache - # if timedelta is less than 2 minutes return DB Status - time_diff = datetime.now() - db_health_cache["last_updated"] - if db_health_cache["status"] != "unknown" and time_diff < timedelta(minutes=2): + if prisma_client is None: + db_health_cache = {"status": "disconnected", "last_updated": datetime.now()} + return db_health_cache + + await prisma_client.health_check() + db_health_cache = {"status": "connected", "last_updated": datetime.now()} return db_health_cache - - if prisma_client is None: - db_health_cache = {"status": "disconnected", "last_updated": datetime.now()} + except Exception as e: + PrismaDBExceptionHandler.handle_db_exception(e) return db_health_cache - await prisma_client.health_check() - db_health_cache = {"status": "connected", "last_updated": datetime.now()} - return db_health_cache - @router.get( "/settings", diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index eefc0bdd06..0e04e9f213 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -176,6 +176,7 @@ from litellm.proxy.common_utils.proxy_state import ProxyState from litellm.proxy.common_utils.reset_budget_job import ResetBudgetJob from litellm.proxy.common_utils.swagger_utils import ERROR_RESPONSES from litellm.proxy.credential_endpoints.endpoints import router as credential_router +from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler from litellm.proxy.fine_tuning_endpoints.endpoints import router as fine_tuning_router from litellm.proxy.fine_tuning_endpoints.endpoints import set_fine_tuning_config from litellm.proxy.guardrails.guardrail_endpoints import router as guardrails_router @@ -456,15 +457,6 @@ async def proxy_startup_event(app: FastAPI): ### LOAD MASTER KEY ### # check if master key set in environment - load from there master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore - # check if DATABASE_URL in environment - load from there - if prisma_client is None: - _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore - prisma_client = await ProxyStartupEvent._setup_prisma_client( - database_url=_db_url, - proxy_logging_obj=proxy_logging_obj, - user_api_key_cache=user_api_key_cache, - ) - ## CHECK PREMIUM USER verbose_proxy_logger.debug( "litellm.proxy.proxy_server.py::startup() - CHECKING PREMIUM USER - {}".format( @@ -527,6 +519,15 @@ async def proxy_startup_event(app: FastAPI): redis_usage_cache=redis_usage_cache, ) + # check if DATABASE_URL in environment - load from there + if prisma_client is None: + _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore + prisma_client = await ProxyStartupEvent._setup_prisma_client( + database_url=_db_url, + proxy_logging_obj=proxy_logging_obj, + user_api_key_cache=user_api_key_cache, + ) + ## JWT AUTH ## ProxyStartupEvent._initialize_jwt_auth( general_settings=general_settings, @@ -3362,33 +3363,36 @@ class ProxyStartupEvent: - Sets up prisma client - Adds necessary views to proxy """ - prisma_client: Optional[PrismaClient] = None - if database_url is not None: - try: - prisma_client = PrismaClient( - database_url=database_url, proxy_logging_obj=proxy_logging_obj - ) - except Exception as e: - raise e + try: + prisma_client: Optional[PrismaClient] = None + if database_url is not None: + try: + prisma_client = PrismaClient( + database_url=database_url, proxy_logging_obj=proxy_logging_obj + ) + except Exception as e: + raise e - await prisma_client.connect() + await prisma_client.connect() - ## Add necessary views to proxy ## - asyncio.create_task( - prisma_client.check_view_exists() - ) # check if all necessary views exist. Don't block execution + ## Add necessary views to proxy ## + asyncio.create_task( + prisma_client.check_view_exists() + ) # check if all necessary views exist. Don't block execution - asyncio.create_task( - prisma_client._set_spend_logs_row_count_in_proxy_state() - ) # set the spend logs row count in proxy state. Don't block execution + asyncio.create_task( + prisma_client._set_spend_logs_row_count_in_proxy_state() + ) # set the spend logs row count in proxy state. Don't block execution - # run a health check to ensure the DB is ready - if ( - get_secret_bool("DISABLE_PRISMA_HEALTH_CHECK_ON_STARTUP", False) - is not True - ): - await prisma_client.health_check() - return prisma_client + # run a health check to ensure the DB is ready + if ( + get_secret_bool("DISABLE_PRISMA_HEALTH_CHECK_ON_STARTUP", False) + is not True + ): + await prisma_client.health_check() + return prisma_client + except Exception as e: + PrismaDBExceptionHandler.handle_db_exception(e) @classmethod def _init_dd_tracer(cls): From 15c04da73599316e4fa929787fd272fed49b5cda Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 20:00:10 -0700 Subject: [PATCH 100/244] refactor tests --- litellm/proxy/proxy_server.py | 1 + .../proxy/auth/test_auth_exception_handler.py | 72 ------------ .../proxy/db/test_exception_handler.py | 109 ++++++++++++++++++ 3 files changed, 110 insertions(+), 72 deletions(-) create mode 100644 tests/litellm/proxy/db/test_exception_handler.py diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 0e04e9f213..59b4bc8fe3 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3393,6 +3393,7 @@ class ProxyStartupEvent: return prisma_client except Exception as e: PrismaDBExceptionHandler.handle_db_exception(e) + return None @classmethod def _init_dd_tracer(cls): diff --git a/tests/litellm/proxy/auth/test_auth_exception_handler.py b/tests/litellm/proxy/auth/test_auth_exception_handler.py index b44de86e05..224bf24b57 100644 --- a/tests/litellm/proxy/auth/test_auth_exception_handler.py +++ b/tests/litellm/proxy/auth/test_auth_exception_handler.py @@ -29,78 +29,6 @@ from litellm.proxy._types import ProxyErrorTypes, ProxyException from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler -# Test is_database_connection_error method -@pytest.mark.parametrize( - "prisma_error", - [ - PrismaError(), - DataError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), - UniqueViolationError( - data={"user_facing_error": {"meta": {"table": "test_table"}}} - ), - ForeignKeyViolationError( - data={"user_facing_error": {"meta": {"table": "test_table"}}} - ), - MissingRequiredValueError( - data={"user_facing_error": {"meta": {"table": "test_table"}}} - ), - RawQueryError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), - TableNotFoundError( - data={"user_facing_error": {"meta": {"table": "test_table"}}} - ), - RecordNotFoundError( - data={"user_facing_error": {"meta": {"table": "test_table"}}} - ), - HTTPClientClosedError(), - ClientNotConnectedError(), - ], -) -def test_is_database_connection_error_prisma_errors(prisma_error): - """ - Test that all Prisma errors are considered database connection errors - """ - handler = UserAPIKeyAuthExceptionHandler() - assert handler.is_database_connection_error(prisma_error) == True - - -def test_is_database_connection_generic_errors(): - """ - Test non-Prisma error cases for database connection checking - """ - handler = UserAPIKeyAuthExceptionHandler() - - # Test with ProxyException (DB connection) - db_proxy_exception = ProxyException( - message="DB Connection Error", - type=ProxyErrorTypes.no_db_connection, - param="test-param", - ) - assert handler.is_database_connection_error(db_proxy_exception) == True - - # Test with non-DB error - regular_exception = Exception("Regular error") - assert handler.is_database_connection_error(regular_exception) == False - - -# Test should_allow_request_on_db_unavailable method -@patch( - "litellm.proxy.proxy_server.general_settings", - {"allow_requests_on_db_unavailable": True}, -) -def test_should_allow_request_on_db_unavailable_true(): - handler = UserAPIKeyAuthExceptionHandler() - assert handler.should_allow_request_on_db_unavailable() == True - - -@patch( - "litellm.proxy.proxy_server.general_settings", - {"allow_requests_on_db_unavailable": False}, -) -def test_should_allow_request_on_db_unavailable_false(): - handler = UserAPIKeyAuthExceptionHandler() - assert handler.should_allow_request_on_db_unavailable() == False - - @pytest.mark.asyncio @pytest.mark.parametrize( "prisma_error", diff --git a/tests/litellm/proxy/db/test_exception_handler.py b/tests/litellm/proxy/db/test_exception_handler.py new file mode 100644 index 0000000000..04f17ff19b --- /dev/null +++ b/tests/litellm/proxy/db/test_exception_handler.py @@ -0,0 +1,109 @@ +import asyncio +import json +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest +from fastapi import HTTPException, Request, status +from prisma import errors as prisma_errors +from prisma.errors import ( + ClientNotConnectedError, + DataError, + ForeignKeyViolationError, + HTTPClientClosedError, + MissingRequiredValueError, + PrismaError, + RawQueryError, + RecordNotFoundError, + TableNotFoundError, + UniqueViolationError, +) + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ProxyErrorTypes, ProxyException +from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler + + +# Test is_database_connection_error method +@pytest.mark.parametrize( + "prisma_error", + [ + PrismaError(), + DataError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + UniqueViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + ForeignKeyViolationError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + MissingRequiredValueError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RawQueryError(data={"user_facing_error": {"meta": {"table": "test_table"}}}), + TableNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + RecordNotFoundError( + data={"user_facing_error": {"meta": {"table": "test_table"}}} + ), + HTTPClientClosedError(), + ClientNotConnectedError(), + ], +) +def test_is_database_connection_error_prisma_errors(prisma_error): + """ + Test that all Prisma errors are considered database connection errors + """ + assert PrismaDBExceptionHandler.is_database_connection_error(prisma_error) == True + + +def test_is_database_connection_generic_errors(): + """ + Test non-Prisma error cases for database connection checking + """ + assert ( + PrismaDBExceptionHandler.is_database_connection_error( + Exception("Regular error") + ) + == False + ) + + # Test with ProxyException (DB connection) + db_proxy_exception = ProxyException( + message="DB Connection Error", + type=ProxyErrorTypes.no_db_connection, + param="test-param", + ) + assert ( + PrismaDBExceptionHandler.is_database_connection_error(db_proxy_exception) + == True + ) + + # Test with non-DB error + regular_exception = Exception("Regular error") + assert ( + PrismaDBExceptionHandler.is_database_connection_error(regular_exception) + == False + ) + + +# Test should_allow_request_on_db_unavailable method +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": True}, +) +def test_should_allow_request_on_db_unavailable_true(): + assert PrismaDBExceptionHandler.should_allow_request_on_db_unavailable() == True + + +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": False}, +) +def test_should_allow_request_on_db_unavailable_false(): + assert PrismaDBExceptionHandler.should_allow_request_on_db_unavailable() == False From 427580eff512fbdafec90157d443c4d1de63332d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 20:35:27 -0700 Subject: [PATCH 101/244] fix _setup_prisma_client --- litellm/proxy/db/exception_handler.py | 5 +++-- litellm/proxy/proxy_server.py | 17 ++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index db73f9e9c9..79229f1e1b 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -18,9 +18,10 @@ class PrismaDBExceptionHandler: """ Returns True if the request should be allowed to proceed despite the DB connection error """ - from litellm.proxy.proxy_server import general_settings + from litellm.proxy.proxy_server import proxy_config - _allow_requests_on_db_unavailable: Union[bool, str] = general_settings.get( + _general_settings = proxy_config.config + _allow_requests_on_db_unavailable: Union[bool, str] = _general_settings.get( "allow_requests_on_db_unavailable", False ) if isinstance(_allow_requests_on_db_unavailable, bool): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 59b4bc8fe3..67d88b9d32 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -457,6 +457,14 @@ async def proxy_startup_event(app: FastAPI): ### LOAD MASTER KEY ### # check if master key set in environment - load from there master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore + # check if DATABASE_URL in environment - load from there + if prisma_client is None: + _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore + prisma_client = await ProxyStartupEvent._setup_prisma_client( + database_url=_db_url, + proxy_logging_obj=proxy_logging_obj, + user_api_key_cache=user_api_key_cache, + ) ## CHECK PREMIUM USER verbose_proxy_logger.debug( "litellm.proxy.proxy_server.py::startup() - CHECKING PREMIUM USER - {}".format( @@ -519,15 +527,6 @@ async def proxy_startup_event(app: FastAPI): redis_usage_cache=redis_usage_cache, ) - # check if DATABASE_URL in environment - load from there - if prisma_client is None: - _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore - prisma_client = await ProxyStartupEvent._setup_prisma_client( - database_url=_db_url, - proxy_logging_obj=proxy_logging_obj, - user_api_key_cache=user_api_key_cache, - ) - ## JWT AUTH ## ProxyStartupEvent._initialize_jwt_auth( general_settings=general_settings, From 34e58be36dcf16d7d139b0b51601f6be18df0a24 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 20:42:01 -0700 Subject: [PATCH 102/244] fix order of _setup_prisma_client --- litellm/proxy/db/exception_handler.py | 5 ++--- litellm/proxy/proxy_server.py | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index 79229f1e1b..db73f9e9c9 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -18,10 +18,9 @@ class PrismaDBExceptionHandler: """ Returns True if the request should be allowed to proceed despite the DB connection error """ - from litellm.proxy.proxy_server import proxy_config + from litellm.proxy.proxy_server import general_settings - _general_settings = proxy_config.config - _allow_requests_on_db_unavailable: Union[bool, str] = _general_settings.get( + _allow_requests_on_db_unavailable: Union[bool, str] = general_settings.get( "allow_requests_on_db_unavailable", False ) if isinstance(_allow_requests_on_db_unavailable, bool): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 67d88b9d32..c2044ac1d6 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -454,17 +454,6 @@ async def proxy_startup_event(app: FastAPI): import json init_verbose_loggers() - ### LOAD MASTER KEY ### - # check if master key set in environment - load from there - master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore - # check if DATABASE_URL in environment - load from there - if prisma_client is None: - _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore - prisma_client = await ProxyStartupEvent._setup_prisma_client( - database_url=_db_url, - proxy_logging_obj=proxy_logging_obj, - user_api_key_cache=user_api_key_cache, - ) ## CHECK PREMIUM USER verbose_proxy_logger.debug( "litellm.proxy.proxy_server.py::startup() - CHECKING PREMIUM USER - {}".format( @@ -521,6 +510,18 @@ async def proxy_startup_event(app: FastAPI): if isinstance(worker_config, dict): await initialize(**worker_config) + ### LOAD MASTER KEY ### + # check if master key set in environment - load from there + master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore + # check if DATABASE_URL in environment - load from there + if prisma_client is None: + _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore + prisma_client = await ProxyStartupEvent._setup_prisma_client( + database_url=_db_url, + proxy_logging_obj=proxy_logging_obj, + user_api_key_cache=user_api_key_cache, + ) + ProxyStartupEvent._initialize_startup_logging( llm_router=llm_router, proxy_logging_obj=proxy_logging_obj, From 87f0201f8400bee3550e1c82befccfc637fc4436 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 20:44:25 -0700 Subject: [PATCH 103/244] test_handle_db_exception_with_connection_error --- .../proxy/db/test_exception_handler.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/litellm/proxy/db/test_exception_handler.py b/tests/litellm/proxy/db/test_exception_handler.py index 04f17ff19b..e68c9b6a99 100644 --- a/tests/litellm/proxy/db/test_exception_handler.py +++ b/tests/litellm/proxy/db/test_exception_handler.py @@ -24,6 +24,7 @@ sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system path +import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import ProxyErrorTypes, ProxyException from litellm.proxy.db.exception_handler import PrismaDBExceptionHandler @@ -107,3 +108,41 @@ def test_should_allow_request_on_db_unavailable_true(): ) def test_should_allow_request_on_db_unavailable_false(): assert PrismaDBExceptionHandler.should_allow_request_on_db_unavailable() == False + + +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": True}, +) +def test_handle_db_exception_with_connection_error(): + """ + Test that DB connection errors are handled gracefully when allow_requests_on_db_unavailable is True + """ + db_error = ClientNotConnectedError() + result = PrismaDBExceptionHandler.handle_db_exception(db_error) + assert result is None + + +@patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": False}, +) +def test_handle_db_exception_raises_error(): + """ + Test that DB connection errors are raised when allow_requests_on_db_unavailable is False + """ + db_error = ClientNotConnectedError() + with pytest.raises(ClientNotConnectedError): + PrismaDBExceptionHandler.handle_db_exception(db_error) + + +def test_handle_db_exception_with_non_db_error(): + """ + Test that non-DB errors are always raised regardless of allow_requests_on_db_unavailable setting + """ + regular_error = litellm.BudgetExceededError( + current_cost=10, + max_budget=10, + ) + with pytest.raises(litellm.BudgetExceededError): + PrismaDBExceptionHandler.handle_db_exception(regular_error) From b6506f7bda5bb2935c97e6ca2b75e306f2c7111f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 20:56:39 -0700 Subject: [PATCH 104/244] test_db_health_readiness_check_with_prisma_error --- .../health_endpoints/test_health_endpoints.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/litellm/proxy/health_endpoints/test_health_endpoints.py diff --git a/tests/litellm/proxy/health_endpoints/test_health_endpoints.py b/tests/litellm/proxy/health_endpoints/test_health_endpoints.py new file mode 100644 index 0000000000..e2dd429357 --- /dev/null +++ b/tests/litellm/proxy/health_endpoints/test_health_endpoints.py @@ -0,0 +1,101 @@ +import asyncio +import json +import os +import sys +from datetime import datetime, timedelta +from unittest.mock import MagicMock, patch + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +import pytest +from prisma.errors import ClientNotConnectedError, HTTPClientClosedError, PrismaError + +from litellm.proxy._types import ProxyErrorTypes, ProxyException +from litellm.proxy.health_endpoints._health_endpoints import ( + _db_health_readiness_check, + db_health_cache, +) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "prisma_error", + [ + PrismaError(), + ClientNotConnectedError(), + HTTPClientClosedError(), + ], +) +async def test_db_health_readiness_check_with_prisma_error(prisma_error): + """ + Test that when prisma_client.health_check() raises a PrismaError and + allow_requests_on_db_unavailable is True, the function should not raise an error + and return the cached health status. + """ + # Mock the prisma client + mock_prisma_client = MagicMock() + mock_prisma_client.health_check.side_effect = prisma_error + + # Reset the health cache to a known state + global db_health_cache + db_health_cache = { + "status": "unknown", + "last_updated": datetime.now() - timedelta(minutes=5), + } + + # Patch the imports and general_settings + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": True}, + ): + + # Call the function + result = await _db_health_readiness_check() + + # Verify that the function called health_check + mock_prisma_client.health_check.assert_called_once() + + # Verify that the function returned the cache + assert result is not None + assert result["status"] == "unknown" # Should retain the status from the cache + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "prisma_error", + [ + PrismaError(), + ClientNotConnectedError(), + HTTPClientClosedError(), + ], +) +async def test_db_health_readiness_check_with_error_and_flag_off(prisma_error): + """ + Test that when prisma_client.health_check() raises a DB error but + allow_requests_on_db_unavailable is False, the exception should be raised. + """ + # Mock the prisma client + mock_prisma_client = MagicMock() + mock_prisma_client.health_check.side_effect = prisma_error + + # Reset the health cache + global db_health_cache + db_health_cache = { + "status": "unknown", + "last_updated": datetime.now() - timedelta(minutes=5), + } + + # Patch the imports and general_settings where the flag is False + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client), patch( + "litellm.proxy.proxy_server.general_settings", + {"allow_requests_on_db_unavailable": False}, + ): + + # The function should raise the exception + with pytest.raises(Exception) as excinfo: + await _db_health_readiness_check() + + # Verify that the raised exception is the same + assert excinfo.value == prisma_error From 05c38049feebd8cc113a361b3834331910dcb6d8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Mar 2025 21:04:36 -0700 Subject: [PATCH 105/244] docs prod.md --- docs/my-website/docs/proxy/prod.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/my-website/docs/proxy/prod.md b/docs/my-website/docs/proxy/prod.md index 314300f2a0..1c1cbeedb4 100644 --- a/docs/my-website/docs/proxy/prod.md +++ b/docs/my-website/docs/proxy/prod.md @@ -114,6 +114,8 @@ When `allow_requests_on_db_unavailable` is set to `true`, LiteLLM will handle er |---------------|-------------------|----------------| | Prisma Errors | ✅ Request will be allowed | Covers issues like DB connection resets or rejections from the DB via Prisma, the ORM used by LiteLLM. | | Httpx Errors | ✅ Request will be allowed | Occurs when the database is unreachable, allowing the request to proceed despite the DB outage. | +| Pod Startup Behavior | ✅ Pods start regardless | LiteLLM Pods will start even if the database is down or unreachable, ensuring higher uptime guarantees for deployments. | +| Health/Readiness Check | ✅ Always returns 200 OK | The /health/readiness endpoint returns a 200 OK status to ensure that pods remain operational even when the database is unavailable. | LiteLLM Budget Errors or Model Errors | ❌ Request will be blocked | Triggered when the DB is reachable but the authentication token is invalid, lacks access, or exceeds budget limits. | From 109add7946ced31850466d38405b7b78af7f6bfd Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 23:04:24 -0700 Subject: [PATCH 106/244] build(model_prices_and_context_window.json): add gemini multimodal embedding cost --- .../model_prices_and_context_window_backup.json | 17 +++++++++++++++++ model_prices_and_context_window.json | 17 +++++++++++++++++ tests/local_testing/test_get_model_info.py | 4 ++++ 3 files changed, 38 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 06f7238c01..b8e32a24ce 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -5373,6 +5373,23 @@ "mode": "embedding", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" }, + "multimodalembedding": { + "max_tokens": 2048, + "max_input_tokens": 2048, + "output_vector_size": 768, + "input_cost_per_character": 0.0000002, + "input_cost_per_image": 0.0001, + "input_cost_per_video_per_second": 0.0005, + "input_cost_per_video_per_second_above_8s_interval": 0.0010, + "input_cost_per_video_per_second_above_15s_interval": 0.0020, + "input_cost_per_token": 0.0000008, + "output_cost_per_token": 0, + "litellm_provider": "vertex_ai-embedding-models", + "mode": "embedding", + "supported_endpoints": ["/v1/embeddings"], + "supported_modalities": ["text", "image", "video"], + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" + }, "text-embedding-large-exp-03-07": { "max_tokens": 8192, "max_input_tokens": 8192, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 06f7238c01..b8e32a24ce 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -5373,6 +5373,23 @@ "mode": "embedding", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" }, + "multimodalembedding": { + "max_tokens": 2048, + "max_input_tokens": 2048, + "output_vector_size": 768, + "input_cost_per_character": 0.0000002, + "input_cost_per_image": 0.0001, + "input_cost_per_video_per_second": 0.0005, + "input_cost_per_video_per_second_above_8s_interval": 0.0010, + "input_cost_per_video_per_second_above_15s_interval": 0.0020, + "input_cost_per_token": 0.0000008, + "output_cost_per_token": 0, + "litellm_provider": "vertex_ai-embedding-models", + "mode": "embedding", + "supported_endpoints": ["/v1/embeddings"], + "supported_modalities": ["text", "image", "video"], + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" + }, "text-embedding-large-exp-03-07": { "max_tokens": 8192, "max_input_tokens": 8192, diff --git a/tests/local_testing/test_get_model_info.py b/tests/local_testing/test_get_model_info.py index 1a0f6d7a8d..27b9b1a2b6 100644 --- a/tests/local_testing/test_get_model_info.py +++ b/tests/local_testing/test_get_model_info.py @@ -441,6 +441,10 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "input_cost_per_token_batches": {"type": "number"}, "input_cost_per_token_cache_hit": {"type": "number"}, "input_cost_per_video_per_second": {"type": "number"}, + "input_cost_per_video_per_second_above_8s_interval": {"type": "number"}, + "input_cost_per_video_per_second_above_15s_interval": { + "type": "number" + }, "input_cost_per_video_per_second_above_128k_tokens": {"type": "number"}, "input_dbu_cost_per_token": {"type": "number"}, "litellm_provider": {"type": "string"}, From c0845fec1fa4a21758b405930d7568d4336d687f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 23:10:25 -0700 Subject: [PATCH 107/244] Add OpenAI gpt-4o-transcribe support (#9517) * refactor: introduce new transformation config for gpt-4o-transcribe models * refactor: expose new transformation configs for audio transcription * ci: fix config yml * feat(openai/transcriptions): support provider config transformation on openai audio transcriptions allows gpt-4o and whisper audio transformation to work as expected * refactor: migrate fireworks ai + deepgram to new transform request pattern * feat(openai/): working support for gpt-4o-audio-transcribe * build(model_prices_and_context_window.json): add gpt-4o-transcribe to model cost map * build(model_prices_and_context_window.json): specify what endpoints are supported for `/audio/transcriptions` * fix(get_supported_openai_params.py): fix return * refactor(deepgram/): migrate unit test to deepgram handler * refactor: cleanup unused imports * fix(get_supported_openai_params.py): fix linting error * test: update test --- litellm/__init__.py | 6 ++ .../get_supported_openai_params.py | 16 +++ .../audio_transcription/transformation.py | 16 ++- litellm/llms/custom_httpx/llm_http_handler.py | 67 ++++--------- .../audio_transcription/transformation.py | 52 +++++++++- .../audio_transcription/transformation.py | 19 +--- .../transcriptions/gpt_transformation.py | 34 +++++++ litellm/llms/openai/transcriptions/handler.py | 26 +++-- .../transcriptions/whisper_transformation.py | 97 +++++++++++++++++++ litellm/main.py | 10 ++ ...odel_prices_and_context_window_backup.json | 25 ++++- litellm/proxy/_new_secret_config.yaml | 4 + litellm/types/llms/openai.py | 7 +- litellm/utils.py | 5 + model_prices_and_context_window.json | 25 ++++- ...eek_audio_transcription_transformation.py} | 32 +++--- tests/litellm_utils_tests/test_utils.py | 10 ++ tests/llm_translation/test_openai.py | 11 +++ tests/local_testing/test_get_model_info.py | 2 + tests/local_testing/test_whisper.py | 30 ++++++ 20 files changed, 402 insertions(+), 92 deletions(-) create mode 100644 litellm/llms/openai/transcriptions/gpt_transformation.py create mode 100644 litellm/llms/openai/transcriptions/whisper_transformation.py rename tests/litellm/llms/{custom_httpx/test_handle_audio_file.py => deepgram/audio_transcription/test_deepseek_audio_transcription_transformation.py} (60%) diff --git a/litellm/__init__.py b/litellm/__init__.py index a59484b035..8cdde24a6a 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -950,6 +950,12 @@ openaiOSeriesConfig = OpenAIOSeriesConfig() from .llms.openai.chat.gpt_transformation import ( OpenAIGPTConfig, ) +from .llms.openai.transcriptions.whisper_transformation import ( + OpenAIWhisperAudioTranscriptionConfig, +) +from .llms.openai.transcriptions.gpt_transformation import ( + OpenAIGPTAudioTranscriptionConfig, +) openAIGPTConfig = OpenAIGPTConfig() from .llms.openai.chat.gpt_audio_transformation import ( diff --git a/litellm/litellm_core_utils/get_supported_openai_params.py b/litellm/litellm_core_utils/get_supported_openai_params.py index 3d4f8cef6f..ccbdb331fd 100644 --- a/litellm/litellm_core_utils/get_supported_openai_params.py +++ b/litellm/litellm_core_utils/get_supported_openai_params.py @@ -79,6 +79,22 @@ def get_supported_openai_params( # noqa: PLR0915 elif custom_llm_provider == "maritalk": return litellm.MaritalkConfig().get_supported_openai_params(model=model) elif custom_llm_provider == "openai": + if request_type == "transcription": + transcription_provider_config = ( + litellm.ProviderConfigManager.get_provider_audio_transcription_config( + model=model, provider=LlmProviders.OPENAI + ) + ) + if isinstance( + transcription_provider_config, litellm.OpenAIGPTAudioTranscriptionConfig + ): + return transcription_provider_config.get_supported_openai_params( + model=model + ) + else: + raise ValueError( + f"Unsupported provider config: {transcription_provider_config} for model: {model}" + ) return litellm.OpenAIConfig().get_supported_openai_params(model=model) elif custom_llm_provider == "azure": if litellm.AzureOpenAIO1Config().is_o_series_model(model=model): diff --git a/litellm/llms/base_llm/audio_transcription/transformation.py b/litellm/llms/base_llm/audio_transcription/transformation.py index e550c574e2..d48edb041c 100644 --- a/litellm/llms/base_llm/audio_transcription/transformation.py +++ b/litellm/llms/base_llm/audio_transcription/transformation.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, Union import httpx @@ -8,7 +8,7 @@ from litellm.types.llms.openai import ( AllMessageValues, OpenAIAudioTranscriptionOptionalParams, ) -from litellm.types.utils import ModelResponse +from litellm.types.utils import FileTypes, ModelResponse if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj @@ -42,6 +42,18 @@ class BaseAudioTranscriptionConfig(BaseConfig, ABC): """ return api_base or "" + @abstractmethod + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> Union[dict, bytes]: + raise NotImplementedError( + "AudioTranscriptionConfig needs a request transformation for audio transcription models" + ) + def transform_request( self, model: str, diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 00caf55207..872626c747 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -1,4 +1,3 @@ -import io import json from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Tuple, Union @@ -8,6 +7,9 @@ import litellm import litellm.litellm_core_utils import litellm.types import litellm.types.utils +from litellm.llms.base_llm.audio_transcription.transformation import ( + BaseAudioTranscriptionConfig, +) from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig @@ -852,54 +854,12 @@ class BaseLLMHTTPHandler: request_data=request_data, ) - def handle_audio_file(self, audio_file: FileTypes) -> bytes: - """ - Processes the audio file input based on its type and returns the binary data. - - Args: - audio_file: Can be a file path (str), a tuple (filename, file_content), or binary data (bytes). - - Returns: - The binary data of the audio file. - """ - binary_data: bytes # Explicitly declare the type - - # Handle the audio file based on type - if isinstance(audio_file, str): - # If it's a file path - with open(audio_file, "rb") as f: - binary_data = f.read() # `f.read()` always returns `bytes` - elif isinstance(audio_file, tuple): - # Handle tuple case - _, file_content = audio_file[:2] - if isinstance(file_content, str): - with open(file_content, "rb") as f: - binary_data = f.read() # `f.read()` always returns `bytes` - elif isinstance(file_content, bytes): - binary_data = file_content - else: - raise TypeError( - f"Unexpected type in tuple: {type(file_content)}. Expected str or bytes." - ) - elif isinstance(audio_file, bytes): - # Assume it's already binary data - binary_data = audio_file - elif isinstance(audio_file, io.BufferedReader) or isinstance( - audio_file, io.BytesIO - ): - # Handle file-like objects - binary_data = audio_file.read() - - else: - raise TypeError(f"Unsupported type for audio_file: {type(audio_file)}") - - return binary_data - def audio_transcriptions( self, model: str, audio_file: FileTypes, optional_params: dict, + litellm_params: dict, model_response: TranscriptionResponse, timeout: float, max_retries: int, @@ -910,11 +870,8 @@ class BaseLLMHTTPHandler: client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, atranscription: bool = False, headers: dict = {}, - litellm_params: dict = {}, + provider_config: Optional[BaseAudioTranscriptionConfig] = None, ) -> TranscriptionResponse: - provider_config = ProviderConfigManager.get_provider_audio_transcription_config( - model=model, provider=litellm.LlmProviders(custom_llm_provider) - ) if provider_config is None: raise ValueError( f"No provider config found for model: {model} and provider: {custom_llm_provider}" @@ -938,7 +895,18 @@ class BaseLLMHTTPHandler: ) # Handle the audio file based on type - binary_data = self.handle_audio_file(audio_file) + data = provider_config.transform_audio_transcription_request( + model=model, + audio_file=audio_file, + optional_params=optional_params, + litellm_params=litellm_params, + ) + binary_data: Optional[bytes] = None + json_data: Optional[dict] = None + if isinstance(data, bytes): + binary_data = data + else: + json_data = data try: # Make the POST request @@ -946,6 +914,7 @@ class BaseLLMHTTPHandler: url=complete_url, headers=headers, content=binary_data, + json=json_data, timeout=timeout, ) except Exception as e: diff --git a/litellm/llms/deepgram/audio_transcription/transformation.py b/litellm/llms/deepgram/audio_transcription/transformation.py index 06296736ea..90720a77f7 100644 --- a/litellm/llms/deepgram/audio_transcription/transformation.py +++ b/litellm/llms/deepgram/audio_transcription/transformation.py @@ -2,6 +2,7 @@ Translates from OpenAI's `/v1/audio/transcriptions` to Deepgram's `/v1/listen` """ +import io from typing import List, Optional, Union from httpx import Headers, Response @@ -12,7 +13,7 @@ from litellm.types.llms.openai import ( AllMessageValues, OpenAIAudioTranscriptionOptionalParams, ) -from litellm.types.utils import TranscriptionResponse +from litellm.types.utils import FileTypes, TranscriptionResponse from ...base_llm.audio_transcription.transformation import ( BaseAudioTranscriptionConfig, @@ -47,6 +48,55 @@ class DeepgramAudioTranscriptionConfig(BaseAudioTranscriptionConfig): message=error_message, status_code=status_code, headers=headers ) + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> Union[dict, bytes]: + """ + Processes the audio file input based on its type and returns the binary data. + + Args: + audio_file: Can be a file path (str), a tuple (filename, file_content), or binary data (bytes). + + Returns: + The binary data of the audio file. + """ + binary_data: bytes # Explicitly declare the type + + # Handle the audio file based on type + if isinstance(audio_file, str): + # If it's a file path + with open(audio_file, "rb") as f: + binary_data = f.read() # `f.read()` always returns `bytes` + elif isinstance(audio_file, tuple): + # Handle tuple case + _, file_content = audio_file[:2] + if isinstance(file_content, str): + with open(file_content, "rb") as f: + binary_data = f.read() # `f.read()` always returns `bytes` + elif isinstance(file_content, bytes): + binary_data = file_content + else: + raise TypeError( + f"Unexpected type in tuple: {type(file_content)}. Expected str or bytes." + ) + elif isinstance(audio_file, bytes): + # Assume it's already binary data + binary_data = audio_file + elif isinstance(audio_file, io.BufferedReader) or isinstance( + audio_file, io.BytesIO + ): + # Handle file-like objects + binary_data = audio_file.read() + + else: + raise TypeError(f"Unsupported type for audio_file: {type(audio_file)}") + + return binary_data + def transform_audio_transcription_response( self, model: str, diff --git a/litellm/llms/fireworks_ai/audio_transcription/transformation.py b/litellm/llms/fireworks_ai/audio_transcription/transformation.py index 8f35705299..00bb5f2679 100644 --- a/litellm/llms/fireworks_ai/audio_transcription/transformation.py +++ b/litellm/llms/fireworks_ai/audio_transcription/transformation.py @@ -2,27 +2,16 @@ from typing import List from litellm.types.llms.openai import OpenAIAudioTranscriptionOptionalParams -from ...base_llm.audio_transcription.transformation import BaseAudioTranscriptionConfig +from ...openai.transcriptions.whisper_transformation import ( + OpenAIWhisperAudioTranscriptionConfig, +) from ..common_utils import FireworksAIMixin class FireworksAIAudioTranscriptionConfig( - FireworksAIMixin, BaseAudioTranscriptionConfig + FireworksAIMixin, OpenAIWhisperAudioTranscriptionConfig ): def get_supported_openai_params( self, model: str ) -> List[OpenAIAudioTranscriptionOptionalParams]: return ["language", "prompt", "response_format", "timestamp_granularities"] - - def map_openai_params( - self, - non_default_params: dict, - optional_params: dict, - model: str, - drop_params: bool, - ) -> dict: - supported_params = self.get_supported_openai_params(model) - for k, v in non_default_params.items(): - if k in supported_params: - optional_params[k] = v - return optional_params diff --git a/litellm/llms/openai/transcriptions/gpt_transformation.py b/litellm/llms/openai/transcriptions/gpt_transformation.py new file mode 100644 index 0000000000..796e10f515 --- /dev/null +++ b/litellm/llms/openai/transcriptions/gpt_transformation.py @@ -0,0 +1,34 @@ +from typing import List + +from litellm.types.llms.openai import OpenAIAudioTranscriptionOptionalParams +from litellm.types.utils import FileTypes + +from .whisper_transformation import OpenAIWhisperAudioTranscriptionConfig + + +class OpenAIGPTAudioTranscriptionConfig(OpenAIWhisperAudioTranscriptionConfig): + def get_supported_openai_params( + self, model: str + ) -> List[OpenAIAudioTranscriptionOptionalParams]: + """ + Get the supported OpenAI params for the `gpt-4o-transcribe` models + """ + return [ + "language", + "prompt", + "response_format", + "temperature", + "include", + ] + + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> dict: + """ + Transform the audio transcription request + """ + return {"model": model, "file": audio_file, **optional_params} diff --git a/litellm/llms/openai/transcriptions/handler.py b/litellm/llms/openai/transcriptions/handler.py index d9dd3c123b..78a913cbf3 100644 --- a/litellm/llms/openai/transcriptions/handler.py +++ b/litellm/llms/openai/transcriptions/handler.py @@ -7,6 +7,9 @@ from pydantic import BaseModel import litellm from litellm.litellm_core_utils.audio_utils.utils import get_audio_file_name from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.base_llm.audio_transcription.transformation import ( + BaseAudioTranscriptionConfig, +) from litellm.types.utils import FileTypes from litellm.utils import ( TranscriptionResponse, @@ -75,6 +78,7 @@ class OpenAIAudioTranscription(OpenAIChatCompletion): model: str, audio_file: FileTypes, optional_params: dict, + litellm_params: dict, model_response: TranscriptionResponse, timeout: float, max_retries: int, @@ -83,16 +87,24 @@ class OpenAIAudioTranscription(OpenAIChatCompletion): api_base: Optional[str], client=None, atranscription: bool = False, + provider_config: Optional[BaseAudioTranscriptionConfig] = None, ) -> TranscriptionResponse: - data = {"model": model, "file": audio_file, **optional_params} - - if "response_format" not in data or ( - data["response_format"] == "text" or data["response_format"] == "json" - ): - data["response_format"] = ( - "verbose_json" # ensures 'duration' is received - used for cost calculation + """ + Handle audio transcription request + """ + if provider_config is not None: + data = provider_config.transform_audio_transcription_request( + model=model, + audio_file=audio_file, + optional_params=optional_params, + litellm_params=litellm_params, ) + if isinstance(data, bytes): + raise ValueError("OpenAI transformation route requires a dict") + else: + data = {"model": model, "file": audio_file, **optional_params} + if atranscription is True: return self.async_audio_transcriptions( # type: ignore audio_file=audio_file, diff --git a/litellm/llms/openai/transcriptions/whisper_transformation.py b/litellm/llms/openai/transcriptions/whisper_transformation.py new file mode 100644 index 0000000000..5a7d6481a8 --- /dev/null +++ b/litellm/llms/openai/transcriptions/whisper_transformation.py @@ -0,0 +1,97 @@ +from typing import List, Optional, Union + +from httpx import Headers + +from litellm.llms.base_llm.audio_transcription.transformation import ( + BaseAudioTranscriptionConfig, +) +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ( + AllMessageValues, + OpenAIAudioTranscriptionOptionalParams, +) +from litellm.types.utils import FileTypes + +from ..common_utils import OpenAIError + + +class OpenAIWhisperAudioTranscriptionConfig(BaseAudioTranscriptionConfig): + def get_supported_openai_params( + self, model: str + ) -> List[OpenAIAudioTranscriptionOptionalParams]: + """ + Get the supported OpenAI params for the `whisper-1` models + """ + return [ + "language", + "prompt", + "response_format", + "temperature", + "timestamp_granularities", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + """ + Map the OpenAI params to the Whisper params + """ + supported_params = self.get_supported_openai_params(model) + for k, v in non_default_params.items(): + if k in supported_params: + optional_params[k] = v + return optional_params + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + api_key = api_key or get_secret_str("OPENAI_API_KEY") + + auth_header = { + "Authorization": f"Bearer {api_key}", + } + + headers.update(auth_header) + return headers + + def transform_audio_transcription_request( + self, + model: str, + audio_file: FileTypes, + optional_params: dict, + litellm_params: dict, + ) -> dict: + """ + Transform the audio transcription request + """ + + data = {"model": model, "file": audio_file, **optional_params} + + if "response_format" not in data or ( + data["response_format"] == "text" or data["response_format"] == "json" + ): + data["response_format"] = ( + "verbose_json" # ensures 'duration' is received - used for cost calculation + ) + + return data + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, Headers] + ) -> BaseLLMException: + return OpenAIError( + status_code=status_code, + message=error_message, + headers=headers, + ) diff --git a/litellm/main.py b/litellm/main.py index 94e19aab0c..b2732c165c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -5095,6 +5095,12 @@ def transcription( response: Optional[ Union[TranscriptionResponse, Coroutine[Any, Any, TranscriptionResponse]] ] = None + + provider_config = ProviderConfigManager.get_provider_audio_transcription_config( + model=model, + provider=LlmProviders(custom_llm_provider), + ) + if custom_llm_provider == "azure": # azure configs api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") @@ -5161,12 +5167,15 @@ def transcription( max_retries=max_retries, api_base=api_base, api_key=api_key, + provider_config=provider_config, + litellm_params=litellm_params_dict, ) elif custom_llm_provider == "deepgram": response = base_llm_http_handler.audio_transcriptions( model=model, audio_file=file, optional_params=optional_params, + litellm_params=litellm_params_dict, model_response=model_response, atranscription=atranscription, client=( @@ -5185,6 +5194,7 @@ def transcription( api_key=api_key, custom_llm_provider="deepgram", headers={}, + provider_config=provider_config, ) if response is None: raise ValueError("Unmapped provider passed in. Unable to get the response.") diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b8e32a24ce..da780f8353 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1176,21 +1176,40 @@ "output_cost_per_pixel": 0.0, "litellm_provider": "openai" }, + "gpt-4o-transcribe": { + "mode": "audio_transcription", + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.000006, + "output_cost_per_token": 0.00001, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] + }, + "gpt-4o-mini-transcribe": { + "mode": "audio_transcription", + "input_cost_per_token": 0.00000125, + "input_cost_per_audio_token": 0.000003, + "output_cost_per_token": 0.000005, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] + }, "whisper-1": { "mode": "audio_transcription", "input_cost_per_second": 0.0001, "output_cost_per_second": 0.0001, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] }, "tts-1": { "mode": "audio_speech", "input_cost_per_character": 0.000015, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/speech"] }, "tts-1-hd": { "mode": "audio_speech", "input_cost_per_character": 0.000030, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/speech"] }, "azure/gpt-4o-mini-realtime-preview-2024-12-17": { "max_tokens": 4096, diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 09db9f10ee..b9a5765ee4 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -9,6 +9,10 @@ model_list: litellm_params: model: gpt-4o-mini api_key: os.environ/OPENAI_API_KEY + - model_name: "openai/*" + litellm_params: + model: openai/* + api_key: os.environ/OPENAI_API_KEY - model_name: "bedrock-nova" litellm_params: model: us.amazon.nova-pro-v1:0 diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 19899648f5..1c5552637c 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -779,7 +779,12 @@ class LiteLLMFineTuningJobCreate(FineTuningJobCreate): AllEmbeddingInputValues = Union[str, List[str], List[int], List[List[int]]] OpenAIAudioTranscriptionOptionalParams = Literal[ - "language", "prompt", "temperature", "response_format", "timestamp_granularities" + "language", + "prompt", + "temperature", + "response_format", + "timestamp_granularities", + "include", ] diff --git a/litellm/utils.py b/litellm/utils.py index dc97c4d898..3fcb4a803a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6364,6 +6364,11 @@ class ProviderConfigManager: return litellm.FireworksAIAudioTranscriptionConfig() elif litellm.LlmProviders.DEEPGRAM == provider: return litellm.DeepgramAudioTranscriptionConfig() + elif litellm.LlmProviders.OPENAI == provider: + if "gpt-4o" in model: + return litellm.OpenAIGPTAudioTranscriptionConfig() + else: + return litellm.OpenAIWhisperAudioTranscriptionConfig() return None @staticmethod diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b8e32a24ce..da780f8353 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1176,21 +1176,40 @@ "output_cost_per_pixel": 0.0, "litellm_provider": "openai" }, + "gpt-4o-transcribe": { + "mode": "audio_transcription", + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.000006, + "output_cost_per_token": 0.00001, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] + }, + "gpt-4o-mini-transcribe": { + "mode": "audio_transcription", + "input_cost_per_token": 0.00000125, + "input_cost_per_audio_token": 0.000003, + "output_cost_per_token": 0.000005, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] + }, "whisper-1": { "mode": "audio_transcription", "input_cost_per_second": 0.0001, "output_cost_per_second": 0.0001, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/transcriptions"] }, "tts-1": { "mode": "audio_speech", "input_cost_per_character": 0.000015, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/speech"] }, "tts-1-hd": { "mode": "audio_speech", "input_cost_per_character": 0.000030, - "litellm_provider": "openai" + "litellm_provider": "openai", + "supported_endpoints": ["/v1/audio/speech"] }, "azure/gpt-4o-mini-realtime-preview-2024-12-17": { "max_tokens": 4096, diff --git a/tests/litellm/llms/custom_httpx/test_handle_audio_file.py b/tests/litellm/llms/deepgram/audio_transcription/test_deepseek_audio_transcription_transformation.py similarity index 60% rename from tests/litellm/llms/custom_httpx/test_handle_audio_file.py rename to tests/litellm/llms/deepgram/audio_transcription/test_deepseek_audio_transcription_transformation.py index 682803788b..ea035db119 100644 --- a/tests/litellm/llms/custom_httpx/test_handle_audio_file.py +++ b/tests/litellm/llms/deepgram/audio_transcription/test_deepseek_audio_transcription_transformation.py @@ -1,47 +1,57 @@ - -import os import io +import os import pathlib import sys import pytest - sys.path.insert( - 0, os.path.abspath("../../../..") + 0, os.path.abspath("../../../../..") ) # Adds the parent directory to the system path -import litellm -from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler +import litellm +from litellm.llms.deepgram.audio_transcription.transformation import ( + DeepgramAudioTranscriptionConfig, +) + @pytest.fixture def test_bytes(): - return b'litellm', b'litellm' + return b"litellm", b"litellm" + @pytest.fixture def test_io_bytes(test_bytes): return io.BytesIO(test_bytes[0]), test_bytes[1] + @pytest.fixture def test_file(): pwd = os.path.dirname(os.path.realpath(__file__)) pwd_path = pathlib.Path(pwd) - test_root = pwd_path.parents[2] + test_root = pwd_path.parents[3] + print(f"test_root: {test_root}") file_path = os.path.join(test_root, "gettysburg.wav") f = open(file_path, "rb") content = f.read() f.seek(0) return f, content + @pytest.mark.parametrize( "fixture_name", [ "test_bytes", "test_io_bytes", "test_file", - ] + ], ) def test_audio_file_handling(fixture_name, request): - handler = BaseLLMHTTPHandler() + handler = DeepgramAudioTranscriptionConfig() (audio_file, expected_output) = request.getfixturevalue(fixture_name) - assert expected_output == handler.handle_audio_file(audio_file) \ No newline at end of file + assert expected_output == handler.transform_audio_transcription_request( + model="deepseek-audio-transcription", + audio_file=audio_file, + optional_params={}, + litellm_params={}, + ) diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index fea225e4a3..535861ce1a 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -2074,3 +2074,13 @@ def test_delta_object(): assert delta.role == "user" assert not hasattr(delta, "thinking_blocks") assert not hasattr(delta, "reasoning_content") + + +def test_get_provider_audio_transcription_config(): + from litellm.utils import ProviderConfigManager + from litellm.types.utils import LlmProviders + + for provider in LlmProviders: + config = ProviderConfigManager.get_provider_audio_transcription_config( + model="whisper-1", provider=provider + ) diff --git a/tests/llm_translation/test_openai.py b/tests/llm_translation/test_openai.py index 633ff76467..676019b86f 100644 --- a/tests/llm_translation/test_openai.py +++ b/tests/llm_translation/test_openai.py @@ -22,6 +22,7 @@ from litellm.types.llms.openai import ( ChatCompletionAnnotation, ChatCompletionAnnotationURLCitation, ) +from base_audio_transcription_unit_tests import BaseLLMAudioTranscriptionTest def test_openai_prediction_param(): @@ -458,3 +459,13 @@ def test_openai_web_search_streaming(): # Assert this request has at-least one web search annotation assert test_openai_web_search is not None validate_web_search_annotations(test_openai_web_search) + + +class TestOpenAIGPT4OAudioTranscription(BaseLLMAudioTranscriptionTest): + def get_base_audio_transcription_call_args(self) -> dict: + return { + "model": "openai/gpt-4o-transcribe", + } + + def get_custom_llm_provider(self) -> litellm.LlmProviders: + return litellm.LlmProviders.OPENAI diff --git a/tests/local_testing/test_get_model_info.py b/tests/local_testing/test_get_model_info.py index 27b9b1a2b6..7677f90671 100644 --- a/tests/local_testing/test_get_model_info.py +++ b/tests/local_testing/test_get_model_info.py @@ -520,6 +520,8 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "/v1/images/variations", "/v1/images/edits", "/v1/batch", + "/v1/audio/transcriptions", + "/v1/audio/speech", ], }, }, diff --git a/tests/local_testing/test_whisper.py b/tests/local_testing/test_whisper.py index 5bda5456a4..bca7ae2add 100644 --- a/tests/local_testing/test_whisper.py +++ b/tests/local_testing/test_whisper.py @@ -134,3 +134,33 @@ async def test_whisper_log_pre_call(): file=audio_file, ) mock_log_pre_call.assert_called_once() + + +@pytest.mark.asyncio +async def test_whisper_log_pre_call(): + from litellm.litellm_core_utils.litellm_logging import Logging + from datetime import datetime + from unittest.mock import patch, MagicMock + from litellm.integrations.custom_logger import CustomLogger + + custom_logger = CustomLogger() + + litellm.callbacks = [custom_logger] + + with patch.object(custom_logger, "log_pre_api_call") as mock_log_pre_call: + await litellm.atranscription( + model="whisper-1", + file=audio_file, + ) + mock_log_pre_call.assert_called_once() + + +@pytest.mark.asyncio +async def test_gpt_4o_transcribe(): + from litellm.litellm_core_utils.litellm_logging import Logging + from datetime import datetime + from unittest.mock import patch, MagicMock + + await litellm.atranscription( + model="openai/gpt-4o-transcribe", file=audio_file, response_format="json" + ) From 6ba31e1d84d1de51f91667decc11424baac29642 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 26 Mar 2025 23:11:15 -0700 Subject: [PATCH 108/244] Allow viewing keyinfo on request logs (#9568) * feat(view_logs.tsx): show model id + api base in request logs easier debugging * fix(index.tsx): fix length of api base easier viewing * build(ui/): initial commit allowing user to click into key from request logs allows easier debugging of 'what key is this? --- ui/litellm-dashboard/src/app/page.tsx | 3 +- .../src/components/networking.tsx | 6 +- .../src/components/view_logs/columns.tsx | 12 +++- .../src/components/view_logs/index.tsx | 61 ++++++++++++++++--- .../src/components/view_logs/table.tsx | 1 + 5 files changed, 72 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 876a6b7c1c..9b9ec0c9c0 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -5,7 +5,7 @@ import { useSearchParams } from "next/navigation"; import { jwtDecode } from "jwt-decode"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { defaultOrg } from "@/components/common_components/default_org"; -import { Team } from "@/components/key_team_helpers/key_list"; +import { KeyResponse, Team } from "@/components/key_team_helpers/key_list"; import Navbar from "@/components/navbar"; import UserDashboard from "@/components/user_dashboard"; import ModelDashboard from "@/components/model_dashboard"; @@ -344,6 +344,7 @@ export default function CreateKeyPage() { userRole={userRole} token={token} accessToken={accessToken} + allTeams={teams as Team[] ?? []} /> ) : ( { try { + console.log("entering keyInfoV1Call"); let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`; url = `${url}?key=${key}`; // Add key as query parameter @@ -2384,13 +2385,16 @@ export const keyInfoV1Call = async (accessToken: string, key: string) => { // Remove body since this is a GET request }); + console.log("response", response); + if (!response.ok) { const errorData = await response.text(); handleError(errorData); - throw new Error("Network response was not ok"); + message.error("Failed to fetch key info - " + errorData); } const data = await response.json(); + console.log("data", data); return data; } catch (error) { console.error("Failed to fetch key info:", error); diff --git a/ui/litellm-dashboard/src/components/view_logs/columns.tsx b/ui/litellm-dashboard/src/components/view_logs/columns.tsx index 980124ff3b..98f5bfbc9d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/columns.tsx @@ -6,12 +6,14 @@ import { CountryCell } from "./country_cell"; import { getProviderLogoAndName } from "../provider_info_helpers"; import { Tooltip } from "antd"; import { TimeCell } from "./time_cell"; +import { Button } from "@tremor/react"; export type LogEntry = { request_id: string; api_key: string; team_id: string; model: string; + model_id: string; api_base?: string; call_type: string; spend: number; @@ -30,6 +32,7 @@ export type LogEntry = { requester_ip_address?: string; messages: string | any[] | Record; response: string | any[] | Record; + onKeyHashClick?: (keyHash: string) => void; }; export const columns: ColumnDef[] = [ @@ -140,9 +143,16 @@ export const columns: ColumnDef[] = [ accessorKey: "metadata.user_api_key", cell: (info: any) => { const value = String(info.getValue() || "-"); + const onKeyHashClick = info.row.original.onKeyHashClick; + return ( - {value} + onKeyHashClick?.(value)} + > + {value} + ); }, diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index e5df2bbe95..fe82ac4ad8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { useState, useRef, useEffect } from "react"; import { useQueryClient } from "@tanstack/react-query"; -import { uiSpendLogsCall } from "../networking"; +import { uiSpendLogsCall, keyInfoV1Call } from "../networking"; import { DataTable } from "./table"; import { columns, LogEntry } from "./columns"; import { Row } from "@tanstack/react-table"; @@ -12,12 +12,15 @@ import { RequestResponsePanel } from "./columns"; import { ErrorViewer } from './ErrorViewer'; import { internalUserRoles } from "../../utils/roles"; import { ConfigInfoMessage } from './ConfigInfoMessage'; - +import { Tooltip } from "antd"; +import { KeyResponse, Team } from "../key_team_helpers/key_list"; +import KeyInfoView from "../key_info_view"; interface SpendLogsTableProps { accessToken: string | null; token: string | null; userRole: string | null; userID: string | null; + allTeams: Team[]; } interface PaginatedResponse { @@ -38,6 +41,7 @@ export default function SpendLogsTable({ token, userRole, userID, + allTeams, }: SpendLogsTableProps) { const [searchTerm, setSearchTerm] = useState(""); const [showFilters, setShowFilters] = useState(false); @@ -63,14 +67,34 @@ export default function SpendLogsTable({ const [tempKeyHash, setTempKeyHash] = useState(""); const [selectedTeamId, setSelectedTeamId] = useState(""); const [selectedKeyHash, setSelectedKeyHash] = useState(""); + const [selectedKeyInfo, setSelectedKeyInfo] = useState(null); + const [selectedKeyIdInfoView, setSelectedKeyIdInfoView] = useState(null); const [selectedFilter, setSelectedFilter] = useState("Team ID"); const [filterByCurrentUser, setFilterByCurrentUser] = useState( userRole && internalUserRoles.includes(userRole) ); + const [expandedRequestId, setExpandedRequestId] = useState(null); const queryClient = useQueryClient(); + useEffect(() => { + const fetchKeyInfo = async () => { + if (selectedKeyIdInfoView && accessToken) { + const keyData = await keyInfoV1Call(accessToken, selectedKeyIdInfoView); + console.log("keyData", keyData); + + const keyResponse: KeyResponse = { + ...keyData["info"], + "token": selectedKeyIdInfoView, + "api_key": selectedKeyIdInfoView, + }; + setSelectedKeyInfo(keyResponse); + } + }; + fetchKeyInfo(); + }, [selectedKeyIdInfoView, accessToken]); + // Close dropdown when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -206,7 +230,11 @@ export default function SpendLogsTable({ // No need for additional filtering since we're now handling this in the API call return matchesSearch; - }) || []; + + }).map(log => ({ + ...log, + onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash) + })) || []; // Add this function to handle manual refresh const handleRefresh = () => { @@ -242,7 +270,10 @@ export default function SpendLogsTable({

- + {selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? ( + setSelectedKeyIdInfoView(null)} /> + ) : ( + <>
@@ -594,6 +625,8 @@ export default function SpendLogsTable({ expandedRequestId={expandedRequestId} />
+ + )}
); } @@ -671,18 +704,25 @@ function RequestViewer({ row }: { row: Row }) { Model: {row.original.model}
+
+ Model ID: + {row.original.model_id} +
Provider: {row.original.custom_llm_provider || "-"}
+
+ API Base: + + {row.original.api_base || "-"} + +
Start Time: {row.original.startTime}
-
- End Time: - {row.original.endTime} -
+
@@ -712,6 +752,11 @@ function RequestViewer({ row }: { row: Row }) { }`}> {(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"} + +
+
+ End Time: + {row.original.endTime}
diff --git a/ui/litellm-dashboard/src/components/view_logs/table.tsx b/ui/litellm-dashboard/src/components/view_logs/table.tsx index af9bc5afd2..ed728de074 100644 --- a/ui/litellm-dashboard/src/components/view_logs/table.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/table.tsx @@ -25,6 +25,7 @@ interface DataTableProps { isLoading?: boolean; expandedRequestId?: string | null; onRowExpand?: (requestId: string | null) => void; + setSelectedKeyIdInfoView?: (keyId: string | null) => void; } export function DataTable({ From 3ca34a181cae83b8b3c73e5937f07e36509a7f35 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Mar 2025 23:23:16 -0700 Subject: [PATCH 109/244] docs(openai.md): add gpt-4o-transcribe to docs --- docs/my-website/docs/providers/openai.md | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/my-website/docs/providers/openai.md b/docs/my-website/docs/providers/openai.md index 794f3da647..9ab9061aaa 100644 --- a/docs/my-website/docs/providers/openai.md +++ b/docs/my-website/docs/providers/openai.md @@ -325,6 +325,74 @@ curl -X POST 'http://0.0.0.0:4000/chat/completions' \ | fine tuned `gpt-3.5-turbo-0613` | `response = completion(model="ft:gpt-3.5-turbo-0613", messages=messages)` | +## OpenAI Audio Transcription + +LiteLLM supports OpenAI Audio Transcription endpoint. + +Supported models: + +| Model Name | Function Call | +|---------------------------|-----------------------------------------------------------------| +| `whisper-1` | `response = completion(model="whisper-1", file=audio_file)` | +| `gpt-4o-transcribe` | `response = completion(model="gpt-4o-transcribe", file=audio_file)` | +| `gpt-4o-mini-transcribe` | `response = completion(model="gpt-4o-mini-transcribe", file=audio_file)` | + + + + +```python +from litellm import transcription +import os + +# set api keys +os.environ["OPENAI_API_KEY"] = "" +audio_file = open("/path/to/audio.mp3", "rb") + +response = transcription(model="gpt-4o-transcribe", file=audio_file) + +print(f"response: {response}") +``` + + + + +1. Setup config.yaml + +```yaml +model_list: +- model_name: gpt-4o-transcribe + litellm_params: + model: gpt-4o-transcribe + api_key: os.environ/OPENAI_API_KEY + model_info: + mode: audio_transcription + +general_settings: + master_key: sk-1234 +``` + +2. Start the proxy + +```bash +litellm --config config.yaml +``` + +3. Test it! + +```bash +curl --location 'http://0.0.0.0:8000/v1/audio/transcriptions' \ +--header 'Authorization: Bearer sk-1234' \ +--form 'file=@"/Users/krrishdholakia/Downloads/gettysburg.wav"' \ +--form 'model="gpt-4o-transcribe"' +``` + + + + + + + + ## Advanced ### Getting OpenAI API Response Headers From 63c9f5937388fc9f87e9298e18f281d2b01d0f41 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 12:06:31 -0700 Subject: [PATCH 110/244] Allow team admins to add/update/delete models on UI + show api base and model id on request logs (#9572) * feat(view_logs.tsx): show model id + api base in request logs easier debugging * fix(index.tsx): fix length of api base easier viewing * refactor(leftnav.tsx): show models tab to team admin * feat(model_dashboard.tsx): add explainer for what the 'models' page is for team admin helps them understand how they can use it * feat(model_management_endpoints.py): restrict model add by team to just team admin allow team admin to add models via non-team keys (e.g. ui token) * test(test_add_update_models.py): update unit testing for new behaviour * fix(model_dashboard.tsx): show user the models * feat(proxy_server.py): add new query param 'user_models_only' to `/v2/model/info` Allows user to retrieve just the models they've added Used in UI to show internal users just the models they've added * feat(model_dashboard.tsx): allow team admins to view their own models * fix: allow ui user to fetch model cost map * feat(add_model_tab.tsx): require team admins to specify team when onboarding models * fix(_types.py): add `/v1/model/info` to info route `/model/info` was already there * fix(model_info_view.tsx): allow user to edit a model they created * fix(model_management_endpoints.py): allow team admin to update team model * feat(model_managament_endpoints.py): allow team admin to delete team models * fix(model_management_endpoints.py): don't require team id to be set when adding a model * fix(proxy_server.py): fix linting error * fix: fix ui linting error * fix(model_management_endpoints.py): ensure consistent auth checks on all model calls * test: remove old test - function no longer exists in same form * test: add updated mock testing --- litellm/proxy/_types.py | 4 + .../model_management_endpoints.py | 170 ++++++++++++--- litellm/proxy/proxy_server.py | 51 ++++- .../test_model_management_endpoints.py | 196 ++++++++++++++++++ tests/local_testing/test_add_update_models.py | 43 ---- .../components/add_model/add_model_tab.tsx | 25 +++ .../add_model/advanced_settings.tsx | 7 - .../src/components/leftnav.tsx | 2 +- .../src/components/model_dashboard.tsx | 103 +++++---- .../src/components/model_info_view.tsx | 12 +- .../src/components/networking.tsx | 14 +- 11 files changed, 483 insertions(+), 144 deletions(-) create mode 100644 tests/litellm/proxy/management_endpoints/test_model_management_endpoints.py diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e6294baab8..6e242ddacb 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -292,6 +292,7 @@ class LiteLLMRoutes(enum.Enum): "/team/available", "/user/info", "/model/info", + "/v1/model/info", "/v2/model/info", "/v2/key/info", "/model_group/info", @@ -386,6 +387,7 @@ class LiteLLMRoutes(enum.Enum): "/global/predict/spend/logs", "/global/activity", "/health/services", + "/get/litellm_model_cost_map", ] + info_routes internal_user_routes = [ @@ -412,6 +414,8 @@ class LiteLLMRoutes(enum.Enum): "/team/member_add", "/team/member_delete", "/model/new", + "/model/update", + "/model/delete", ] # routes that manage their own allowed/disallowed logic ## Org Admin Routes ## diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 2a2b7eae24..f83abe77be 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -13,7 +13,7 @@ model/{model_id}/update - PATCH endpoint for model update. import asyncio import json import uuid -from typing import Optional, cast +from typing import Literal, Optional, Union, cast from fastapi import APIRouter, Depends, HTTPException, Request, status from pydantic import BaseModel @@ -23,6 +23,7 @@ from litellm.constants import LITELLM_PROXY_ADMIN_NAME from litellm.proxy._types import ( CommonProxyErrors, LiteLLM_ProxyModelTable, + LiteLLM_TeamTable, LitellmTableNames, LitellmUserRoles, ModelInfoDelete, @@ -35,6 +36,7 @@ from litellm.proxy._types import ( ) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper +from litellm.proxy.management_endpoints.common_utils import _is_user_team_admin from litellm.proxy.management_endpoints.team_endpoints import ( team_model_add, update_team, @@ -317,22 +319,111 @@ async def _add_team_model_to_db( return model_response -def check_if_team_id_matches_key( - team_id: Optional[str], user_api_key_dict: UserAPIKeyAuth -) -> bool: - can_make_call = True - if ( - user_api_key_dict.user_role - and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN - ): +class ModelManagementAuthChecks: + """ + Common auth checks for model management endpoints + """ + + @staticmethod + def can_user_make_team_model_call( + team_id: str, + user_api_key_dict: UserAPIKeyAuth, + team_obj: Optional[LiteLLM_TeamTable] = None, + premium_user: bool = False, + ) -> Literal[True]: + if premium_user is False: + raise HTTPException( + status_code=403, + detail={"error": CommonProxyErrors.not_premium_user.value}, + ) + if ( + user_api_key_dict.user_role + and user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN + ): + return True + elif team_obj is None or not _is_user_team_admin( + user_api_key_dict=user_api_key_dict, team_obj=team_obj + ): + raise HTTPException( + status_code=403, + detail={ + "error": "Team ID={} does not match the API key's team ID={}, OR you are not the admin for this team. Check `/user/info` to verify your team admin status.".format( + team_id, user_api_key_dict.team_id + ) + }, + ) + return True + + @staticmethod + async def allow_team_model_action( + model_params: Union[Deployment, updateDeployment], + user_api_key_dict: UserAPIKeyAuth, + prisma_client: PrismaClient, + premium_user: bool, + ) -> Literal[True]: + if model_params.model_info is None or model_params.model_info.team_id is None: + return True + if model_params.model_info.team_id is not None and premium_user is not True: + raise HTTPException( + status_code=403, + detail={"error": CommonProxyErrors.not_premium_user.value}, + ) + + _existing_team_row = await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": model_params.model_info.team_id} + ) + + if _existing_team_row is None: + raise HTTPException( + status_code=400, + detail={ + "error": "Team id={} does not exist in db".format( + model_params.model_info.team_id + ) + }, + ) + existing_team_row = LiteLLM_TeamTable(**_existing_team_row.model_dump()) + + ModelManagementAuthChecks.can_user_make_team_model_call( + team_id=model_params.model_info.team_id, + user_api_key_dict=user_api_key_dict, + team_obj=existing_team_row, + premium_user=premium_user, + ) + return True + + @staticmethod + async def can_user_make_model_call( + model_params: Union[Deployment, updateDeployment], + user_api_key_dict: UserAPIKeyAuth, + prisma_client: PrismaClient, + premium_user: bool, + ) -> Literal[True]: + + ## Check team model auth + if ( + model_params.model_info is not None + and model_params.model_info.team_id is not None + ): + return ModelManagementAuthChecks.can_user_make_team_model_call( + team_id=model_params.model_info.team_id, + user_api_key_dict=user_api_key_dict, + premium_user=premium_user, + ) + ## Check non-team model auth + elif user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException( + status_code=403, + detail={ + "error": "User does not have permission to make this model call. Your role={}. You can only make model calls if you are a PROXY_ADMIN or if you are a team admin, by specifying a team_id in the model_info.".format( + user_api_key_dict.user_role + ) + }, + ) + else: + return True + return True - if team_id is None: - if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: - can_make_call = False - else: - if user_api_key_dict.team_id != team_id: - can_make_call = False - return can_make_call #### [BETA] - This is a beta endpoint, format might change based on user feedback. - https://github.com/BerriAI/litellm/issues/964 @@ -358,6 +449,7 @@ async def delete_model( from litellm.proxy.proxy_server import ( llm_router, + premium_user, prisma_client, store_model_in_db, ) @@ -370,6 +462,23 @@ async def delete_model( }, ) + model_in_db = await prisma_client.db.litellm_proxymodeltable.find_unique( + where={"model_id": model_info.id} + ) + if model_in_db is None: + raise HTTPException( + status_code=400, + detail={"error": f"Model with id={model_info.id} not found in db"}, + ) + + model_params = Deployment(**model_in_db.model_dump()) + await ModelManagementAuthChecks.can_user_make_model_call( + model_params=model_params, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + premium_user=premium_user, + ) + # update DB if store_model_in_db is True: """ @@ -464,19 +573,13 @@ async def add_new_model( }, ) - if model_params.model_info.team_id is not None and premium_user is not True: - raise HTTPException( - status_code=403, - detail={"error": CommonProxyErrors.not_premium_user.value}, - ) - - if not check_if_team_id_matches_key( - team_id=model_params.model_info.team_id, user_api_key_dict=user_api_key_dict - ): - raise HTTPException( - status_code=403, - detail={"error": "Team ID does not match the API key's team ID"}, - ) + ## Auth check + await ModelManagementAuthChecks.can_user_make_model_call( + model_params=model_params, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + premium_user=premium_user, + ) model_response: Optional[LiteLLM_ProxyModelTable] = None # update DB @@ -593,6 +696,7 @@ async def update_model( from litellm.proxy.proxy_server import ( LITELLM_PROXY_ADMIN_NAME, llm_router, + premium_user, prisma_client, store_model_in_db, ) @@ -606,6 +710,14 @@ async def update_model( "error": "No DB Connected. Here's how to do it - https://docs.litellm.ai/docs/proxy/virtual_keys" }, ) + + await ModelManagementAuthChecks.can_user_make_model_call( + model_params=model_params, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + premium_user=premium_user, + ) + # update DB if store_model_in_db is True: _model_id = None diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c2044ac1d6..5d7e92fd73 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -215,7 +215,6 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( from litellm.proxy.management_endpoints.model_management_endpoints import ( _add_model_to_db, _add_team_model_to_db, - check_if_team_id_matches_key, ) from litellm.proxy.management_endpoints.model_management_endpoints import ( router as model_management_router, @@ -5494,9 +5493,40 @@ async def transform_request(request: TransformRequestBody): return return_raw_request(endpoint=request.call_type, kwargs=request.request_body) +async def _check_if_model_is_user_added( + models: List[Dict], + user_api_key_dict: UserAPIKeyAuth, + prisma_client: Optional[PrismaClient], +) -> List[Dict]: + """ + Check if model is in db + + Check if db model is 'created_by' == user_api_key_dict.user_id + + Only return models that match + """ + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, + ) + filtered_models = [] + for model in models: + id = model.get("model_info", {}).get("id", None) + if id is None: + continue + db_model = await prisma_client.db.litellm_proxymodeltable.find_unique( + where={"model_id": id} + ) + if db_model is not None: + if db_model.created_by == user_api_key_dict.user_id: + filtered_models.append(model) + return filtered_models + + @router.get( "/v2/model/info", - description="v2 - returns all the models set on the config.yaml, shows 'user_access' = True if the user has access to the model. Provides more info about each model in /models, including config.yaml descriptions (except api key and api base)", + description="v2 - returns models available to the user based on their API key permissions. Shows model info from config.yaml (except api key and api base). Filter to just user-added models with ?user_models_only=true", tags=["model management"], dependencies=[Depends(user_api_key_auth)], include_in_schema=False, @@ -5506,6 +5536,9 @@ async def model_info_v2( model: Optional[str] = fastapi.Query( None, description="Specify the model name (optional)" ), + user_models_only: Optional[bool] = fastapi.Query( + False, description="Only return models added by this user" + ), debug: Optional[bool] = False, ): """ @@ -5536,6 +5569,20 @@ async def model_info_v2( if model is not None: all_models = [m for m in all_models if m["model_name"] == model] + if user_models_only is True: + """ + Check if model is in db + + Check if db model is 'created_by' == user_api_key_dict.user_id + + Only return models that match + """ + all_models = await _check_if_model_is_user_added( + models=all_models, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + ) + # fill in model info based on config.yaml and litellm model_prices_and_context_window.json for _model in all_models: # provided model_info in config.yaml diff --git a/tests/litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/litellm/proxy/management_endpoints/test_model_management_endpoints.py new file mode 100644 index 0000000000..49e3150e61 --- /dev/null +++ b/tests/litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -0,0 +1,196 @@ +import json +import os +import sys +from typing import Optional + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../../..") +) # Adds the parent directory to the system path +from litellm.proxy._types import ( + LiteLLM_TeamTable, + LitellmUserRoles, + Member, + UserAPIKeyAuth, +) +from litellm.proxy.management_endpoints.model_management_endpoints import ( + ModelManagementAuthChecks, +) +from litellm.proxy.utils import PrismaClient +from litellm.types.router import Deployment, LiteLLM_Params, updateDeployment + + +class MockPrismaClient: + def __init__(self, team_exists: bool = True): + self.team_exists = team_exists + self.db = self + + async def find_unique(self, where): + if self.team_exists: + return LiteLLM_TeamTable( + team_id=where["team_id"], + team_alias="test_team", + members_with_roles=[Member(user_id="test_user", role="admin")], + ) + return None + + @property + def litellm_teamtable(self): + return self + + +class TestModelManagementAuthChecks: + def setup_method(self): + """Setup test cases""" + self.admin_user = UserAPIKeyAuth( + user_id="test_admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + + self.normal_user = UserAPIKeyAuth( + user_id="test_user", user_role=LitellmUserRoles.INTERNAL_USER + ) + + self.team_admin_user = UserAPIKeyAuth( + user_id="test_user", + team_id="test_team", + user_role=LitellmUserRoles.INTERNAL_USER, + ) + + @pytest.mark.asyncio + async def test_can_user_make_team_model_call_admin_success(self): + """Test that admin users can make team model calls""" + result = ModelManagementAuthChecks.can_user_make_team_model_call( + team_id="test_team", user_api_key_dict=self.admin_user, premium_user=True + ) + assert result is True + + @pytest.mark.asyncio + async def test_can_user_make_team_model_call_non_premium_fails(self): + """Test that non-premium users cannot make team model calls""" + with pytest.raises(Exception) as exc_info: + ModelManagementAuthChecks.can_user_make_team_model_call( + team_id="test_team", + user_api_key_dict=self.admin_user, + premium_user=False, + ) + assert "403" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_can_user_make_team_model_call_team_admin_success(self): + """Test that team admins can make calls for their team""" + team_obj = LiteLLM_TeamTable( + team_id="test_team", + team_alias="test_team", + members_with_roles=[ + Member(user_id=self.team_admin_user.user_id, role="admin") + ], + ) + + result = ModelManagementAuthChecks.can_user_make_team_model_call( + team_id="test_team", + user_api_key_dict=self.team_admin_user, + team_obj=team_obj, + premium_user=True, + ) + assert result is True + + @pytest.mark.asyncio + async def test_allow_team_model_action_success(self): + """Test successful team model action""" + model_params = Deployment( + model_name="test_model", + litellm_params=LiteLLM_Params(model="test_model", team_id="test_team"), + model_info={"team_id": "test_team"}, + ) + prisma_client = MockPrismaClient(team_exists=True) + + result = await ModelManagementAuthChecks.allow_team_model_action( + model_params=model_params, + user_api_key_dict=self.admin_user, + prisma_client=prisma_client, + premium_user=True, + ) + assert result is True + + @pytest.mark.asyncio + async def test_allow_team_model_action_non_premium_fails(self): + """Test team model action fails for non-premium users""" + model_params = Deployment( + model_name="test_model", + litellm_params=LiteLLM_Params(model="test_model", team_id="test_team"), + model_info={"team_id": "test_team"}, + ) + prisma_client = MockPrismaClient(team_exists=True) + + with pytest.raises(Exception) as exc_info: + await ModelManagementAuthChecks.allow_team_model_action( + model_params=model_params, + user_api_key_dict=self.admin_user, + prisma_client=prisma_client, + premium_user=False, + ) + assert "403" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_allow_team_model_action_nonexistent_team_fails(self): + """Test team model action fails for non-existent team""" + model_params = Deployment( + model_name="test_model", + litellm_params=LiteLLM_Params( + model="test_model", + ), + model_info={"team_id": "nonexistent_team"}, + ) + prisma_client = MockPrismaClient(team_exists=False) + + with pytest.raises(Exception) as exc_info: + await ModelManagementAuthChecks.allow_team_model_action( + model_params=model_params, + user_api_key_dict=self.admin_user, + prisma_client=prisma_client, + premium_user=True, + ) + assert "400" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_can_user_make_model_call_admin_success(self): + """Test that admin users can make any model call""" + model_params = Deployment( + model_name="test_model", + litellm_params=LiteLLM_Params( + model="test_model", + ), + model_info={"team_id": "test_team"}, + ) + prisma_client = MockPrismaClient() + + result = await ModelManagementAuthChecks.can_user_make_model_call( + model_params=model_params, + user_api_key_dict=self.admin_user, + prisma_client=prisma_client, + premium_user=True, + ) + assert result is True + + @pytest.mark.asyncio + async def test_can_user_make_model_call_normal_user_fails(self): + """Test that normal users cannot make model calls""" + model_params = Deployment( + model_name="test_model", + litellm_params=LiteLLM_Params( + model="test_model", + ), + model_info={"team_id": "test_team"}, + ) + prisma_client = MockPrismaClient() + + with pytest.raises(Exception) as exc_info: + await ModelManagementAuthChecks.can_user_make_model_call( + model_params=model_params, + user_api_key_dict=self.normal_user, + prisma_client=prisma_client, + premium_user=True, + ) + assert "403" in str(exc_info.value) diff --git a/tests/local_testing/test_add_update_models.py b/tests/local_testing/test_add_update_models.py index 1ea714d9b7..163687c1c2 100644 --- a/tests/local_testing/test_add_update_models.py +++ b/tests/local_testing/test_add_update_models.py @@ -110,49 +110,6 @@ async def test_add_new_model(prisma_client): assert _new_model_in_db is not None -@pytest.mark.parametrize( - "team_id, key_team_id, user_role, expected_result", - [ - ("1234", "1234", LitellmUserRoles.PROXY_ADMIN.value, True), - ( - "1234", - "1235", - LitellmUserRoles.PROXY_ADMIN.value, - True, - ), # proxy admin can add models for any team - (None, "1234", LitellmUserRoles.PROXY_ADMIN.value, True), - (None, None, LitellmUserRoles.PROXY_ADMIN.value, True), - ( - "1234", - "1234", - LitellmUserRoles.INTERNAL_USER.value, - True, - ), # internal users can add models for their team - ("1234", "1235", LitellmUserRoles.INTERNAL_USER.value, False), - (None, "1234", LitellmUserRoles.INTERNAL_USER.value, False), - ( - None, - None, - LitellmUserRoles.INTERNAL_USER.value, - False, - ), # internal users cannot add models by default - ], -) -def test_can_add_model(team_id, key_team_id, user_role, expected_result): - from litellm.proxy.proxy_server import check_if_team_id_matches_key - - args = { - "team_id": team_id, - "user_api_key_dict": UserAPIKeyAuth( - user_role=user_role, - api_key="sk-1234", - team_id=key_team_id, - ), - } - - assert check_if_team_id_matches_key(**args) is expected_result - - @pytest.mark.asyncio @pytest.mark.skip(reason="new feature, tests passing locally") async def test_add_update_model(prisma_client): diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx index e2115e8467..1e0a2316df 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx @@ -13,6 +13,8 @@ import ConnectionErrorDisplay from "./model_connection_test"; import { TEST_MODES } from "./add_model_modes"; import { Row, Col } from "antd"; import { Text, TextInput } from "@tremor/react"; +import TeamDropdown from "../common_components/team_dropdown"; +import { all_admin_roles } from "@/utils/roles"; interface AddModelTabProps { form: FormInstance; @@ -28,6 +30,7 @@ interface AddModelTabProps { teams: Team[] | null; credentials: CredentialItem[]; accessToken: string; + userRole: string; } const { Title, Link } = Typography; @@ -46,6 +49,7 @@ const AddModelTab: React.FC = ({ teams, credentials, accessToken, + userRole, }) => { // State for test mode and connection testing const [testMode, setTestMode] = useState("chat"); @@ -64,6 +68,8 @@ const AddModelTab: React.FC = ({ setIsResultModalVisible(true); }; + const isAdmin = all_admin_roles.includes(userRole); + return ( <> Add new model @@ -217,6 +223,25 @@ const AddModelTab: React.FC = ({ ); }} +
+
+ Team Settings +
+
+ + + = ({
- - - }, { key: "3", page: "llm-playground", label: "Test Key", icon: , roles: rolesWithWriteAccess }, - { key: "2", page: "models", label: "Models", icon: , roles: all_admin_roles }, + { key: "2", page: "models", label: "Models", icon: , roles: rolesWithWriteAccess }, { key: "4", page: "usage", label: "Usage", icon: }, { key: "6", page: "teams", label: "Teams", icon: }, { key: "17", page: "organizations", label: "Organizations", icon: , roles: all_admin_roles }, diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index 1e80e2250e..493f9ba426 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -111,6 +111,7 @@ import ModelInfoView from "./model_info_view"; import AddModelTab from "./add_model/add_model_tab"; import { ModelDataTable } from "./model_dashboard/table"; import { columns } from "./model_dashboard/columns"; +import { all_admin_roles } from "@/utils/roles"; interface ModelDashboardProps { accessToken: string | null; @@ -479,9 +480,6 @@ const ModelDashboard: React.FC = ({ } const fetchData = async () => { try { - const _providerSettings = await modelSettingsCall(accessToken); - setProviderSettings(_providerSettings); - // Replace with your actual API call for model data const modelDataResponse = await modelInfoCall( accessToken, @@ -490,6 +488,12 @@ const ModelDashboard: React.FC = ({ ); console.log("Model data response:", modelDataResponse.data); setModelData(modelDataResponse); + const _providerSettings = await modelSettingsCall(accessToken); + if (_providerSettings) { + setProviderSettings(_providerSettings); + } + + // loop through modelDataResponse and get all`model_name` values let all_model_groups: Set = new Set(); @@ -1001,7 +1005,7 @@ const ModelDashboard: React.FC = ({ ); let dynamicProviderForm: ProviderSettings | undefined = undefined; - if (providerKey) { + if (providerKey && providerSettings) { dynamicProviderForm = providerSettings.find( (provider) => provider.name === provider_map[providerKey] ); @@ -1055,16 +1059,17 @@ const ModelDashboard: React.FC = ({ /> ) : ( +
- All Models + {all_admin_roles.includes(userRole) ? All Models : Your Models} Add Model - LLM Credentials - + {all_admin_roles.includes(userRole) && LLM Credentials} + {all_admin_roles.includes(userRole) &&
/health Models
-
- Model Analytics - Model Retry Settings +
} + {all_admin_roles.includes(userRole) && Model Analytics} + {all_admin_roles.includes(userRole) && Model Retry Settings}
@@ -1081,26 +1086,25 @@ const ModelDashboard: React.FC = ({
- -
- Filter by Public Model Name +
+ {/* Left side - Title and description */} +
+ Model Management + {!all_admin_roles.includes(userRole) && + Add models for teams you are an admin for. + } +
+ + {/* Right side - Filter */} +
+ Filter by Public Model Name:
- - selectedModelGroup === "all" || - model.model_name === selectedModelGroup || - !selectedModelGroup - )} - isLoading={false} // Add loading state if needed - /> - - handleEditModelSubmit(data, accessToken, setEditModalVisible, setSelectedModel)} +
+ + selectedModelGroup === "all" || + model.model_name === selectedModelGroup || + !selectedModelGroup + )} + isLoading={false} // Add loading state if needed /> @@ -1153,6 +1151,7 @@ const ModelDashboard: React.FC = ({ teams={teams} credentials={credentialsList} accessToken={accessToken} + userRole={userRole} /> diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 2d05e185c1..7a1c9a7abc 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -58,7 +58,7 @@ export default function ModelInfoView({ const [isEditing, setIsEditing] = useState(false); const [existingCredential, setExistingCredential] = useState(null); - const canEditModel = userRole === "Admin"; + const canEditModel = userRole === "Admin" || modelData.model_info.created_by === userID; const isAdmin = userRole === "Admin"; const usingExistingCredential = modelData.litellm_params?.litellm_credential_name != null && modelData.litellm_params?.litellm_credential_name != undefined; @@ -209,8 +209,8 @@ export default function ModelInfoView({ Public Model Name: {getDisplayModelName(modelData)} {modelData.model_info.id}
- {isAdmin && ( -
+
+ {isAdmin && ( Re-use Credentials + )} + {canEditModel && ( Delete Model -
- )} + )} +
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index cf0b9ec5dc..fbe0374e34 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -1,6 +1,7 @@ /** * Helper file for calls being made to proxy */ +import { all_admin_roles } from "@/utils/roles"; import { message } from "antd"; const isLocal = process.env.NODE_ENV === "development"; @@ -131,9 +132,9 @@ export const modelCreateCall = async ( }); if (!response.ok) { - const errorData = await response.json(); + const errorData = await response.text(); const errorMsg = - errorData.error?.message?.error || + errorData|| "Network response was not ok"; message.error(errorMsg); throw new Error(errorMsg); @@ -183,9 +184,8 @@ export const modelSettingsCall = async (accessToken: String) => { //message.info("Received model data"); return data; // Handle success - you might want to update some state or UI based on the created key - } catch (error) { - console.error("Failed to get callbacks:", error); - throw error; + } catch (error: any) { + console.error("Failed to get model settings:", error); } }; @@ -1213,8 +1213,12 @@ export const modelInfoCall = async ( * Get all models on proxy */ try { + console.log("modelInfoCall:", accessToken, userID, userRole); let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/model/info` : `/v2/model/info`; + if (!all_admin_roles.includes(userRole as string)) { // only show users models they've added + url += `?user_models_only=true`; + } //message.info("Requesting model data"); const response = await fetch(url, { method: "GET", From f1fa0044cb1ee6b88f3bdfb199ddd018e6d39e54 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 12:07:17 -0700 Subject: [PATCH 111/244] =?UTF-8?q?bump:=20version=201.64.1=20=E2=86=92=20?= =?UTF-8?q?1.65.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7aed9c3e3c..c3b0fd3224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.64.1" +version = "1.65.0" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -105,7 +105,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.64.1" +version = "1.65.0" version_files = [ "pyproject.toml:^version" ] From f1f40eba3f06074e2d747ac54158410b0a323df8 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 12:14:29 -0700 Subject: [PATCH 112/244] docs(index.md): document new team model flow --- .../img/release_notes/team_model_add.png | Bin 0 -> 71649 bytes .../my-website/release_notes/v1.65.0/index.md | 34 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 docs/my-website/img/release_notes/team_model_add.png create mode 100644 docs/my-website/release_notes/v1.65.0/index.md diff --git a/docs/my-website/img/release_notes/team_model_add.png b/docs/my-website/img/release_notes/team_model_add.png new file mode 100644 index 0000000000000000000000000000000000000000..f548469846b6c9de11d81629be55bdc8a721c5ac GIT binary patch literal 71649 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 b?(X+($AgE?;q~@$|BMXGZewr$_4NM$R6v%K literal 0 HcmV?d00001 diff --git a/docs/my-website/release_notes/v1.65.0/index.md b/docs/my-website/release_notes/v1.65.0/index.md new file mode 100644 index 0000000000..46525ea55f --- /dev/null +++ b/docs/my-website/release_notes/v1.65.0/index.md @@ -0,0 +1,34 @@ +--- +title: v1.65.0 - Team Model Add - update +slug: v1.65.0 +date: 2025-03-28T10:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://media.licdn.com/dms/image/v2/D4D03AQGrlsJ3aqpHmQ/profile-displayphoto-shrink_400_400/B4DZSAzgP7HYAg-/0/1737327772964?e=1743638400&v=beta&t=39KOXMUFedvukiWWVPHf3qI45fuQD7lNglICwN31DrI + - name: Ishaan Jaffer + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +tags: [management endpoints, team models, ui] +hide_table_of_contents: false +--- + +import Image from '@theme/IdealImage'; + +v1.65.0 updates the `/model/new` endpoint to prevent non-team admins from creating team models. + +This means that only proxy admins or team admins can create team models. + +## Additional Changes + +- Allows team admins to call `/model/update` to update team models. +- Allows team admins to call `/model/delete` to delete team models. +- Introduces new `user_models_only` param to `/v2/model/info` - only return models added by this user. + + +These changes enable team admins to add and manage models for their team on the LiteLLM UI + API. + + + \ No newline at end of file From 11838e1c3bd93025a2d591b068d18df1849ceb44 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 14:50:41 -0700 Subject: [PATCH 113/244] Litellm fix db testing (#9593) * ci: fix test * test: safely change db url * fix: print db url * test: remove delenv --- .circleci/config.yml | 6 ++++++ tests/proxy_unit_tests/test_db_schema_migration.py | 4 ++-- tests/proxy_unit_tests/test_key_generate_prisma.py | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 91636c5625..e1488a9083 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -500,6 +500,12 @@ jobs: working_directory: ~/project steps: - checkout + - run: + name: Install PostgreSQL + command: | + sudo apt-get update + sudo apt-get install postgresql postgresql-contrib + echo 'export PATH=/usr/lib/postgresql/*/bin:$PATH' >> $BASH_ENV - setup_google_dns - run: name: Show git commit hash diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py index 59367aa9cb..47125e86a7 100644 --- a/tests/proxy_unit_tests/test_db_schema_migration.py +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -17,12 +17,12 @@ def schema_setup(postgresql_my): return postgresql_my -def test_schema_migration_check(schema_setup): +def test_aaaasschema_migration_check(schema_setup, monkeypatch): """Test to check if schema requires migration""" # Set test database URL test_db_url = f"postgresql://{schema_setup.info.user}:@{schema_setup.info.host}:{schema_setup.info.port}/{schema_setup.info.dbname}" # test_db_url = "postgresql://neondb_owner:npg_JiZPS0DAhRn4@ep-delicate-wave-a55cvbuc.us-east-2.aws.neon.tech/neondb?sslmode=require" - os.environ["DATABASE_URL"] = test_db_url + monkeypatch.setenv("DATABASE_URL", test_db_url) deploy_dir = Path("./deploy") source_migrations_dir = deploy_dir / "migrations" diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index f922b2c27f..bbfa5b1c11 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -3286,6 +3286,7 @@ async def test_aadmin_only_routes(prisma_client): only an admin should be able to access admin only routes """ litellm.set_verbose = True + print(f"os.getenv('DATABASE_URL')={os.getenv('DATABASE_URL')}") setattr(litellm.proxy.proxy_server, "prisma_client", prisma_client) setattr(litellm.proxy.proxy_server, "master_key", "sk-1234") await litellm.proxy.proxy_server.prisma_client.connect() @@ -3882,6 +3883,7 @@ async def test_get_paginated_teams(prisma_client): @pytest.mark.asyncio +@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize("entity_type", ["key", "user", "team"]) async def test_reset_budget_job(prisma_client, entity_type): """ From 21e3b764f570bdf23ebca9afda4875f83503f643 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 16:31:23 -0700 Subject: [PATCH 114/244] use DBSpendUpdateWriter class for managing DB spend updates --- litellm/proxy/db/db_spend_update_writer.py | 233 ++++++++++++++++++ .../proxy/hooks/proxy_track_cost_callback.py | 8 +- litellm/proxy/proxy_server.py | 221 +---------------- .../hooks/test_proxy_track_cost_callback.py | 3 +- .../test_unit_test_proxy_hooks.py | 13 +- 5 files changed, 255 insertions(+), 223 deletions(-) create mode 100644 litellm/proxy/db/db_spend_update_writer.py diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py new file mode 100644 index 0000000000..d5f2ef5228 --- /dev/null +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -0,0 +1,233 @@ +import asyncio +import os +import traceback +from datetime import datetime +from typing import Any, Optional, Union + +import litellm +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import LiteLLM_UserTable, SpendLogsPayload +from litellm.proxy.proxy_server import hash_token +from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload +from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend + + +class DBSpendUpdateWriter: + + @staticmethod + async def update_database( # noqa: PLR0915 + # LiteLLM management object fields + token: Optional[str], + user_id: Optional[str], + end_user_id: Optional[str], + team_id: Optional[str], + org_id: Optional[str], + # Completion object fields + kwargs: Optional[dict], + completion_response: Optional[Union[litellm.ModelResponse, Any, Exception]], + start_time: Optional[datetime], + end_time: Optional[datetime], + response_cost: Optional[float], + ): + from litellm.proxy.proxy_server import ( + disable_spend_logs, + litellm_proxy_budget_name, + prisma_client, + user_api_key_cache, + ) + + try: + verbose_proxy_logger.debug( + f"Enters prisma db call, response_cost: {response_cost}, token: {token}; user_id: {user_id}; team_id: {team_id}" + ) + if ProxyUpdateSpend.disable_spend_updates() is True: + return + if token is not None and isinstance(token, str) and token.startswith("sk-"): + hashed_token = hash_token(token=token) + else: + hashed_token = token + + ### UPDATE USER SPEND ### + async def _update_user_db(): + """ + - Update that user's row + - Update litellm-proxy-budget row (global proxy spend) + """ + ## if an end-user is passed in, do an upsert - we can't guarantee they already exist in db + existing_user_obj = await user_api_key_cache.async_get_cache( + key=user_id + ) + if existing_user_obj is not None and isinstance( + existing_user_obj, dict + ): + existing_user_obj = LiteLLM_UserTable(**existing_user_obj) + try: + if prisma_client is not None: # update + user_ids = [user_id] + if ( + litellm.max_budget > 0 + ): # track global proxy budget, if user set max budget + user_ids.append(litellm_proxy_budget_name) + ### KEY CHANGE ### + for _id in user_ids: + if _id is not None: + prisma_client.user_list_transactons[_id] = ( + response_cost + + prisma_client.user_list_transactons.get(_id, 0) + ) + if end_user_id is not None: + prisma_client.end_user_list_transactons[end_user_id] = ( + response_cost + + prisma_client.end_user_list_transactons.get( + end_user_id, 0 + ) + ) + except Exception as e: + verbose_proxy_logger.info( + "\033[91m" + + f"Update User DB call failed to execute {str(e)}\n{traceback.format_exc()}" + ) + + ### UPDATE KEY SPEND ### + async def _update_key_db(): + try: + verbose_proxy_logger.debug( + f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}." + ) + if hashed_token is None: + return + if prisma_client is not None: + prisma_client.key_list_transactons[hashed_token] = ( + response_cost + + prisma_client.key_list_transactons.get(hashed_token, 0) + ) + except Exception as e: + verbose_proxy_logger.exception( + f"Update Key DB Call failed to execute - {str(e)}" + ) + raise e + + ### UPDATE SPEND LOGS ### + async def _insert_spend_log_to_db(): + try: + if prisma_client: + payload = get_logging_payload( + kwargs=kwargs, + response_obj=completion_response, + start_time=start_time, + end_time=end_time, + ) + payload["spend"] = response_cost or 0.0 + DBSpendUpdateWriter._set_spend_logs_payload( + payload=payload, + spend_logs_url=os.getenv("SPEND_LOGS_URL"), + prisma_client=prisma_client, + ) + except Exception as e: + verbose_proxy_logger.debug( + f"Update Spend Logs DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + + ### UPDATE TEAM SPEND ### + async def _update_team_db(): + try: + verbose_proxy_logger.debug( + f"adding spend to team db. Response cost: {response_cost}. team_id: {team_id}." + ) + if team_id is None: + verbose_proxy_logger.debug( + "track_cost_callback: team_id is None. Not tracking spend for team" + ) + return + if prisma_client is not None: + prisma_client.team_list_transactons[team_id] = ( + response_cost + + prisma_client.team_list_transactons.get(team_id, 0) + ) + + try: + # Track spend of the team member within this team + # key is "team_id::::user_id::" + team_member_key = f"team_id::{team_id}::user_id::{user_id}" + prisma_client.team_member_list_transactons[ + team_member_key + ] = ( + response_cost + + prisma_client.team_member_list_transactons.get( + team_member_key, 0 + ) + ) + except Exception: + pass + except Exception as e: + verbose_proxy_logger.info( + f"Update Team DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + + ### UPDATE ORG SPEND ### + async def _update_org_db(): + try: + verbose_proxy_logger.debug( + "adding spend to org db. Response cost: {}. org_id: {}.".format( + response_cost, org_id + ) + ) + if org_id is None: + verbose_proxy_logger.debug( + "track_cost_callback: org_id is None. Not tracking spend for org" + ) + return + if prisma_client is not None: + prisma_client.org_list_transactons[org_id] = ( + response_cost + + prisma_client.org_list_transactons.get(org_id, 0) + ) + except Exception as e: + verbose_proxy_logger.info( + f"Update Org DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + + asyncio.create_task(_update_user_db()) + asyncio.create_task(_update_key_db()) + asyncio.create_task(_update_team_db()) + asyncio.create_task(_update_org_db()) + if disable_spend_logs is False: + await _insert_spend_log_to_db() + else: + verbose_proxy_logger.info( + "disable_spend_logs=True. Skipping writing spend logs to db. Other spend updates - Key/User/Team table will still occur." + ) + + verbose_proxy_logger.debug("Runs spend update on all tables") + except Exception: + verbose_proxy_logger.debug( + f"Error updating Prisma database: {traceback.format_exc()}" + ) + + @staticmethod + def _set_spend_logs_payload( + payload: Union[dict, SpendLogsPayload], + prisma_client: PrismaClient, + spend_logs_url: Optional[str] = None, + ) -> PrismaClient: + verbose_proxy_logger.info( + "Writing spend log to db - request_id: {}, spend: {}".format( + payload.get("request_id"), payload.get("spend") + ) + ) + if prisma_client is not None and spend_logs_url is not None: + if isinstance(payload["startTime"], datetime): + payload["startTime"] = payload["startTime"].isoformat() + if isinstance(payload["endTime"], datetime): + payload["endTime"] = payload["endTime"].isoformat() + prisma_client.spend_log_transactions.append(payload) + elif prisma_client is not None: + prisma_client.spend_log_transactions.append(payload) + + prisma_client.add_spend_log_transaction_to_daily_user_transaction( + payload.copy() + ) + return prisma_client diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index e8a947329d..f205b0146f 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -13,6 +13,7 @@ from litellm.litellm_core_utils.core_helpers import ( from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.auth_checks import log_db_metrics +from litellm.proxy.db.db_spend_update_writer import DBSpendUpdateWriter from litellm.proxy.utils import ProxyUpdateSpend from litellm.types.utils import ( StandardLoggingPayload, @@ -33,8 +34,6 @@ class _ProxyDBLogger(CustomLogger): original_exception: Exception, user_api_key_dict: UserAPIKeyAuth, ): - from litellm.proxy.proxy_server import update_database - if _ProxyDBLogger._should_track_errors_in_db() is False: return @@ -67,7 +66,7 @@ class _ProxyDBLogger(CustomLogger): request_data.get("proxy_server_request") or {} ) request_data["litellm_params"]["metadata"] = existing_metadata - await update_database( + await DBSpendUpdateWriter.update_database( token=user_api_key_dict.api_key, response_cost=0.0, user_id=user_api_key_dict.user_id, @@ -94,7 +93,6 @@ class _ProxyDBLogger(CustomLogger): prisma_client, proxy_logging_obj, update_cache, - update_database, ) verbose_proxy_logger.debug("INSIDE _PROXY_track_cost_callback") @@ -138,7 +136,7 @@ class _ProxyDBLogger(CustomLogger): end_user_id=end_user_id, ): ## UPDATE DATABASE - await update_database( + await DBSpendUpdateWriter.update_database( token=user_api_key, response_cost=response_cost, user_id=user_id, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 5d7e92fd73..6a2da7d83b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -897,211 +897,6 @@ def cost_tracking(): litellm.logging_callback_manager.add_litellm_callback(_ProxyDBLogger()) -def _set_spend_logs_payload( - payload: Union[dict, SpendLogsPayload], - prisma_client: PrismaClient, - spend_logs_url: Optional[str] = None, -): - verbose_proxy_logger.info( - "Writing spend log to db - request_id: {}, spend: {}".format( - payload.get("request_id"), payload.get("spend") - ) - ) - if prisma_client is not None and spend_logs_url is not None: - if isinstance(payload["startTime"], datetime): - payload["startTime"] = payload["startTime"].isoformat() - if isinstance(payload["endTime"], datetime): - payload["endTime"] = payload["endTime"].isoformat() - prisma_client.spend_log_transactions.append(payload) - elif prisma_client is not None: - prisma_client.spend_log_transactions.append(payload) - - prisma_client.add_spend_log_transaction_to_daily_user_transaction(payload.copy()) - return prisma_client - - -async def update_database( # noqa: PLR0915 - token, - response_cost, - user_id=None, - end_user_id=None, - team_id=None, - kwargs=None, - completion_response=None, - start_time=None, - end_time=None, - org_id=None, -): - try: - global prisma_client - verbose_proxy_logger.debug( - f"Enters prisma db call, response_cost: {response_cost}, token: {token}; user_id: {user_id}; team_id: {team_id}" - ) - if ProxyUpdateSpend.disable_spend_updates() is True: - return - if token is not None and isinstance(token, str) and token.startswith("sk-"): - hashed_token = hash_token(token=token) - else: - hashed_token = token - - ### UPDATE USER SPEND ### - async def _update_user_db(): - """ - - Update that user's row - - Update litellm-proxy-budget row (global proxy spend) - """ - ## if an end-user is passed in, do an upsert - we can't guarantee they already exist in db - existing_user_obj = await user_api_key_cache.async_get_cache(key=user_id) - if existing_user_obj is not None and isinstance(existing_user_obj, dict): - existing_user_obj = LiteLLM_UserTable(**existing_user_obj) - try: - if prisma_client is not None: # update - user_ids = [user_id] - if ( - litellm.max_budget > 0 - ): # track global proxy budget, if user set max budget - user_ids.append(litellm_proxy_budget_name) - ### KEY CHANGE ### - for _id in user_ids: - if _id is not None: - prisma_client.user_list_transactons[_id] = ( - response_cost - + prisma_client.user_list_transactons.get(_id, 0) - ) - if end_user_id is not None: - prisma_client.end_user_list_transactons[end_user_id] = ( - response_cost - + prisma_client.end_user_list_transactons.get( - end_user_id, 0 - ) - ) - except Exception as e: - verbose_proxy_logger.info( - "\033[91m" - + f"Update User DB call failed to execute {str(e)}\n{traceback.format_exc()}" - ) - - ### UPDATE KEY SPEND ### - async def _update_key_db(): - try: - verbose_proxy_logger.debug( - f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}." - ) - if hashed_token is None: - return - if prisma_client is not None: - prisma_client.key_list_transactons[hashed_token] = ( - response_cost - + prisma_client.key_list_transactons.get(hashed_token, 0) - ) - except Exception as e: - verbose_proxy_logger.exception( - f"Update Key DB Call failed to execute - {str(e)}" - ) - raise e - - ### UPDATE SPEND LOGS ### - async def _insert_spend_log_to_db(): - try: - global prisma_client - if prisma_client is not None: - # Helper to generate payload to log - payload = get_logging_payload( - kwargs=kwargs, - response_obj=completion_response, - start_time=start_time, - end_time=end_time, - ) - payload["spend"] = response_cost - prisma_client = _set_spend_logs_payload( - payload=payload, - spend_logs_url=os.getenv("SPEND_LOGS_URL"), - prisma_client=prisma_client, - ) - except Exception as e: - verbose_proxy_logger.debug( - f"Update Spend Logs DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - ### UPDATE TEAM SPEND ### - async def _update_team_db(): - try: - verbose_proxy_logger.debug( - f"adding spend to team db. Response cost: {response_cost}. team_id: {team_id}." - ) - if team_id is None: - verbose_proxy_logger.debug( - "track_cost_callback: team_id is None. Not tracking spend for team" - ) - return - if prisma_client is not None: - prisma_client.team_list_transactons[team_id] = ( - response_cost - + prisma_client.team_list_transactons.get(team_id, 0) - ) - - try: - # Track spend of the team member within this team - # key is "team_id::::user_id::" - team_member_key = f"team_id::{team_id}::user_id::{user_id}" - prisma_client.team_member_list_transactons[team_member_key] = ( - response_cost - + prisma_client.team_member_list_transactons.get( - team_member_key, 0 - ) - ) - except Exception: - pass - except Exception as e: - verbose_proxy_logger.info( - f"Update Team DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - ### UPDATE ORG SPEND ### - async def _update_org_db(): - try: - verbose_proxy_logger.debug( - "adding spend to org db. Response cost: {}. org_id: {}.".format( - response_cost, org_id - ) - ) - if org_id is None: - verbose_proxy_logger.debug( - "track_cost_callback: org_id is None. Not tracking spend for org" - ) - return - if prisma_client is not None: - prisma_client.org_list_transactons[org_id] = ( - response_cost - + prisma_client.org_list_transactons.get(org_id, 0) - ) - except Exception as e: - verbose_proxy_logger.info( - f"Update Org DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - asyncio.create_task(_update_user_db()) - asyncio.create_task(_update_key_db()) - asyncio.create_task(_update_team_db()) - asyncio.create_task(_update_org_db()) - # asyncio.create_task(_insert_spend_log_to_db()) - if disable_spend_logs is False: - await _insert_spend_log_to_db() - else: - verbose_proxy_logger.info( - "disable_spend_logs=True. Skipping writing spend logs to db. Other spend updates - Key/User/Team table will still occur." - ) - - verbose_proxy_logger.debug("Runs spend update on all tables") - except Exception: - verbose_proxy_logger.debug( - f"Error updating Prisma database: {traceback.format_exc()}" - ) - - async def update_cache( # noqa: PLR0915 token: Optional[str], user_id: Optional[str], @@ -3294,14 +3089,14 @@ class ProxyStartupEvent: prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj ) - ### GET STORED CREDENTIALS ### - scheduler.add_job( - proxy_config.get_credentials, - "interval", - seconds=10, - args=[prisma_client], - ) - await proxy_config.get_credentials(prisma_client=prisma_client) + ### GET STORED CREDENTIALS ### + scheduler.add_job( + proxy_config.get_credentials, + "interval", + seconds=1, + args=[prisma_client], + ) + await proxy_config.get_credentials(prisma_client=prisma_client) if ( proxy_logging_obj is not None and proxy_logging_obj.slack_alerting_instance.alerting is not None diff --git a/tests/litellm/proxy/hooks/test_proxy_track_cost_callback.py b/tests/litellm/proxy/hooks/test_proxy_track_cost_callback.py index 1e3b22ae2d..8850436329 100644 --- a/tests/litellm/proxy/hooks/test_proxy_track_cost_callback.py +++ b/tests/litellm/proxy/hooks/test_proxy_track_cost_callback.py @@ -47,7 +47,8 @@ async def test_async_post_call_failure_hook(): # Mock update_database function with patch( - "litellm.proxy.proxy_server.update_database", new_callable=AsyncMock + "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", + new_callable=AsyncMock, ) as mock_update_database: # Call the method await logger.async_post_call_failure_hook( diff --git a/tests/proxy_unit_tests/test_unit_test_proxy_hooks.py b/tests/proxy_unit_tests/test_unit_test_proxy_hooks.py index 535f5bf019..129be6d754 100644 --- a/tests/proxy_unit_tests/test_unit_test_proxy_hooks.py +++ b/tests/proxy_unit_tests/test_unit_test_proxy_hooks.py @@ -5,6 +5,7 @@ from unittest.mock import Mock, patch, AsyncMock import pytest from fastapi import Request from litellm.proxy.utils import _get_redoc_url, _get_docs_url +from datetime import datetime sys.path.insert(0, os.path.abspath("../..")) import litellm @@ -22,16 +23,20 @@ async def test_disable_spend_logs(): with patch("litellm.proxy.proxy_server.disable_spend_logs", True), patch( "litellm.proxy.proxy_server.prisma_client", mock_prisma_client ): - from litellm.proxy.proxy_server import update_database + from litellm.proxy.db.db_spend_update_writer import DBSpendUpdateWriter # Call update_database with disable_spend_logs=True - await update_database( + await DBSpendUpdateWriter.update_database( token="fake-token", response_cost=0.1, user_id="user123", completion_response=None, - start_time="2024-01-01", - end_time="2024-01-01", + start_time=datetime.now(), + end_time=datetime.now(), + end_user_id="end_user_id", + team_id="team_id", + org_id="org_id", + kwargs={}, ) # Verify no spend logs were added assert len(mock_prisma_client.spend_log_transactions) == 0 From 072be44a54a5cd37162f0cd2d0fb166d1d670cad Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 16:34:06 -0700 Subject: [PATCH 115/244] fix get_credentials job --- litellm/proxy/proxy_server.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 6a2da7d83b..d7e62f98d0 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3089,14 +3089,14 @@ class ProxyStartupEvent: prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj ) - ### GET STORED CREDENTIALS ### - scheduler.add_job( - proxy_config.get_credentials, - "interval", - seconds=1, - args=[prisma_client], - ) - await proxy_config.get_credentials(prisma_client=prisma_client) + ### GET STORED CREDENTIALS ### + scheduler.add_job( + proxy_config.get_credentials, + "interval", + seconds=10, + args=[prisma_client], + ) + await proxy_config.get_credentials(prisma_client=prisma_client) if ( proxy_logging_obj is not None and proxy_logging_obj.slack_alerting_instance.alerting is not None From a0fd508de405040e22b00cbb043e8bde0afd5e75 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 16:43:18 -0700 Subject: [PATCH 116/244] DBSpendUpdateWriter --- litellm/proxy/db/db_spend_update_writer.py | 3 +-- .../spend_tracking/test_spend_management_endpoints.py | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index d5f2ef5228..af1d3294c5 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -7,9 +7,8 @@ from typing import Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import LiteLLM_UserTable, SpendLogsPayload -from litellm.proxy.proxy_server import hash_token from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload -from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend +from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend, hash_token class DBSpendUpdateWriter: diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index a5ee9ddf70..e08bce432d 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -416,7 +416,8 @@ class TestSpendLogsPayload: # litellm._turn_on_debug() with patch.object( - litellm.proxy.proxy_server, "_set_spend_logs_payload" + litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter, + "_set_spend_logs_payload", ) as mock_client, patch.object(litellm.proxy.proxy_server, "prisma_client"): response = await litellm.acompletion( model="gpt-4o", @@ -509,7 +510,8 @@ class TestSpendLogsPayload: client = AsyncHTTPHandler() with patch.object( - litellm.proxy.proxy_server, "_set_spend_logs_payload" + litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter, + "_set_spend_logs_payload", ) as mock_client, patch.object( litellm.proxy.proxy_server, "prisma_client" ), patch.object( @@ -604,7 +606,8 @@ class TestSpendLogsPayload: ) with patch.object( - litellm.proxy.proxy_server, "_set_spend_logs_payload" + litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter, + "_set_spend_logs_payload", ) as mock_client, patch.object( litellm.proxy.proxy_server, "prisma_client" ), patch.object( From 7995fd7c98bf6fe3a058edfb60c64b05fc330622 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 16:44:46 -0700 Subject: [PATCH 117/244] fix DBSpendUpdateWriter --- litellm/proxy/db/db_spend_update_writer.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index af1d3294c5..ecafdcc3df 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -1,3 +1,10 @@ +""" +Module responsible for + +1. Writing spend increments to either in memory list of transactions or to redis +2. Reading increments from redis or in memory list of transactions and committing them to db +""" + import asyncio import os import traceback @@ -12,6 +19,12 @@ from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend, hash_token class DBSpendUpdateWriter: + """ + Module responsible for + + 1. Writing spend increments to either in memory list of transactions or to redis + 2. Reading increments from redis or in memory list of transactions and committing them to db + """ @staticmethod async def update_database( # noqa: PLR0915 From 403f2ef68dae3735fc61b5ada98b336ee5ab044c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 16:57:12 -0700 Subject: [PATCH 118/244] use simple static methods for updating spend --- litellm/proxy/db/db_spend_update_writer.py | 349 ++++++++++++--------- 1 file changed, 201 insertions(+), 148 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index ecafdcc3df..37d709c0f1 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -13,6 +13,7 @@ from typing import Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger +from litellm.caching import DualCache from litellm.proxy._types import LiteLLM_UserTable, SpendLogsPayload from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend, hash_token @@ -27,7 +28,7 @@ class DBSpendUpdateWriter: """ @staticmethod - async def update_database( # noqa: PLR0915 + async def update_database( # LiteLLM management object fields token: Optional[str], user_id: Optional[str], @@ -59,155 +60,47 @@ class DBSpendUpdateWriter: else: hashed_token = token - ### UPDATE USER SPEND ### - async def _update_user_db(): - """ - - Update that user's row - - Update litellm-proxy-budget row (global proxy spend) - """ - ## if an end-user is passed in, do an upsert - we can't guarantee they already exist in db - existing_user_obj = await user_api_key_cache.async_get_cache( - key=user_id + asyncio.create_task( + DBSpendUpdateWriter._update_user_db( + response_cost=response_cost, + user_id=user_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + litellm_proxy_budget_name=litellm_proxy_budget_name, + end_user_id=end_user_id, ) - if existing_user_obj is not None and isinstance( - existing_user_obj, dict - ): - existing_user_obj = LiteLLM_UserTable(**existing_user_obj) - try: - if prisma_client is not None: # update - user_ids = [user_id] - if ( - litellm.max_budget > 0 - ): # track global proxy budget, if user set max budget - user_ids.append(litellm_proxy_budget_name) - ### KEY CHANGE ### - for _id in user_ids: - if _id is not None: - prisma_client.user_list_transactons[_id] = ( - response_cost - + prisma_client.user_list_transactons.get(_id, 0) - ) - if end_user_id is not None: - prisma_client.end_user_list_transactons[end_user_id] = ( - response_cost - + prisma_client.end_user_list_transactons.get( - end_user_id, 0 - ) - ) - except Exception as e: - verbose_proxy_logger.info( - "\033[91m" - + f"Update User DB call failed to execute {str(e)}\n{traceback.format_exc()}" - ) - - ### UPDATE KEY SPEND ### - async def _update_key_db(): - try: - verbose_proxy_logger.debug( - f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}." - ) - if hashed_token is None: - return - if prisma_client is not None: - prisma_client.key_list_transactons[hashed_token] = ( - response_cost - + prisma_client.key_list_transactons.get(hashed_token, 0) - ) - except Exception as e: - verbose_proxy_logger.exception( - f"Update Key DB Call failed to execute - {str(e)}" - ) - raise e - - ### UPDATE SPEND LOGS ### - async def _insert_spend_log_to_db(): - try: - if prisma_client: - payload = get_logging_payload( - kwargs=kwargs, - response_obj=completion_response, - start_time=start_time, - end_time=end_time, - ) - payload["spend"] = response_cost or 0.0 - DBSpendUpdateWriter._set_spend_logs_payload( - payload=payload, - spend_logs_url=os.getenv("SPEND_LOGS_URL"), - prisma_client=prisma_client, - ) - except Exception as e: - verbose_proxy_logger.debug( - f"Update Spend Logs DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - ### UPDATE TEAM SPEND ### - async def _update_team_db(): - try: - verbose_proxy_logger.debug( - f"adding spend to team db. Response cost: {response_cost}. team_id: {team_id}." - ) - if team_id is None: - verbose_proxy_logger.debug( - "track_cost_callback: team_id is None. Not tracking spend for team" - ) - return - if prisma_client is not None: - prisma_client.team_list_transactons[team_id] = ( - response_cost - + prisma_client.team_list_transactons.get(team_id, 0) - ) - - try: - # Track spend of the team member within this team - # key is "team_id::::user_id::" - team_member_key = f"team_id::{team_id}::user_id::{user_id}" - prisma_client.team_member_list_transactons[ - team_member_key - ] = ( - response_cost - + prisma_client.team_member_list_transactons.get( - team_member_key, 0 - ) - ) - except Exception: - pass - except Exception as e: - verbose_proxy_logger.info( - f"Update Team DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - ### UPDATE ORG SPEND ### - async def _update_org_db(): - try: - verbose_proxy_logger.debug( - "adding spend to org db. Response cost: {}. org_id: {}.".format( - response_cost, org_id - ) - ) - if org_id is None: - verbose_proxy_logger.debug( - "track_cost_callback: org_id is None. Not tracking spend for org" - ) - return - if prisma_client is not None: - prisma_client.org_list_transactons[org_id] = ( - response_cost - + prisma_client.org_list_transactons.get(org_id, 0) - ) - except Exception as e: - verbose_proxy_logger.info( - f"Update Org DB failed to execute - {str(e)}\n{traceback.format_exc()}" - ) - raise e - - asyncio.create_task(_update_user_db()) - asyncio.create_task(_update_key_db()) - asyncio.create_task(_update_team_db()) - asyncio.create_task(_update_org_db()) + ) + asyncio.create_task( + DBSpendUpdateWriter._update_key_db( + response_cost=response_cost, + hashed_token=hashed_token, + prisma_client=prisma_client, + ) + ) + asyncio.create_task( + DBSpendUpdateWriter._update_team_db( + response_cost=response_cost, + team_id=team_id, + user_id=user_id, + prisma_client=prisma_client, + ) + ) + asyncio.create_task( + DBSpendUpdateWriter._update_org_db( + response_cost=response_cost, + org_id=org_id, + prisma_client=prisma_client, + ) + ) if disable_spend_logs is False: - await _insert_spend_log_to_db() + await DBSpendUpdateWriter._insert_spend_log_to_db( + kwargs=kwargs, + completion_response=completion_response, + start_time=start_time, + end_time=end_time, + response_cost=response_cost, + prisma_client=prisma_client, + ) else: verbose_proxy_logger.info( "disable_spend_logs=True. Skipping writing spend logs to db. Other spend updates - Key/User/Team table will still occur." @@ -219,6 +112,166 @@ class DBSpendUpdateWriter: f"Error updating Prisma database: {traceback.format_exc()}" ) + @staticmethod + async def _update_key_db( + response_cost: Optional[float], + hashed_token: Optional[str], + prisma_client: Optional[PrismaClient], + ): + try: + verbose_proxy_logger.debug( + f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}." + ) + if hashed_token is None: + return + if prisma_client is not None: + prisma_client.key_list_transactons[hashed_token] = ( + response_cost + + prisma_client.key_list_transactons.get(hashed_token, 0) + ) + except Exception as e: + verbose_proxy_logger.exception( + f"Update Key DB Call failed to execute - {str(e)}" + ) + raise e + + @staticmethod + async def _update_user_db( + response_cost: Optional[float], + user_id: Optional[str], + prisma_client: Optional[PrismaClient], + user_api_key_cache: DualCache, + litellm_proxy_budget_name: Optional[str], + end_user_id: Optional[str] = None, + ): + """ + - Update that user's row + - Update litellm-proxy-budget row (global proxy spend) + """ + ## if an end-user is passed in, do an upsert - we can't guarantee they already exist in db + existing_user_obj = await user_api_key_cache.async_get_cache(key=user_id) + if existing_user_obj is not None and isinstance(existing_user_obj, dict): + existing_user_obj = LiteLLM_UserTable(**existing_user_obj) + try: + if prisma_client is not None: # update + user_ids = [user_id] + if ( + litellm.max_budget > 0 + ): # track global proxy budget, if user set max budget + user_ids.append(litellm_proxy_budget_name) + ### KEY CHANGE ### + for _id in user_ids: + if _id is not None: + prisma_client.user_list_transactons[_id] = ( + response_cost + + prisma_client.user_list_transactons.get(_id, 0) + ) + if end_user_id is not None: + prisma_client.end_user_list_transactons[end_user_id] = ( + response_cost + + prisma_client.end_user_list_transactons.get(end_user_id, 0) + ) + except Exception as e: + verbose_proxy_logger.info( + "\033[91m" + + f"Update User DB call failed to execute {str(e)}\n{traceback.format_exc()}" + ) + + @staticmethod + async def _update_team_db( + response_cost: Optional[float], + team_id: Optional[str], + user_id: Optional[str], + prisma_client: Optional[PrismaClient], + ): + try: + verbose_proxy_logger.debug( + f"adding spend to team db. Response cost: {response_cost}. team_id: {team_id}." + ) + if team_id is None: + verbose_proxy_logger.debug( + "track_cost_callback: team_id is None. Not tracking spend for team" + ) + return + if prisma_client is not None: + prisma_client.team_list_transactons[team_id] = ( + response_cost + prisma_client.team_list_transactons.get(team_id, 0) + ) + + try: + # Track spend of the team member within this team + # key is "team_id::::user_id::" + team_member_key = f"team_id::{team_id}::user_id::{user_id}" + prisma_client.team_member_list_transactons[team_member_key] = ( + response_cost + + prisma_client.team_member_list_transactons.get( + team_member_key, 0 + ) + ) + except Exception: + pass + except Exception as e: + verbose_proxy_logger.info( + f"Update Team DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + + @staticmethod + async def _update_org_db( + response_cost: Optional[float], + org_id: Optional[str], + prisma_client: Optional[PrismaClient], + ): + try: + verbose_proxy_logger.debug( + "adding spend to org db. Response cost: {}. org_id: {}.".format( + response_cost, org_id + ) + ) + if org_id is None: + verbose_proxy_logger.debug( + "track_cost_callback: org_id is None. Not tracking spend for org" + ) + return + if prisma_client is not None: + prisma_client.org_list_transactons[org_id] = ( + response_cost + prisma_client.org_list_transactons.get(org_id, 0) + ) + except Exception as e: + verbose_proxy_logger.info( + f"Update Org DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + + @staticmethod + async def _insert_spend_log_to_db( + kwargs: Optional[dict], + completion_response: Optional[Union[litellm.ModelResponse, Any, Exception]], + start_time: Optional[datetime], + end_time: Optional[datetime], + response_cost: Optional[float], + prisma_client: Optional[PrismaClient], + ): + try: + if prisma_client: + payload = get_logging_payload( + kwargs=kwargs, + response_obj=completion_response, + start_time=start_time, + end_time=end_time, + ) + payload["spend"] = response_cost or 0.0 + DBSpendUpdateWriter._set_spend_logs_payload( + payload=payload, + spend_logs_url=os.getenv("SPEND_LOGS_URL"), + prisma_client=prisma_client, + ) + except Exception as e: + verbose_proxy_logger.debug( + f"Update Spend Logs DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + @staticmethod def _set_spend_logs_payload( payload: Union[dict, SpendLogsPayload], From b721b2b4acfb84e4b5b73e488a33562b12759c75 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 17:04:50 -0700 Subject: [PATCH 119/244] use DBSpendUpdateWriter common function for --- litellm/proxy/_types.py | 15 +++ litellm/proxy/db/db_spend_update_writer.py | 142 ++++++++++++++------- 2 files changed, 111 insertions(+), 46 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 6e242ddacb..17b4acd138 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -144,6 +144,21 @@ class LitellmTableNames(str, enum.Enum): PROXY_MODEL_TABLE_NAME = "LiteLLM_ProxyModelTable" +class Litellm_EntityType(enum.Enum): + """ + Enum for types of entities on litellm + + This enum allows specifying the type of entity that is being tracked in the database. + """ + + KEY = "key" + USER = "user" + END_USER = "end_user" + TEAM = "team" + TEAM_MEMBER = "team_member" + ORGANIZATION = "organization" + + def hash_token(token: str): import hashlib diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 37d709c0f1..34e8eae173 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -14,7 +14,7 @@ from typing import Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger from litellm.caching import DualCache -from litellm.proxy._types import LiteLLM_UserTable, SpendLogsPayload +from litellm.proxy._types import Litellm_EntityType, LiteLLM_UserTable, SpendLogsPayload from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend, hash_token @@ -112,6 +112,52 @@ class DBSpendUpdateWriter: f"Error updating Prisma database: {traceback.format_exc()}" ) + @staticmethod + async def _update_transaction_list( + response_cost: Optional[float], + entity_id: Optional[str], + transaction_list: dict, + entity_type: Litellm_EntityType, + debug_msg: Optional[str] = None, + ) -> bool: + """ + Common helper method to update a transaction list for an entity + + Args: + response_cost: The cost to add + entity_id: The ID of the entity to update + transaction_list: The transaction list dictionary to update + entity_type: The type of entity (from EntityType enum) + debug_msg: Optional custom debug message + + Returns: + bool: True if update happened, False otherwise + """ + try: + if debug_msg: + verbose_proxy_logger.debug(debug_msg) + else: + verbose_proxy_logger.debug( + f"adding spend to {entity_type.value} db. Response cost: {response_cost}. {entity_type.value}_id: {entity_id}." + ) + + if entity_id is None: + verbose_proxy_logger.debug( + f"track_cost_callback: {entity_type.value}_id is None. Not tracking spend for {entity_type.value}" + ) + return False + + transaction_list[entity_id] = response_cost + transaction_list.get( + entity_id, 0 + ) + return True + + except Exception as e: + verbose_proxy_logger.info( + f"Update {entity_type.value.capitalize()} DB failed to execute - {str(e)}\n{traceback.format_exc()}" + ) + raise e + @staticmethod async def _update_key_db( response_cost: Optional[float], @@ -119,16 +165,16 @@ class DBSpendUpdateWriter: prisma_client: Optional[PrismaClient], ): try: - verbose_proxy_logger.debug( - f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}." - ) - if hashed_token is None: + if hashed_token is None or prisma_client is None: return - if prisma_client is not None: - prisma_client.key_list_transactons[hashed_token] = ( - response_cost - + prisma_client.key_list_transactons.get(hashed_token, 0) - ) + + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=hashed_token, + transaction_list=prisma_client.key_list_transactons, + entity_type=Litellm_EntityType.KEY, + debug_msg=f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}.", + ) except Exception as e: verbose_proxy_logger.exception( f"Update Key DB Call failed to execute - {str(e)}" @@ -159,17 +205,22 @@ class DBSpendUpdateWriter: litellm.max_budget > 0 ): # track global proxy budget, if user set max budget user_ids.append(litellm_proxy_budget_name) - ### KEY CHANGE ### + for _id in user_ids: if _id is not None: - prisma_client.user_list_transactons[_id] = ( - response_cost - + prisma_client.user_list_transactons.get(_id, 0) + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=_id, + transaction_list=prisma_client.user_list_transactons, + entity_type=Litellm_EntityType.USER, ) + if end_user_id is not None: - prisma_client.end_user_list_transactons[end_user_id] = ( - response_cost - + prisma_client.end_user_list_transactons.get(end_user_id, 0) + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=end_user_id, + transaction_list=prisma_client.end_user_list_transactons, + entity_type=Litellm_EntityType.END_USER, ) except Exception as e: verbose_proxy_logger.info( @@ -185,31 +236,32 @@ class DBSpendUpdateWriter: prisma_client: Optional[PrismaClient], ): try: - verbose_proxy_logger.debug( - f"adding spend to team db. Response cost: {response_cost}. team_id: {team_id}." - ) - if team_id is None: + if team_id is None or prisma_client is None: verbose_proxy_logger.debug( - "track_cost_callback: team_id is None. Not tracking spend for team" + "track_cost_callback: team_id is None or prisma_client is None. Not tracking spend for team" ) return - if prisma_client is not None: - prisma_client.team_list_transactons[team_id] = ( - response_cost + prisma_client.team_list_transactons.get(team_id, 0) - ) - try: - # Track spend of the team member within this team + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=team_id, + transaction_list=prisma_client.team_list_transactons, + entity_type=Litellm_EntityType.TEAM, + ) + + try: + # Track spend of the team member within this team + if user_id is not None: # key is "team_id::::user_id::" team_member_key = f"team_id::{team_id}::user_id::{user_id}" - prisma_client.team_member_list_transactons[team_member_key] = ( - response_cost - + prisma_client.team_member_list_transactons.get( - team_member_key, 0 - ) + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=team_member_key, + transaction_list=prisma_client.team_member_list_transactons, + entity_type=Litellm_EntityType.TEAM_MEMBER, ) - except Exception: - pass + except Exception: + pass except Exception as e: verbose_proxy_logger.info( f"Update Team DB failed to execute - {str(e)}\n{traceback.format_exc()}" @@ -223,20 +275,18 @@ class DBSpendUpdateWriter: prisma_client: Optional[PrismaClient], ): try: - verbose_proxy_logger.debug( - "adding spend to org db. Response cost: {}. org_id: {}.".format( - response_cost, org_id - ) - ) - if org_id is None: + if org_id is None or prisma_client is None: verbose_proxy_logger.debug( - "track_cost_callback: org_id is None. Not tracking spend for org" + "track_cost_callback: org_id is None or prisma_client is None. Not tracking spend for org" ) return - if prisma_client is not None: - prisma_client.org_list_transactons[org_id] = ( - response_cost + prisma_client.org_list_transactons.get(org_id, 0) - ) + + await DBSpendUpdateWriter._update_transaction_list( + response_cost=response_cost, + entity_id=org_id, + transaction_list=prisma_client.org_list_transactons, + entity_type=Litellm_EntityType.ORGANIZATION, + ) except Exception as e: verbose_proxy_logger.info( f"Update Org DB failed to execute - {str(e)}\n{traceback.format_exc()}" From fb83567a037837aa380bdc98ed3627c831868191 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 17:15:25 -0700 Subject: [PATCH 120/244] Litellm new UI build (#9601) * build: new ui build * build: new ui build * fix(proxy_server.py): only show user models their key can access on `/models` * fix(model_management_endpoints.py): ensure team admin can add models * test: update unit testing to reflect changes * fix(model_dashboard.tsx): fix sizing on models page * build: fix ui --- .../static/chunks/250-601568e45a5ffece.js | 1 + .../static/chunks/250-a75ee9d79f1140b0.js | 1 - .../static/chunks/394-48a36e9c9b2cb488.js | 4 +- .../chunks/app/layout-429ad74a94df7643.js | 1 + .../chunks/app/layout-af8319e6c59a08da.js | 1 - .../app/model_hub/page-cde2fb783e81a6c1.js | 2 +- .../app/onboarding/page-1ffe69692e4b2037.js | 1 - .../app/onboarding/page-5110f2c6a3c9a2f4.js | 1 + .../chunks/app/page-75d771fb848b47a8.js | 1 - .../chunks/app/page-e21d4be3d6c3c16e.js | 1 + ...80647d.js => main-app-4f7318ae681a6d94.js} | 2 +- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../out/assets/logos/anthropic.svg | 5 + .../out/assets/logos/assemblyai_small.png | Bin 0 -> 414 bytes .../_experimental/out/assets/logos/aws.svg | 34 +++++++ .../out/assets/logos/bedrock.svg | 1 + .../out/assets/logos/cerebras.svg | 89 ++++++++++++++++++ .../_experimental/out/assets/logos/cohere.svg | 1 + .../out/assets/logos/databricks.svg | 1 + .../out/assets/logos/deepseek.svg | 25 +++++ .../out/assets/logos/fireworks.svg | 1 + .../_experimental/out/assets/logos/google.svg | 2 + .../_experimental/out/assets/logos/groq.svg | 3 + .../out/assets/logos/microsoft_azure.svg | 72 ++++++++++++++ .../out/assets/logos/mistral.svg | 1 + .../_experimental/out/assets/logos/ollama.svg | 7 ++ .../out/assets/logos/openai_small.svg | 5 + .../out/assets/logos/openrouter.svg | 39 ++++++++ .../out/assets/logos/perplexity-ai.svg | 16 ++++ .../out/assets/logos/sambanova.svg | 1 + .../out/assets/logos/togetherai.svg | 14 +++ .../_experimental/out/assets/logos/xai.svg | 28 ++++++ litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 4 +- litellm/proxy/_experimental/out/model_hub.txt | 4 +- .../proxy/_experimental/out/onboarding.txt | 4 +- litellm/proxy/_new_secret_config.yaml | 11 +-- .../model_management_endpoints.py | 15 +++ litellm/proxy/proxy_server.py | 12 ++- .../test_model_management_endpoints.py | 13 ++- ui/litellm-dashboard/out/404.html | 2 +- .../static/chunks/250-601568e45a5ffece.js | 1 + .../static/chunks/250-a75ee9d79f1140b0.js | 1 - .../static/chunks/394-48a36e9c9b2cb488.js | 4 +- .../chunks/app/layout-429ad74a94df7643.js | 1 + .../chunks/app/layout-af8319e6c59a08da.js | 1 - .../app/model_hub/page-cde2fb783e81a6c1.js | 2 +- .../app/onboarding/page-1ffe69692e4b2037.js | 1 - .../app/onboarding/page-5110f2c6a3c9a2f4.js | 1 + .../chunks/app/page-75d771fb848b47a8.js | 1 - .../chunks/app/page-e21d4be3d6c3c16e.js | 1 + ...80647d.js => main-app-4f7318ae681a6d94.js} | 2 +- .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../out/assets/logos/anthropic.svg | 5 + .../out/assets/logos/assemblyai_small.png | Bin 0 -> 414 bytes ui/litellm-dashboard/out/assets/logos/aws.svg | 34 +++++++ .../out/assets/logos/bedrock.svg | 1 + .../out/assets/logos/cerebras.svg | 89 ++++++++++++++++++ .../out/assets/logos/cohere.svg | 1 + .../out/assets/logos/databricks.svg | 1 + .../out/assets/logos/deepseek.svg | 25 +++++ .../out/assets/logos/fireworks.svg | 1 + .../out/assets/logos/google.svg | 2 + .../out/assets/logos/groq.svg | 3 + .../out/assets/logos/microsoft_azure.svg | 72 ++++++++++++++ .../out/assets/logos/mistral.svg | 1 + .../out/assets/logos/ollama.svg | 7 ++ .../out/assets/logos/openai_small.svg | 5 + .../out/assets/logos/openrouter.svg | 39 ++++++++ .../out/assets/logos/perplexity-ai.svg | 16 ++++ .../out/assets/logos/sambanova.svg | 1 + .../out/assets/logos/togetherai.svg | 14 +++ ui/litellm-dashboard/out/assets/logos/xai.svg | 28 ++++++ ui/litellm-dashboard/out/index.html | 2 +- ui/litellm-dashboard/out/index.txt | 4 +- ui/litellm-dashboard/out/model_hub.html | 2 +- ui/litellm-dashboard/out/model_hub.txt | 4 +- ui/litellm-dashboard/out/onboarding.html | 2 +- ui/litellm-dashboard/out/onboarding.txt | 4 +- .../src/components/model_dashboard.tsx | 14 ++- 82 files changed, 770 insertions(+), 51 deletions(-) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/250-601568e45a5ffece.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/250-a75ee9d79f1140b0.js rename ui/litellm-dashboard/out/_next/static/chunks/394-0222ddf4d701e0b4.js => litellm/proxy/_experimental/out/_next/static/chunks/394-48a36e9c9b2cb488.js (66%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-429ad74a94df7643.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/layout-af8319e6c59a08da.js rename ui/litellm-dashboard/out/_next/static/chunks/app/model_hub/page-068a441595bd0fc3.js => litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-cde2fb783e81a6c1.js (60%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-1ffe69692e4b2037.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/page-5110f2c6a3c9a2f4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-75d771fb848b47a8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-e21d4be3d6c3c16e.js rename litellm/proxy/_experimental/out/_next/static/chunks/{main-app-475d6efe4080647d.js => main-app-4f7318ae681a6d94.js} (54%) rename litellm/proxy/_experimental/out/_next/static/{9yIyUkG6nV2cO0gn7kJ-Q => soi--ciJeUE6G2Fk4NMBG}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{9yIyUkG6nV2cO0gn7kJ-Q => soi--ciJeUE6G2Fk4NMBG}/_ssgManifest.js (100%) create mode 100644 litellm/proxy/_experimental/out/assets/logos/anthropic.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/assemblyai_small.png create mode 100644 litellm/proxy/_experimental/out/assets/logos/aws.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/bedrock.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/cerebras.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/cohere.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/databricks.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/deepseek.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/fireworks.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/google.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/groq.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/microsoft_azure.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/mistral.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/ollama.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/openai_small.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/openrouter.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/perplexity-ai.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/sambanova.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/togetherai.svg create mode 100644 litellm/proxy/_experimental/out/assets/logos/xai.svg create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/250-601568e45a5ffece.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/250-a75ee9d79f1140b0.js rename litellm/proxy/_experimental/out/_next/static/chunks/394-0222ddf4d701e0b4.js => ui/litellm-dashboard/out/_next/static/chunks/394-48a36e9c9b2cb488.js (66%) create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/layout-429ad74a94df7643.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/layout-af8319e6c59a08da.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-068a441595bd0fc3.js => ui/litellm-dashboard/out/_next/static/chunks/app/model_hub/page-cde2fb783e81a6c1.js (60%) delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/onboarding/page-1ffe69692e4b2037.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/onboarding/page-5110f2c6a3c9a2f4.js delete mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-75d771fb848b47a8.js create mode 100644 ui/litellm-dashboard/out/_next/static/chunks/app/page-e21d4be3d6c3c16e.js rename ui/litellm-dashboard/out/_next/static/chunks/{main-app-475d6efe4080647d.js => main-app-4f7318ae681a6d94.js} (54%) rename ui/litellm-dashboard/out/_next/static/{9yIyUkG6nV2cO0gn7kJ-Q => soi--ciJeUE6G2Fk4NMBG}/_buildManifest.js (100%) rename ui/litellm-dashboard/out/_next/static/{9yIyUkG6nV2cO0gn7kJ-Q => soi--ciJeUE6G2Fk4NMBG}/_ssgManifest.js (100%) create mode 100644 ui/litellm-dashboard/out/assets/logos/anthropic.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/assemblyai_small.png create mode 100644 ui/litellm-dashboard/out/assets/logos/aws.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/bedrock.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/cerebras.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/cohere.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/databricks.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/deepseek.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/fireworks.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/google.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/groq.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/microsoft_azure.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/mistral.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/ollama.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/openai_small.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/openrouter.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/perplexity-ai.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/sambanova.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/togetherai.svg create mode 100644 ui/litellm-dashboard/out/assets/logos/xai.svg diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/250-601568e45a5ffece.js b/litellm/proxy/_experimental/out/_next/static/chunks/250-601568e45a5ffece.js new file mode 100644 index 0000000000..e6ec4d178e --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/250-601568e45a5ffece.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$I:function(){return M},AZ:function(){return U},Au:function(){return ep},BL:function(){return eI},Br:function(){return F},E9:function(){return eV},EG:function(){return eD},EY:function(){return eM},Eb:function(){return N},FC:function(){return ea},Gh:function(){return eb},H1:function(){return v},H2:function(){return n},Hx:function(){return eu},I1:function(){return j},It:function(){return x},J$:function(){return W},K8:function(){return d},K_:function(){return eH},LY:function(){return ev},Lp:function(){return eB},N3:function(){return eT},N8:function(){return $},NL:function(){return eQ},NV:function(){return f},Nc:function(){return eC},O3:function(){return eJ},OD:function(){return ef},OU:function(){return es},Of:function(){return b},Og:function(){return y},Ov:function(){return E},PT:function(){return D},Qg:function(){return eE},RQ:function(){return _},Rg:function(){return X},Sb:function(){return eP},So:function(){return Q},Tj:function(){return eq},VA:function(){return G},Vt:function(){return eU},W_:function(){return J},X:function(){return ee},XO:function(){return k},Xd:function(){return em},Xm:function(){return S},YU:function(){return eR},Zr:function(){return m},a6:function(){return B},ao:function(){return eZ},b1:function(){return ec},cq:function(){return A},cu:function(){return eS},eH:function(){return H},eZ:function(){return eN},fP:function(){return K},g:function(){return eX},gX:function(){return ej},h3:function(){return er},hT:function(){return ek},hy:function(){return u},ix:function(){return L},j2:function(){return et},jA:function(){return eL},jE:function(){return eA},kK:function(){return p},kn:function(){return Z},lP:function(){return h},lg:function(){return eg},mR:function(){return Y},m_:function(){return I},mp:function(){return ez},n$:function(){return eh},nd:function(){return eY},o6:function(){return q},oC:function(){return e_},pf:function(){return eG},qI:function(){return g},qk:function(){return e$},qm:function(){return w},r6:function(){return O},rs:function(){return C},s0:function(){return R},sN:function(){return ex},t$:function(){return P},t0:function(){return ey},t3:function(){return eK},tN:function(){return en},u5:function(){return eo},um:function(){return eF},v9:function(){return ew},vh:function(){return eO},wX:function(){return T},wd:function(){return el},xA:function(){return ed},zg:function(){return ei}});var r=o(20347),a=o(41021);let n=null;console.log=function(){};let c=0,s=e=>new Promise(t=>setTimeout(t,e)),l=async e=>{let t=Date.now();t-c>6e4?(e.includes("Authentication Error - Expired Key")&&(a.ZP.info("UI Session Expired. Logging out."),c=t,await s(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),c=t):console.log("Error suppressed to prevent spam:",e)},i="Authorization";function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),i=e}let h=async()=>{let e=n?"".concat(n,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},w=async e=>{try{let t=n?"".concat(n,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}}),r=await o.json();return console.log("received litellm model cost data: ".concat(r)),r}catch(e){throw console.error("Failed to get model cost map:",e),e}},p=async(e,t)=>{try{let o=n?"".concat(n,"/model/new"):"/model/new",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text()||"Network response was not ok";throw a.ZP.error(e),Error(e)}let c=await r.json();return console.log("API Response:",c),a.ZP.destroy(),a.ZP.success("Model ".concat(t.model_name," created successfully"),2),c}catch(e){throw console.error("Failed to create key:",e),e}},u=async e=>{try{let t=n?"".concat(n,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){console.error("Failed to get model settings:",e)}},y=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=n?"".concat(n,"/model/delete"):"/model/delete",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=n?"".concat(n,"/budget/delete"):"/budget/delete",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/new"):"/budget/new",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/update"):"/budget/update",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},k=async(e,t)=>{try{let o=n?"".concat(n,"/invitation/new"):"/invitation/new",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},_=async e=>{try{let t=n?"".concat(n,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let r=n?"".concat(n,"/key/generate"):"/key/generate",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error(e)}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let r=n?"".concat(n,"/user/new"):"/user/new",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error(e)}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=n?"".concat(n,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},N=async(e,t)=>{try{let o=n?"".concat(n,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log(a),a}catch(e){throw console.error("Failed to delete user(s):",e),e}},C=async(e,t)=>{try{let o=n?"".concat(n,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log(a),a}catch(e){throw console.error("Failed to delete key:",e),e}},b=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;try{let a=n?"".concat(n,"/user/list"):"/user/list";console.log("in userListCall");let c=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");c.append("user_ids",e)}o&&c.append("page",o.toString()),r&&c.append("page_size",r.toString());let s=c.toString();s&&(a+="?".concat(s));let d=await fetch(a,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw l(e),Error("Network response was not ok")}let h=await d.json();return console.log("/user/list API Response:",h),h}catch(e){throw console.error("Failed to create key:",e),e}},F=async function(e,t,o){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0;try{let s;if(r){s=n?"".concat(n,"/user/list"):"/user/list";let e=new URLSearchParams;null!=a&&e.append("page",a.toString()),null!=c&&e.append("page_size",c.toString()),s+="?".concat(e.toString())}else s=n?"".concat(n,"/user/info"):"/user/info","Admin"===o||"Admin Viewer"===o||t&&(s+="?user_id=".concat(t));console.log("Requesting user data from:",s);let d=await fetch(s,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw l(e),Error("Network response was not ok")}let h=await d.json();return console.log("API Response:",h),h}catch(e){throw console.error("Failed to fetch user data:",e),e}},S=async(e,t)=>{try{let o=n?"".concat(n,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},x=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let r=n?"".concat(n,"/team/list"):"/team/list";console.log("in teamInfoCall");let a=new URLSearchParams;o&&a.append("user_id",o.toString()),t&&a.append("organization_id",t.toString());let c=a.toString();c&&(r+="?".concat(c));let s=await fetch(r,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw l(e),Error("Network response was not ok")}let d=await s.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},B=async e=>{try{let t=n?"".concat(n,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log("/team/available_teams API Response:",r),r}catch(e){throw e}},O=async e=>{try{let t=n?"".concat(n,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{let o=n?"".concat(n,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/organization/new"):"/organization/new",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=n?"".concat(n,"/organization/update"):"/organization/update",r=await fetch(o,{method:"PATCH",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("Update Team Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t)=>{try{let o=n?"".concat(n,"/organization/delete"):"/organization/delete",r=await fetch(o,{method:"DELETE",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!r.ok){let e=await r.text();throw l(e),Error("Error deleting organization: ".concat(e))}return await r.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async e=>{try{let t=n?"".concat(n,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},I=async(e,t,o,r)=>{let a=n?"".concat(n,"/onboarding/claim_token"):"/onboarding/claim_token";try{let n=await fetch(a,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:r})});if(!n.ok){let e=await n.text();throw l(e),Error("Network response was not ok")}let c=await n.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},R=async(e,t,o)=>{try{let r=n?"".concat(n,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!a.ok){let e=await a.text();throw l(e),Error("Network response was not ok")}let c=await a.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},z=!1,V=null,U=async(e,t,o)=>{try{console.log("modelInfoCall:",e,t,o);let c=n?"".concat(n,"/v2/model/info"):"/v2/model/info";r.ZL.includes(o)||(c+="?user_models_only=true");let s=await fetch(c,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw e+="error shown=".concat(z),z||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),a.ZP.info(e,10),z=!0,V&&clearTimeout(V),V=setTimeout(()=>{z=!1},1e4)),Error("Network response was not ok")}let l=await s.json();return console.log("modelInfoCall:",l),l}catch(e){throw console.error("Failed to create key:",e),e}},L=async(e,t)=>{try{let o=n?"".concat(n,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok)throw await r.text(),Error("Network response was not ok");let a=await r.json();return console.log("modelInfoV1Call:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},Z=async e=>{try{let t=n?"".concat(n,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log("modelHubCall:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},D=async e=>{try{let t=n?"".concat(n,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let r=await o.json();return console.log("getAllowedIPs:",r),r.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},H=async(e,t)=>{try{let o=n?"".concat(n,"/add/allowed_ip"):"/add/allowed_ip",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!r.ok){let e=await r.text();throw Error("Network response was not ok: ".concat(e))}let a=await r.json();return console.log("addAllowedIP:",a),a}catch(e){throw console.error("Failed to add allowed IP:",e),e}},M=async(e,t)=>{try{let o=n?"".concat(n,"/delete/allowed_ip"):"/delete/allowed_ip",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!r.ok){let e=await r.text();throw Error("Network response was not ok: ".concat(e))}let a=await r.json();return console.log("deleteAllowedIP:",a),a}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},q=async(e,t,o,r,a,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics"):"/model/metrics";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(a,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},X=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(a="".concat(a,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(r));let c=await fetch(a,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw l(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},K=async(e,t,o,r,a,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(a,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},$=async(e,t,o,r,a,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/exceptions"):"/model/metrics/exceptions";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(a,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},Q=async function(e,t,o){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",i);try{let t=n?"".concat(n,"/models"):"/models",o=new URLSearchParams;!0===r&&o.append("return_wildcard_routes","True"),a&&o.append("team_id",a.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw l(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},Y=async e=>{try{let t=n?"".concat(n,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},W=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/global/spend/tags"):"/global/spend/tags";t&&o&&(a="".concat(a,"?start_date=").concat(t,"&end_date=").concat(o)),r&&(a+="".concat(a,"&tags=").concat(r.join(","))),console.log("in tagsSpendLogsCall:",a);let c=await fetch("".concat(a),{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},ee=async e=>{try{let t=n?"".concat(n,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},et=async e=>{try{let t=n?"".concat(n,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},eo=async(e,t)=>{try{let o=n?"".concat(n,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to create key:",e),e}},er=async(e,t,o,r,a,c,s,d,h)=>{try{let w=n?"".concat(n,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),r&&p.append("request_id",r),a&&p.append("start_date",a),c&&p.append("end_date",c),s&&p.append("page",s.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw l(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},ea=async e=>{try{let t=n?"".concat(n,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},en=async e=>{try{let t=n?"".concat(n,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:r}):JSON.stringify({startTime:o,endTime:r});let s={method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(a,s);if(!d.ok){let e=await d.text();throw l(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},es=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/global/spend/provider"):"/global/spend/provider";o&&r&&(a+="?start_date=".concat(o,"&end_date=").concat(r)),t&&(a+="&api_key=".concat(t));let c={method:"GET",headers:{[i]:"Bearer ".concat(e)}},s=await fetch(a,c);if(!s.ok){let e=await s.text();throw l(e),Error("Network response was not ok")}let d=await s.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},el=async(e,t,o)=>{try{let r=n?"".concat(n,"/global/activity"):"/global/activity";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let a={method:"GET",headers:{[i]:"Bearer ".concat(e)}},c=await fetch(r,a);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ei=async(e,t,o)=>{try{let r=n?"".concat(n,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let a={method:"GET",headers:{[i]:"Bearer ".concat(e)}},c=await fetch(r,a);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ed=async(e,t,o)=>{try{let r=n?"".concat(n,"/global/activity/model"):"/global/activity/model";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let a={method:"GET",headers:{[i]:"Bearer ".concat(e)}},c=await fetch(r,a);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eh=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o)),r&&(a+="&model_group=".concat(r));let c={method:"GET",headers:{[i]:"Bearer ".concat(e)}},s=await fetch(a,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let l=await s.json();return console.log(l),l}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async(e,t,o,r)=>{try{let a=n?"".concat(n,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o)),r&&(a+="&model_group=".concat(r));let c={method:"GET",headers:{[i]:"Bearer ".concat(e)}},s=await fetch(a,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let l=await s.json();return console.log(l),l}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ep=async e=>{try{let t=n?"".concat(n,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},eu=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let a=n?"".concat(n,"/health/test_connection"):"/health/test_connection",c=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",[i]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let l=await c.json();if(!c.ok||"error"===l.status){if("error"===l.status);else{var r;return{status:"error",message:(null===(r=l.error)||void 0===r?void 0:r.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return l}catch(e){throw console.error("Model connection test error:",e),e}},ey=async(e,t)=>{try{console.log("entering keyInfoV1Call");let o=n?"".concat(n,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(console.log("response",r),!r.ok){let e=await r.text();l(e),a.ZP.error("Failed to fetch key info - "+e)}let c=await r.json();return console.log("data",c),c}catch(e){throw console.error("Failed to fetch key info:",e),e}},ef=async(e,t,o,r,a)=>{try{let c=n?"".concat(n,"/key/list"):"/key/list";console.log("in keyListCall");let s=new URLSearchParams;o&&s.append("team_id",o.toString()),t&&s.append("organization_id",t.toString()),r&&s.append("page",r.toString()),a&&s.append("size",a.toString()),s.append("return_full_object","true"),s.append("include_team_keys","true");let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw l(e),Error("Network response was not ok")}let w=await h.json();return console.log("/team/list API Response:",w),w}catch(e){throw console.error("Failed to create key:",e),e}},em=async(e,t)=>{try{let o=n?"".concat(n,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log(a),a}catch(e){throw console.error("Failed to get requested models:",e),e}},eg=async e=>{try{let t=n?"".concat(n,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log("response from user/available_role",r),r}catch(e){throw e}},ek=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/team/new"):"/team/new",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},e_=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/credentials"):"/credentials",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eT=async e=>{try{let t=n?"".concat(n,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log("/credentials API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eE=async(e,t,o)=>{try{let r=n?"".concat(n,"/credentials"):"/credentials";t?r+="/by_name/".concat(t):o&&(r+="/by_model/".concat(o)),console.log("in credentialListCall");let a=await fetch(r,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw l(e),Error("Network response was not ok")}let c=await a.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ej=async(e,t)=>{try{let o=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let r=await fetch(o,{method:"DELETE",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let a=await r.json();return console.log(a),a}catch(e){throw console.error("Failed to delete key:",e),e}},eN=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let r=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t),a=await fetch(r,{method:"PATCH",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eC=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=n?"".concat(n,"/key/update"):"/key/update",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("Update key Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=n?"".concat(n,"/team/update"):"/team/update",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("Update Team Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eF=async(e,t)=>{try{console.log("Form Values in modelUpateCall:",t);let o=n?"".concat(n,"/model/update"):"/model/update",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let a=await r.json();return console.log("Update model Response:",a),a}catch(e){throw console.error("Failed to update model:",e),e}},eS=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=n?"".concat(n,"/team/member_add"):"/team/member_add",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ex=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=n?"".concat(n,"/team/member_update"):"/team/member_update",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=n?"".concat(n,"/team/member_delete"):"/team/member_delete",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eO=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=n?"".concat(n,"/organization/member_add"):"/organization/member_add",a=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error(e)}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let r=n?"".concat(n,"/organization/member_delete"):"/organization/member_delete",a=await fetch(r,{method:"DELETE",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let r=n?"".concat(n,"/organization/member_update"):"/organization/member_update",a=await fetch(r,{method:"PATCH",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!a.ok){let e=await a.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await a.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},eG=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let r=n?"".concat(n,"/user/update"):"/user/update",a={...t};null!==o&&(a.user_role=o),a=JSON.stringify(a);let c=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:a});if(!c.ok){let e=await c.text();throw l(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let s=await c.json();return console.log("API Response:",s),s}catch(e){throw console.error("Failed to create key:",e),e}},eA=async(e,t)=>{try{let o=n?"".concat(n,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error(e)}let c=await r.json();return a.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eJ=async e=>{try{let t=n?"".concat(n,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eI=async(e,t,o)=>{try{let t=n?"".concat(n,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eR=async e=>{try{let t=n?"".concat(n,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},ez=async e=>{try{let t=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eV=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),r=await fetch(o,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok)throw await r.text(),Error("Network response was not ok");return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eU=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eL=async(e,t,o)=>{try{let r=n?"".concat(n,"/config/field/update"):"/config/field/update",c=await fetch(r,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw l(e),Error("Network response was not ok")}let s=await c.json();return a.ZP.success("Successfully updated value!"),s}catch(e){throw console.error("Failed to set callbacks:",e),e}},eZ=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/delete"):"/config/field/delete",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let c=await r.json();return a.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eD=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),r=await fetch(o,{method:"DELETE",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eH=async(e,t)=>{try{let o=n?"".concat(n,"/config/update"):"/config/update",r=await fetch(o,{method:"POST",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eM=async e=>{try{let t=n?"".concat(n,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},eq=async e=>{try{let t=n?"".concat(n,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eX=async e=>{try{let t=n?"".concat(n,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eK=async e=>{try{let t=n?"".concat(n,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log("Guardrails list response:",r),r}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},e$=async(e,t,o)=>{try{let r=n?"".concat(n,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",r);let a=await fetch(r,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw l(e),Error("Network response was not ok")}let c=await a.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},eQ=async e=>{try{let t=n?"".concat(n,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw l(e),Error("Network response was not ok")}let r=await o.json();return console.log("Fetched SSO settings:",r),r}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},eY=async(e,t)=>{try{let o=n?"".concat(n,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let r=await fetch(o,{method:"PATCH",headers:{[i]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){let e=await r.text();throw l(e),Error("Network response was not ok")}let c=await r.json();return console.log("Updated internal user settings:",c),a.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}}},20347:function(e,t,o){o.d(t,{LQ:function(){return n},ZL:function(){return r},lo:function(){return a}});let r=["Admin","Admin Viewer","proxy_admin","proxy_admin_viewer","org_admin"],a=["Internal User","Internal Viewer"],n=["Internal User","Admin"]}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/250-a75ee9d79f1140b0.js b/litellm/proxy/_experimental/out/_next/static/chunks/250-a75ee9d79f1140b0.js deleted file mode 100644 index 882840d12b..0000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/250-a75ee9d79f1140b0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$I:function(){return H},AZ:function(){return V},Au:function(){return ew},BL:function(){return eA},Br:function(){return b},E9:function(){return ez},EG:function(){return eZ},EY:function(){return eH},Eb:function(){return j},FC:function(){return er},Gh:function(){return eC},H1:function(){return P},H2:function(){return a},Hx:function(){return ep},I1:function(){return E},It:function(){return S},J$:function(){return Q},K8:function(){return i},K_:function(){return eD},LY:function(){return eP},Lp:function(){return ex},N3:function(){return e_},N8:function(){return K},NL:function(){return e$},NV:function(){return y},Nc:function(){return eN},O3:function(){return eJ},OD:function(){return ey},OU:function(){return ec},Of:function(){return C},Og:function(){return u},Ov:function(){return T},PT:function(){return Z},Qg:function(){return eT},RQ:function(){return k},Rg:function(){return q},Sb:function(){return eO},So:function(){return $},Tj:function(){return eM},VA:function(){return v},Vt:function(){return eV},W_:function(){return J},X:function(){return W},XO:function(){return g},Xd:function(){return ef},Xm:function(){return F},YU:function(){return eR},Zr:function(){return f},a6:function(){return x},ao:function(){return eL},b1:function(){return en},cq:function(){return G},cu:function(){return eF},eH:function(){return D},eZ:function(){return ej},fP:function(){return X},g:function(){return eq},gX:function(){return eE},h3:function(){return eo},hT:function(){return eg},hy:function(){return p},ix:function(){return U},j2:function(){return ee},jA:function(){return eU},jE:function(){return eG},kK:function(){return w},kn:function(){return L},lP:function(){return d},lg:function(){return em},mR:function(){return Y},m_:function(){return A},mp:function(){return eI},n$:function(){return ed},nd:function(){return eY},o6:function(){return M},oC:function(){return ek},pf:function(){return ev},qI:function(){return m},qk:function(){return eK},qm:function(){return h},r6:function(){return B},rs:function(){return N},s0:function(){return R},sN:function(){return eS},t$:function(){return O},t0:function(){return eu},t3:function(){return eX},tN:function(){return ea},u5:function(){return et},um:function(){return eb},v9:function(){return eh},vh:function(){return eB},wX:function(){return _},wd:function(){return es},xA:function(){return ei},zg:function(){return el}});var r=o(41021);let a=null;console.log=function(){};let n=0,c=e=>new Promise(t=>setTimeout(t,e)),s=async e=>{let t=Date.now();t-n>6e4?(e.includes("Authentication Error - Expired Key")&&(r.ZP.info("UI Session Expired. Logging out."),n=t,await c(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),n=t):console.log("Error suppressed to prevent spam:",e)},l="Authorization";function i(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),l=e}let d=async()=>{let e=a?"".concat(a,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},h=async e=>{try{let t=a?"".concat(a,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}}),r=await o.json();return console.log("received litellm model cost data: ".concat(r)),r}catch(e){throw console.error("Failed to get model cost map:",e),e}},w=async(e,t)=>{try{let c=a?"".concat(a,"/model/new"):"/model/new",s=await fetch(c,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!s.ok){var o,n;let e=await s.json(),t=(null===(n=e.error)||void 0===n?void 0:null===(o=n.message)||void 0===o?void 0:o.error)||"Network response was not ok";throw r.ZP.error(t),Error(t)}let i=await s.json();return console.log("API Response:",i),r.ZP.destroy(),r.ZP.success("Model ".concat(t.model_name," created successfully"),2),i}catch(e){throw console.error("Failed to create key:",e),e}},p=async e=>{try{let t=a?"".concat(a,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},u=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=a?"".concat(a,"/model/delete"):"/model/delete",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},y=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=a?"".concat(a,"/budget/delete"):"/budget/delete",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=a?"".concat(a,"/budget/new"):"/budget/new",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=a?"".concat(a,"/budget/update"):"/budget/update",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{let o=a?"".concat(a,"/invitation/new"):"/invitation/new",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},k=async e=>{try{let t=a?"".concat(a,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},_=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let r=a?"".concat(a,"/key/generate"):"/key/generate",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error(e)}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let r=a?"".concat(a,"/user/new"):"/user/new",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error(e)}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t)=>{try{let o=a?"".concat(a,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log(n),n}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=a?"".concat(a,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log(n),n}catch(e){throw console.error("Failed to delete user(s):",e),e}},N=async(e,t)=>{try{let o=a?"".concat(a,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log(n),n}catch(e){throw console.error("Failed to delete key:",e),e}},C=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;try{let n=a?"".concat(a,"/user/list"):"/user/list";console.log("in userListCall");let c=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");c.append("user_ids",e)}o&&c.append("page",o.toString()),r&&c.append("page_size",r.toString());let i=c.toString();i&&(n+="?".concat(i));let d=await fetch(n,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw s(e),Error("Network response was not ok")}let h=await d.json();return console.log("/user/list API Response:",h),h}catch(e){throw console.error("Failed to create key:",e),e}},b=async function(e,t,o){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],n=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0;try{let i;if(r){i=a?"".concat(a,"/user/list"):"/user/list";let e=new URLSearchParams;null!=n&&e.append("page",n.toString()),null!=c&&e.append("page_size",c.toString()),i+="?".concat(e.toString())}else i=a?"".concat(a,"/user/info"):"/user/info","Admin"===o||"Admin Viewer"===o||t&&(i+="?user_id=".concat(t));console.log("Requesting user data from:",i);let d=await fetch(i,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw s(e),Error("Network response was not ok")}let h=await d.json();return console.log("API Response:",h),h}catch(e){throw console.error("Failed to fetch user data:",e),e}},F=async(e,t)=>{try{let o=a?"".concat(a,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},S=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let r=a?"".concat(a,"/team/list"):"/team/list";console.log("in teamInfoCall");let n=new URLSearchParams;o&&n.append("user_id",o.toString()),t&&n.append("organization_id",t.toString());let c=n.toString();c&&(r+="?".concat(c));let i=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!i.ok){let e=await i.text();throw s(e),Error("Network response was not ok")}let d=await i.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},x=async e=>{try{let t=a?"".concat(a,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log("/team/available_teams API Response:",r),r}catch(e){throw e}},B=async e=>{try{let t=a?"".concat(a,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},O=async(e,t)=>{try{let o=a?"".concat(a,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=a?"".concat(a,"/organization/new"):"/organization/new",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=a?"".concat(a,"/organization/update"):"/organization/update",r=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("Update Team Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{let o=a?"".concat(a,"/organization/delete"):"/organization/delete",r=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!r.ok){let e=await r.text();throw s(e),Error("Error deleting organization: ".concat(e))}return await r.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async e=>{try{let t=a?"".concat(a,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t,o,r)=>{let n=a?"".concat(a,"/onboarding/claim_token"):"/onboarding/claim_token";try{let a=await fetch(n,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:r})});if(!a.ok){let e=await a.text();throw s(e),Error("Network response was not ok")}let c=await a.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},R=async(e,t,o)=>{try{let r=a?"".concat(a,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!n.ok){let e=await n.text();throw s(e),Error("Network response was not ok")}let c=await n.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},I=!1,z=null,V=async(e,t,o)=>{try{let t=a?"".concat(a,"/v2/model/info"):"/v2/model/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw e+="error shown=".concat(I),I||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),r.ZP.info(e,10),I=!0,z&&clearTimeout(z),z=setTimeout(()=>{I=!1},1e4)),Error("Network response was not ok")}let n=await o.json();return console.log("modelInfoCall:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},U=async(e,t)=>{try{let o=a?"".concat(a,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok)throw await r.text(),Error("Network response was not ok");let n=await r.json();return console.log("modelInfoV1Call:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},L=async e=>{try{let t=a?"".concat(a,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log("modelHubCall:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},Z=async e=>{try{let t=a?"".concat(a,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let r=await o.json();return console.log("getAllowedIPs:",r),r.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},D=async(e,t)=>{try{let o=a?"".concat(a,"/add/allowed_ip"):"/add/allowed_ip",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!r.ok){let e=await r.text();throw Error("Network response was not ok: ".concat(e))}let n=await r.json();return console.log("addAllowedIP:",n),n}catch(e){throw console.error("Failed to add allowed IP:",e),e}},H=async(e,t)=>{try{let o=a?"".concat(a,"/delete/allowed_ip"):"/delete/allowed_ip",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!r.ok){let e=await r.text();throw Error("Network response was not ok: ".concat(e))}let n=await r.json();return console.log("deleteAllowedIP:",n),n}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},M=async(e,t,o,r,n,c,i,d)=>{try{let t=a?"".concat(a,"/model/metrics"):"/model/metrics";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(n,"&endTime=").concat(c,"&api_key=").concat(i,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},q=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(n="".concat(n,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(r));let c=await fetch(n,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw s(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},X=async(e,t,o,r,n,c,i,d)=>{try{let t=a?"".concat(a,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(n,"&endTime=").concat(c,"&api_key=").concat(i,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},K=async(e,t,o,r,n,c,i,d)=>{try{let t=a?"".concat(a,"/model/metrics/exceptions"):"/model/metrics/exceptions";r&&(t="".concat(t,"?_selected_model_group=").concat(r,"&startTime=").concat(n,"&endTime=").concat(c,"&api_key=").concat(i,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},$=async function(e,t,o){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],n=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",l);try{let t=a?"".concat(a,"/models"):"/models",o=new URLSearchParams;!0===r&&o.append("return_wildcard_routes","True"),n&&o.append("team_id",n.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw s(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},Y=async e=>{try{let t=a?"".concat(a,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},Q=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/global/spend/tags"):"/global/spend/tags";t&&o&&(n="".concat(n,"?start_date=").concat(t,"&end_date=").concat(o)),r&&(n+="".concat(n,"&tags=").concat(r.join(","))),console.log("in tagsSpendLogsCall:",n);let c=await fetch("".concat(n),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},W=async e=>{try{let t=a?"".concat(a,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ee=async e=>{try{let t=a?"".concat(a,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},et=async(e,t)=>{try{let o=a?"".concat(a,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to create key:",e),e}},eo=async(e,t,o,r,n,c,i,d,h)=>{try{let w=a?"".concat(a,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),r&&p.append("request_id",r),n&&p.append("start_date",n),c&&p.append("end_date",c),i&&p.append("page",i.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw s(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},er=async e=>{try{let t=a?"".concat(a,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ea=async e=>{try{let t=a?"".concat(a,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},en=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:r}):JSON.stringify({startTime:o,endTime:r});let i={method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(n,i);if(!d.ok){let e=await d.text();throw s(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/global/spend/provider"):"/global/spend/provider";o&&r&&(n+="?start_date=".concat(o,"&end_date=").concat(r)),t&&(n+="&api_key=".concat(t));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},i=await fetch(n,c);if(!i.ok){let e=await i.text();throw s(e),Error("Network response was not ok")}let d=await i.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},es=async(e,t,o)=>{try{let r=a?"".concat(a,"/global/activity"):"/global/activity";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let n={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(r,n);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},el=async(e,t,o)=>{try{let r=a?"".concat(a,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let n={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(r,n);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ei=async(e,t,o)=>{try{let r=a?"".concat(a,"/global/activity/model"):"/global/activity/model";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o));let n={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(r,n);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ed=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(n+="?start_date=".concat(t,"&end_date=").concat(o)),r&&(n+="&model_group=".concat(r));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(n,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eh=async(e,t,o,r)=>{try{let n=a?"".concat(a,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(n+="?start_date=".concat(t,"&end_date=").concat(o)),r&&(n+="&model_group=".concat(r));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(n,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async e=>{try{let t=a?"".concat(a,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ep=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let n=a?"".concat(a,"/health/test_connection"):"/health/test_connection",c=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",[l]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let i=await c.json();if(!c.ok||"error"===i.status){if("error"===i.status);else{var r;return{status:"error",message:(null===(r=i.error)||void 0===r?void 0:r.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return i}catch(e){throw console.error("Model connection test error:",e),e}},eu=async(e,t)=>{try{let o=a?"".concat(a,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to fetch key info:",e),e}},ey=async(e,t,o,r,n)=>{try{let c=a?"".concat(a,"/key/list"):"/key/list";console.log("in keyListCall");let i=new URLSearchParams;o&&i.append("team_id",o.toString()),t&&i.append("organization_id",t.toString()),r&&i.append("page",r.toString()),n&&i.append("size",n.toString()),i.append("return_full_object","true"),i.append("include_team_keys","true");let d=i.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw s(e),Error("Network response was not ok")}let w=await h.json();return console.log("/team/list API Response:",w),w}catch(e){throw console.error("Failed to create key:",e),e}},ef=async(e,t)=>{try{let o=a?"".concat(a,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log(n),n}catch(e){throw console.error("Failed to get requested models:",e),e}},em=async e=>{try{let t=a?"".concat(a,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let r=await o.json();return console.log("response from user/available_role",r),r}catch(e){throw e}},eg=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=a?"".concat(a,"/team/new"):"/team/new",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},ek=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=a?"".concat(a,"/credentials"):"/credentials",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("API Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},e_=async e=>{try{let t=a?"".concat(a,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log("/credentials API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eT=async(e,t,o)=>{try{let r=a?"".concat(a,"/credentials"):"/credentials";t?r+="/by_name/".concat(t):o&&(r+="/by_model/".concat(o)),console.log("in credentialListCall");let n=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!n.ok){let e=await n.text();throw s(e),Error("Network response was not ok")}let c=await n.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eE=async(e,t)=>{try{let o=a?"".concat(a,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let r=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}let n=await r.json();return console.log(n),n}catch(e){throw console.error("Failed to delete key:",e),e}},ej=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let r=a?"".concat(a,"/credentials/").concat(t):"/credentials/".concat(t),n=await fetch(r,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eN=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=a?"".concat(a,"/key/update"):"/key/update",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("Update key Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},eC=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=a?"".concat(a,"/team/update"):"/team/update",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("Update Team Response:",n),n}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t)=>{try{console.log("Form Values in modelUpateCall:",t);let o=a?"".concat(a,"/model/update"):"/model/update",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let n=await r.json();return console.log("Update model Response:",n),n}catch(e){throw console.error("Failed to update model:",e),e}},eF=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=a?"".concat(a,"/team/member_add"):"/team/member_add",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eS=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=a?"".concat(a,"/team/member_update"):"/team/member_update",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ex=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=a?"".concat(a,"/team/member_delete"):"/team/member_delete",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let r=a?"".concat(a,"/organization/member_add"):"/organization/member_add",n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error(e)}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eO=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let r=a?"".concat(a,"/organization/member_delete"):"/organization/member_delete",n=await fetch(r,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let r=a?"".concat(a,"/organization/member_update"):"/organization/member_update",n=await fetch(r,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!n.ok){let e=await n.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await n.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let r=a?"".concat(a,"/user/update"):"/user/update",n={...t};null!==o&&(n.user_role=o),n=JSON.stringify(n);let c=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:n});if(!c.ok){let e=await c.text();throw s(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let i=await c.json();return console.log("API Response:",i),i}catch(e){throw console.error("Failed to create key:",e),e}},eG=async(e,t)=>{try{let o=a?"".concat(a,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let n=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!n.ok){let e=await n.text();throw s(e),Error(e)}let c=await n.json();return r.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eJ=async e=>{try{let t=a?"".concat(a,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eA=async(e,t,o)=>{try{let t=a?"".concat(a,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eR=async e=>{try{let t=a?"".concat(a,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eI=async e=>{try{let t=a?"".concat(a,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},ez=async(e,t)=>{try{let o=a?"".concat(a,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),r=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok)throw await r.text(),Error("Network response was not ok");return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eV=async(e,t)=>{try{let o=a?"".concat(a,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eU=async(e,t,o)=>{try{let n=a?"".concat(a,"/config/field/update"):"/config/field/update",c=await fetch(n,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw s(e),Error("Network response was not ok")}let i=await c.json();return r.ZP.success("Successfully updated value!"),i}catch(e){throw console.error("Failed to set callbacks:",e),e}},eL=async(e,t)=>{try{let o=a?"".concat(a,"/config/field/delete"):"/config/field/delete",n=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!n.ok){let e=await n.text();throw s(e),Error("Network response was not ok")}let c=await n.json();return r.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eZ=async(e,t)=>{try{let o=a?"".concat(a,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),r=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eD=async(e,t)=>{try{let o=a?"".concat(a,"/config/update"):"/config/update",r=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw s(e),Error("Network response was not ok")}return await r.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eH=async e=>{try{let t=a?"".concat(a,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},eM=async e=>{try{let t=a?"".concat(a,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eq=async e=>{try{let t=a?"".concat(a,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eX=async e=>{try{let t=a?"".concat(a,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log("Guardrails list response:",r),r}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},eK=async(e,t,o)=>{try{let r=a?"".concat(a,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",r);let n=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!n.ok){let e=await n.text();throw s(e),Error("Network response was not ok")}let c=await n.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},e$=async e=>{try{let t=a?"".concat(a,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw s(e),Error("Network response was not ok")}let r=await o.json();return console.log("Fetched SSO settings:",r),r}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},eY=async(e,t)=>{try{let o=a?"".concat(a,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let n=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!n.ok){let e=await n.text();throw s(e),Error("Network response was not ok")}let c=await n.json();return console.log("Updated internal user settings:",c),r.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}}}}]); \ No newline at end of file diff --git a/ui/litellm-dashboard/out/_next/static/chunks/394-0222ddf4d701e0b4.js b/litellm/proxy/_experimental/out/_next/static/chunks/394-48a36e9c9b2cb488.js similarity index 66% rename from ui/litellm-dashboard/out/_next/static/chunks/394-0222ddf4d701e0b4.js rename to litellm/proxy/_experimental/out/_next/static/chunks/394-48a36e9c9b2cb488.js index f6dfcd861e..fa8ff4bc05 100644 --- a/ui/litellm-dashboard/out/_next/static/chunks/394-0222ddf4d701e0b4.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/394-48a36e9c9b2cb488.js @@ -1,10 +1,10 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[394],{12660:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M917.7 148.8l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 00-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 000 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM769.1 441.7l-59.4 59.4-186.8-186.8 59.4-59.4c24.9-24.9 58.1-38.7 93.4-38.7 35.3 0 68.4 13.7 93.4 38.7 24.9 24.9 38.7 58.1 38.7 93.4 0 35.3-13.8 68.4-38.7 93.4zm-190.2 105a8.03 8.03 0 00-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 00-11.3 0L363 475.3l-43-43a7.85 7.85 0 00-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2c-68.9 69-77 175.7-24.3 253.5l-76.1 76.1a8.03 8.03 0 000 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2zM441.7 769.1a131.32 131.32 0 01-93.4 38.7c-35.3 0-68.4-13.7-93.4-38.7a131.32 131.32 0 01-38.7-93.4c0-35.3 13.7-68.4 38.7-93.4l59.4-59.4 186.8 186.8-59.4 59.4z"}}]},name:"api",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},88009:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},37527:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 00-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM512 196.7l271.1 197.2H240.9L512 196.7zM264 462h117v374H264V462zm189 0h117v374H453V462zm307 374H642V462h118v374z"}}]},name:"bank",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},9775:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-600-80h56c4.4 0 8-3.6 8-8V560c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V384c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v320c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V462c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v242c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v400c0 4.4 3.6 8 8 8z"}}]},name:"bar-chart",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},68208:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M856 376H648V168c0-8.8-7.2-16-16-16H168c-8.8 0-16 7.2-16 16v464c0 8.8 7.2 16 16 16h208v208c0 8.8 7.2 16 16 16h464c8.8 0 16-7.2 16-16V392c0-8.8-7.2-16-16-16zm-480 16v188H220V220h360v156H392c-8.8 0-16 7.2-16 16zm204 52v136H444V444h136zm224 360H444V648h188c8.8 0 16-7.2 16-16V444h156v360z"}}]},name:"block",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},9738:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"}}]},name:"check",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},44625:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208zM304 240a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0z"}}]},name:"database",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},70464:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"}}]},name:"down",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},73879:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"download",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},39760:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"ellipsis",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},41169:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 472a40 40 0 1080 0 40 40 0 10-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 01-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z"}}]},name:"experiment",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},6520:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"}}]},name:"eye",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},15424:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M464 336a48 48 0 1096 0 48 48 0 10-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"}}]},name:"info-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},92403:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5l-41.1 41.1-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-44.9 44.9-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-65.3 65.3a8.03 8.03 0 000 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6A304.06 304.06 0 00608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644c-60.9 0-118.2-23.7-161.2-66.8-43.1-43-66.8-100.3-66.8-161.2 0-60.9 23.7-118.2 66.8-161.2 43-43.1 100.3-66.8 161.2-66.8 60.9 0 118.2 23.7 161.2 66.8 43.1 43 66.8 100.3 66.8 161.2 0 60.9-23.7 118.2-66.8 161.2z"}}]},name:"key",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},15327:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"}}]},name:"left",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},48231:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM305.8 637.7c3.1 3.1 8.1 3.1 11.3 0l138.3-137.6L583 628.5c3.1 3.1 8.2 3.1 11.3 0l275.4-275.3c3.1-3.1 3.1-8.2 0-11.3l-39.6-39.6a8.03 8.03 0 00-11.3 0l-230 229.9L461.4 404a8.03 8.03 0 00-11.3 0L266.3 586.7a8.03 8.03 0 000 11.3l39.5 39.7z"}}]},name:"line-chart",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},40428:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M868 732h-70.3c-4.8 0-9.3 2.1-12.3 5.8-7 8.5-14.5 16.7-22.4 24.5a353.84 353.84 0 01-112.7 75.9A352.8 352.8 0 01512.4 866c-47.9 0-94.3-9.4-137.9-27.8a353.84 353.84 0 01-112.7-75.9 353.28 353.28 0 01-76-112.5C167.3 606.2 158 559.9 158 512s9.4-94.2 27.8-137.8c17.8-42.1 43.4-80 76-112.5s70.5-58.1 112.7-75.9c43.6-18.4 90-27.8 137.9-27.8 47.9 0 94.3 9.3 137.9 27.8 42.2 17.8 80.1 43.4 112.7 75.9 7.9 7.9 15.3 16.1 22.4 24.5 3 3.7 7.6 5.8 12.3 5.8H868c6.3 0 10.2-7 6.7-12.3C798 160.5 663.8 81.6 511.3 82 271.7 82.6 79.6 277.1 82 516.4 84.4 751.9 276.2 942 512.4 942c152.1 0 285.7-78.8 362.3-197.7 3.4-5.3-.4-12.3-6.7-12.3zm88.9-226.3L815 393.7c-5.3-4.2-13-.4-13 6.3v76H488c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h314v76c0 6.7 7.8 10.5 13 6.3l141.9-112a8 8 0 000-12.6z"}}]},name:"logout",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},45246:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"minus-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},28595:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M719.4 499.1l-296.1-215A15.9 15.9 0 00398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 000-25.8zm-257.6 134V390.9L628.5 512 461.8 633.1z"}}]},name:"play-circle",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},96473:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"}},{tag:"path",attrs:{d:"M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"}}]},name:"plus",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},57400:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64L128 192v384c0 212.1 171.9 384 384 384s384-171.9 384-384V192L512 64zm312 512c0 172.3-139.7 312-312 312S200 748.3 200 576V246l312-110 312 110v330z"}},{tag:"path",attrs:{d:"M378.4 475.1a35.91 35.91 0 00-50.9 0 35.91 35.91 0 000 50.9l129.4 129.4 2.1 2.1a33.98 33.98 0 0048.1 0L730.6 434a33.98 33.98 0 000-48.1l-2.8-2.8a33.98 33.98 0 00-48.1 0L483 579.7 378.4 475.1z"}}]},name:"safety",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},29436:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"}}]},name:"search",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},55322:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"}}]},name:"setting",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},41361:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M824.2 699.9a301.55 301.55 0 00-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 00-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 008 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 01612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 008-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 01-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 01612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 01-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 008 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"}}]},name:"team",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},3632:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 00-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"upload",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},15883:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"}}]},name:"user",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},35291:function(e,t,n){"use strict";n.d(t,{Z:function(){return l}});var r=n(1119),o=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"},a=n(55015),l=o.forwardRef(function(e,t){return o.createElement(a.Z,(0,r.Z)({},e,{ref:t,icon:i}))})},58747:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"}))}},4537:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"}))}},75105:function(e,t,n){"use strict";n.d(t,{Z:function(){return et}});var r=n(5853),o=n(2265),i=n(47625),a=n(93765),l=n(61994),c=n(59221),s=n(86757),u=n.n(s),d=n(95645),f=n.n(d),p=n(77571),h=n.n(p),m=n(82559),g=n.n(m),v=n(21652),y=n.n(v),b=n(57165),x=n(81889),w=n(9841),S=n(58772),k=n(34067),E=n(16630),C=n(85355),O=n(82944),j=["layout","type","stroke","connectNulls","isRange","ref"];function P(e){return(P="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function N(){return(N=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(i,j));return o.createElement(w.m,{clipPath:n?"url(#clipPath-".concat(r,")"):null},o.createElement(b.H,N({},(0,O.L6)(d,!0),{points:e,connectNulls:s,type:l,baseLine:t,layout:a,stroke:"none",className:"recharts-area-area"})),"none"!==c&&o.createElement(b.H,N({},(0,O.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:s,fill:"none",points:e})),"none"!==c&&u&&o.createElement(b.H,N({},(0,O.L6)(this.props,!1),{className:"recharts-area-curve",layout:a,type:l,connectNulls:s,fill:"none",points:t})))}},{key:"renderAreaWithAnimation",value:function(e,t){var n=this,r=this.props,i=r.points,a=r.baseLine,l=r.isAnimationActive,s=r.animationBegin,u=r.animationDuration,d=r.animationEasing,f=r.animationId,p=this.state,m=p.prevPoints,v=p.prevBaseLine;return o.createElement(c.ZP,{begin:s,duration:u,isActive:l,easing:d,from:{t:0},to:{t:1},key:"area-".concat(f),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(r){var l=r.t;if(m){var c,s=m.length/i.length,u=i.map(function(e,t){var n=Math.floor(t*s);if(m[n]){var r=m[n],o=(0,E.k4)(r.x,e.x),i=(0,E.k4)(r.y,e.y);return I(I({},e),{},{x:o(l),y:i(l)})}return e});return c=(0,E.hj)(a)&&"number"==typeof a?(0,E.k4)(v,a)(l):h()(a)||g()(a)?(0,E.k4)(v,0)(l):a.map(function(e,t){var n=Math.floor(t*s);if(v[n]){var r=v[n],o=(0,E.k4)(r.x,e.x),i=(0,E.k4)(r.y,e.y);return I(I({},e),{},{x:o(l),y:i(l)})}return e}),n.renderAreaStatically(u,c,e,t)}return o.createElement(w.m,null,o.createElement("defs",null,o.createElement("clipPath",{id:"animationClipPath-".concat(t)},n.renderClipRect(l))),o.createElement(w.m,{clipPath:"url(#animationClipPath-".concat(t,")")},n.renderAreaStatically(i,a,e,t)))})}},{key:"renderArea",value:function(e,t){var n=this.props,r=n.points,o=n.baseLine,i=n.isAnimationActive,a=this.state,l=a.prevPoints,c=a.prevBaseLine,s=a.totalLength;return i&&r&&r.length&&(!l&&s>0||!y()(l,r)||!y()(c,o))?this.renderAreaWithAnimation(e,t):this.renderAreaStatically(r,o,e,t)}},{key:"render",value:function(){var e,t=this.props,n=t.hide,r=t.dot,i=t.points,a=t.className,c=t.top,s=t.left,u=t.xAxis,d=t.yAxis,f=t.width,p=t.height,m=t.isAnimationActive,g=t.id;if(n||!i||!i.length)return null;var v=this.state.isAnimationFinished,y=1===i.length,b=(0,l.Z)("recharts-area",a),x=u&&u.allowDataOverflow,k=d&&d.allowDataOverflow,E=x||k,C=h()(g)?this.id:g,j=null!==(e=(0,O.L6)(r,!1))&&void 0!==e?e:{r:3,strokeWidth:2},P=j.r,N=j.strokeWidth,M=((0,O.$k)(r)?r:{}).clipDot,I=void 0===M||M,R=2*(void 0===P?3:P)+(void 0===N?2:N);return o.createElement(w.m,{className:b},x||k?o.createElement("defs",null,o.createElement("clipPath",{id:"clipPath-".concat(C)},o.createElement("rect",{x:x?s:s-f/2,y:k?c:c-p/2,width:x?f:2*f,height:k?p:2*p})),!I&&o.createElement("clipPath",{id:"clipPath-dots-".concat(C)},o.createElement("rect",{x:s-R/2,y:c-R/2,width:f+R,height:p+R}))):null,y?null:this.renderArea(E,C),(r||y)&&this.renderDots(E,I,C),(!m||v)&&S.e.renderCallByParent(this.props,i))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curPoints:e.points,curBaseLine:e.baseLine,prevPoints:t.curPoints,prevBaseLine:t.curBaseLine}:e.points!==t.curPoints||e.baseLine!==t.curBaseLine?{curPoints:e.points,curBaseLine:e.baseLine}:null}}],n&&R(a.prototype,n),r&&R(a,r),Object.defineProperty(a,"prototype",{writable:!1}),a}(o.PureComponent);D(L,"displayName","Area"),D(L,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!k.x.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"}),D(L,"getBaseValue",function(e,t,n,r){var o=e.layout,i=e.baseValue,a=t.props.baseValue,l=null!=a?a:i;if((0,E.hj)(l)&&"number"==typeof l)return l;var c="horizontal"===o?r:n,s=c.scale.domain();if("number"===c.type){var u=Math.max(s[0],s[1]),d=Math.min(s[0],s[1]);return"dataMin"===l?d:"dataMax"===l?u:u<0?u:Math.max(Math.min(s[0],s[1]),0)}return"dataMin"===l?s[0]:"dataMax"===l?s[1]:s[0]}),D(L,"getComposedData",function(e){var t,n=e.props,r=e.item,o=e.xAxis,i=e.yAxis,a=e.xAxisTicks,l=e.yAxisTicks,c=e.bandSize,s=e.dataKey,u=e.stackedData,d=e.dataStartIndex,f=e.displayedData,p=e.offset,h=n.layout,m=u&&u.length,g=L.getBaseValue(n,r,o,i),v="horizontal"===h,y=!1,b=f.map(function(e,t){m?n=u[d+t]:Array.isArray(n=(0,C.F$)(e,s))?y=!0:n=[g,n];var n,r=null==n[1]||m&&null==(0,C.F$)(e,s);return v?{x:(0,C.Hv)({axis:o,ticks:a,bandSize:c,entry:e,index:t}),y:r?null:i.scale(n[1]),value:n,payload:e}:{x:r?null:o.scale(n[1]),y:(0,C.Hv)({axis:i,ticks:l,bandSize:c,entry:e,index:t}),value:n,payload:e}});return t=m||y?b.map(function(e){var t=Array.isArray(e.value)?e.value[0]:null;return v?{x:e.x,y:null!=t&&null!=e.y?i.scale(t):null}:{x:null!=t?o.scale(t):null,y:e.y}}):v?i.scale(g):o.scale(g),I({points:b,baseLine:t,layout:h,isRange:y},p)}),D(L,"renderDotItem",function(e,t){return o.isValidElement(e)?o.cloneElement(e,t):u()(e)?e(t):o.createElement(x.o,N({},t,{className:"recharts-area-dot"}))});var z=n(97059),B=n(62994),F=n(25311),H=(0,a.z)({chartName:"AreaChart",GraphicalChild:L,axisComponents:[{axisType:"xAxis",AxisComp:z.K},{axisType:"yAxis",AxisComp:B.B}],formatAxisMap:F.t9}),q=n(56940),W=n(8147),K=n(22190),V=n(54061),U=n(65278),G=n(98593),X=n(69448),$=n(32644),Y=n(7084),Q=n(26898),J=n(65954),ee=n(1153);let et=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:l,stack:c=!1,colors:s=Q.s,valueFormatter:u=ee.Cj,startEndOnly:d=!1,showXAxis:f=!0,showYAxis:p=!0,yAxisWidth:h=56,intervalType:m="equidistantPreserveStart",showAnimation:g=!1,animationDuration:v=900,showTooltip:y=!0,showLegend:b=!0,showGridLines:w=!0,showGradient:S=!0,autoMinValue:k=!1,curveType:E="linear",minValue:C,maxValue:O,connectNulls:j=!1,allowDecimals:P=!0,noDataText:N,className:M,onValueChange:I,enableLegendSlider:R=!1,customTooltip:T,rotateLabelX:A,tickGap:_=5}=e,D=(0,r._T)(e,["data","categories","index","stack","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","showAnimation","animationDuration","showTooltip","showLegend","showGridLines","showGradient","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),Z=(f||p)&&(!d||p)?20:0,[F,et]=(0,o.useState)(60),[en,er]=(0,o.useState)(void 0),[eo,ei]=(0,o.useState)(void 0),ea=(0,$.me)(a,s),el=(0,$.i4)(k,C,O),ec=!!I;function es(e){ec&&(e===eo&&!en||(0,$.FB)(n,e)&&en&&en.dataKey===e?(ei(void 0),null==I||I(null)):(ei(e),null==I||I({eventType:"category",categoryClicked:e})),er(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,J.q)("w-full h-80",M)},D),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(H,{data:n,onClick:ec&&(eo||en)?()=>{er(void 0),ei(void 0),null==I||I(null)}:void 0},w?o.createElement(q.q,{className:(0,J.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(z.K,{padding:{left:Z,right:Z},hide:!f,dataKey:l,tick:{transform:"translate(0, 6)"},ticks:d?[n[0][l],n[n.length-1][l]]:void 0,fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),interval:d?"preserveStartEnd":m,tickLine:!1,axisLine:!1,minTickGap:_,angle:null==A?void 0:A.angle,dy:null==A?void 0:A.verticalShift,height:null==A?void 0:A.xAxisHeight}),o.createElement(B.B,{width:h,hide:!p,axisLine:!1,tickLine:!1,type:"number",domain:el,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,J.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:u,allowDecimals:P}),o.createElement(W.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:y?e=>{let{active:t,payload:n,label:r}=e;return T?o.createElement(T,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=ea.get(e.dataKey))&&void 0!==t?t:Y.fr.Gray})}),active:t,label:r}):o.createElement(G.ZP,{active:t,payload:n,label:r,valueFormatter:u,categoryColors:ea})}:o.createElement(o.Fragment,null),position:{y:0}}),b?o.createElement(K.D,{verticalAlign:"top",height:F,content:e=>{let{payload:t}=e;return(0,U.Z)({payload:t},ea,et,eo,ec?e=>es(e):void 0,R)}}):null,a.map(e=>{var t,n;return o.createElement("defs",{key:e},S?o.createElement("linearGradient",{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:Y.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{offset:"5%",stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.15:.4}),o.createElement("stop",{offset:"95%",stopColor:"currentColor",stopOpacity:0})):o.createElement("linearGradient",{className:(0,ee.bM)(null!==(n=ea.get(e))&&void 0!==n?n:Y.fr.Gray,Q.K.text).textColor,id:ea.get(e),x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{stopColor:"currentColor",stopOpacity:en||eo&&eo!==e?.1:.3})))}),a.map(e=>{var t;return o.createElement(L,{className:(0,ee.bM)(null!==(t=ea.get(e))&&void 0!==t?t:Y.fr.Gray,Q.K.text).strokeColor,strokeOpacity:en||eo&&eo!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,dataKey:u}=e;return o.createElement(x.o,{className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",I?"cursor-pointer":"",(0,ee.bM)(null!==(t=ea.get(u))&&void 0!==t?t:Y.fr.Gray,Q.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,onClick:(t,r)=>{r.stopPropagation(),ec&&(e.index===(null==en?void 0:en.index)&&e.dataKey===(null==en?void 0:en.dataKey)||(0,$.FB)(n,e.dataKey)&&eo&&eo===e.dataKey?(ei(void 0),er(void 0),null==I||I(null)):(ei(e.dataKey),er({index:e.index,dataKey:e.dataKey}),null==I||I(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,cx:s,cy:u,dataKey:d,index:f}=t;return(0,$.FB)(n,e)&&!(en||eo&&eo!==e)||(null==en?void 0:en.index)===f&&(null==en?void 0:en.dataKey)===e?o.createElement(x.o,{key:f,cx:s,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,className:(0,J.q)("stroke-tremor-background dark:stroke-dark-tremor-background",I?"cursor-pointer":"",(0,ee.bM)(null!==(r=ea.get(d))&&void 0!==r?r:Y.fr.Gray,Q.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:E,dataKey:e,stroke:"",fill:"url(#".concat(ea.get(e),")"),strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:g,animationDuration:v,stackId:c?"a":void 0,connectNulls:j})}),I?a.map(e=>o.createElement(V.x,{className:(0,J.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:E,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:j,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;es(n)}})):null):o.createElement(X.Z,{noDataText:N})))});et.displayName="AreaChart"},40278:function(e,t,n){"use strict";n.d(t,{Z:function(){return k}});var r=n(5853),o=n(7084),i=n(26898),a=n(65954),l=n(1153),c=n(2265),s=n(47625),u=n(93765),d=n(31699),f=n(97059),p=n(62994),h=n(25311),m=(0,u.z)({chartName:"BarChart",GraphicalChild:d.$,defaultTooltipEventType:"axis",validateTooltipEventTypes:["axis","item"],axisComponents:[{axisType:"xAxis",AxisComp:f.K},{axisType:"yAxis",AxisComp:p.B}],formatAxisMap:h.t9}),g=n(56940),v=n(8147),y=n(22190),b=n(65278),x=n(98593),w=n(69448),S=n(32644);let k=c.forwardRef((e,t)=>{let{data:n=[],categories:u=[],index:h,colors:k=i.s,valueFormatter:E=l.Cj,layout:C="horizontal",stack:O=!1,relative:j=!1,startEndOnly:P=!1,animationDuration:N=900,showAnimation:M=!1,showXAxis:I=!0,showYAxis:R=!0,yAxisWidth:T=56,intervalType:A="equidistantPreserveStart",showTooltip:_=!0,showLegend:D=!0,showGridLines:Z=!0,autoMinValue:L=!1,minValue:z,maxValue:B,allowDecimals:F=!0,noDataText:H,onValueChange:q,enableLegendSlider:W=!1,customTooltip:K,rotateLabelX:V,tickGap:U=5,className:G}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","layout","stack","relative","startEndOnly","animationDuration","showAnimation","showXAxis","showYAxis","yAxisWidth","intervalType","showTooltip","showLegend","showGridLines","autoMinValue","minValue","maxValue","allowDecimals","noDataText","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap","className"]),$=I||R?20:0,[Y,Q]=(0,c.useState)(60),J=(0,S.me)(u,k),[ee,et]=c.useState(void 0),[en,er]=(0,c.useState)(void 0),eo=!!q;function ei(e,t,n){var r,o,i,a;n.stopPropagation(),q&&((0,S.vZ)(ee,Object.assign(Object.assign({},e.payload),{value:e.value}))?(er(void 0),et(void 0),null==q||q(null)):(er(null===(o=null===(r=e.tooltipPayload)||void 0===r?void 0:r[0])||void 0===o?void 0:o.dataKey),et(Object.assign(Object.assign({},e.payload),{value:e.value})),null==q||q(Object.assign({eventType:"bar",categoryClicked:null===(a=null===(i=e.tooltipPayload)||void 0===i?void 0:i[0])||void 0===a?void 0:a.dataKey},e.payload))))}let ea=(0,S.i4)(L,z,B);return c.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-80",G)},X),c.createElement(s.h,{className:"h-full w-full"},(null==n?void 0:n.length)?c.createElement(m,{data:n,stackOffset:O?"sign":j?"expand":"none",layout:"vertical"===C?"vertical":"horizontal",onClick:eo&&(en||ee)?()=>{et(void 0),er(void 0),null==q||q(null)}:void 0},Z?c.createElement(g.q,{className:(0,a.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:"vertical"!==C,vertical:"vertical"===C}):null,"vertical"!==C?c.createElement(f.K,{padding:{left:$,right:$},hide:!I,dataKey:h,interval:P?"preserveStartEnd":A,tick:{transform:"translate(0, 6)"},ticks:P?[n[0][h],n[n.length-1][h]]:void 0,fill:"",stroke:"",className:(0,a.q)("mt-4 text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,angle:null==V?void 0:V.angle,dy:null==V?void 0:V.verticalShift,height:null==V?void 0:V.xAxisHeight,minTickGap:U}):c.createElement(f.K,{hide:!I,type:"number",tick:{transform:"translate(-3, 0)"},domain:ea,fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,tickFormatter:E,minTickGap:U,allowDecimals:F,angle:null==V?void 0:V.angle,dy:null==V?void 0:V.verticalShift,height:null==V?void 0:V.xAxisHeight}),"vertical"!==C?c.createElement(p.B,{width:T,hide:!R,axisLine:!1,tickLine:!1,type:"number",domain:ea,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:j?e=>"".concat((100*e).toString()," %"):E,allowDecimals:F}):c.createElement(p.B,{width:T,hide:!R,dataKey:h,axisLine:!1,tickLine:!1,ticks:P?[n[0][h],n[n.length-1][h]]:void 0,type:"category",interval:"preserveStartEnd",tick:{transform:"translate(0, 6)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content")}),c.createElement(v.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{fill:"#d1d5db",opacity:"0.15"},content:_?e=>{let{active:t,payload:n,label:r}=e;return K?c.createElement(K,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=J.get(e.dataKey))&&void 0!==t?t:o.fr.Gray})}),active:t,label:r}):c.createElement(x.ZP,{active:t,payload:n,label:r,valueFormatter:E,categoryColors:J})}:c.createElement(c.Fragment,null),position:{y:0}}),D?c.createElement(y.D,{verticalAlign:"top",height:Y,content:e=>{let{payload:t}=e;return(0,b.Z)({payload:t},J,Q,en,eo?e=>{eo&&(e!==en||ee?(er(e),null==q||q({eventType:"category",categoryClicked:e})):(er(void 0),null==q||q(null)),et(void 0))}:void 0,W)}}):null,u.map(e=>{var t;return c.createElement(d.$,{className:(0,a.q)((0,l.bM)(null!==(t=J.get(e))&&void 0!==t?t:o.fr.Gray,i.K.background).fillColor,q?"cursor-pointer":""),key:e,name:e,type:"linear",stackId:O||j?"a":void 0,dataKey:e,fill:"",isAnimationActive:M,animationDuration:N,shape:e=>((e,t,n,r)=>{let{fillOpacity:o,name:i,payload:a,value:l}=e,{x:s,width:u,y:d,height:f}=e;return"horizontal"===r&&f<0?(d+=f,f=Math.abs(f)):"vertical"===r&&u<0&&(s+=u,u=Math.abs(u)),c.createElement("rect",{x:s,y:d,width:u,height:f,opacity:t||n&&n!==i?(0,S.vZ)(t,Object.assign(Object.assign({},a),{value:l}))?o:.3:o})})(e,ee,en,C),onClick:ei})})):c.createElement(w.Z,{noDataText:H})))});k.displayName="BarChart"},14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return ez}});var r=n(5853),o=n(7084),i=n(26898),a=n(65954),l=n(1153),c=n(2265),s=n(60474),u=n(47625),d=n(93765),f=n(86757),p=n.n(f),h=n(9841),m=n(81889),g=n(61994),v=n(82944),y=["points","className","baseLinePoints","connectNulls"];function b(){return(b=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){S(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),S(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},E=function(e,t){var n=k(e);t&&(n=[n.reduce(function(e,t){return[].concat(x(e),x(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},C=function(e,t,n){var r=E(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(E(t.reverse(),n).slice(1))},O=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,y);if(!t||!t.length)return null;var a=(0,g.Z)("recharts-polygon",n);if(r&&r.length){var l=i.stroke&&"none"!==i.stroke,s=C(t,r,o);return c.createElement("g",{className:a},c.createElement("path",b({},(0,v.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),l?c.createElement("path",b({},(0,v.L6)(i,!0),{fill:"none",d:E(t,o)})):null,l?c.createElement("path",b({},(0,v.L6)(i,!0),{fill:"none",d:E(r,o)})):null)}var u=E(t,o);return c.createElement("path",b({},(0,v.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},j=n(58811),P=n(41637),N=n(39206);function M(e){return(M="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function I(){return(I=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=T(T({},(0,v.L6)(this.props,!1)),{},{fill:"none"},(0,v.L6)(o,!1));if("circle"===i)return c.createElement(m.o,I({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var l=this.props.ticks.map(function(e){return(0,N.op)(t,n,r,e.coordinate)});return c.createElement(O,I({className:"recharts-polar-angle-axis-line"},a,{points:l}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,r=t.tick,o=t.tickLine,a=t.tickFormatter,l=t.stroke,s=(0,v.L6)(this.props,!1),u=(0,v.L6)(r,!1),d=T(T({},s),{},{fill:"none"},(0,v.L6)(o,!1)),f=n.map(function(t,n){var f=e.getTickLineCoord(t),p=T(T(T({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:l},u),{},{index:n,payload:t,x:f.x2,y:f.y2});return c.createElement(h.m,I({className:"recharts-polar-angle-axis-tick",key:"tick-".concat(t.coordinate)},(0,P.bw)(e.props,t,n)),o&&c.createElement("line",I({className:"recharts-polar-angle-axis-tick-line"},d,f)),r&&i.renderTickItem(r,p,a?a(t.value,n):t.value))});return c.createElement(h.m,{className:"recharts-polar-angle-axis-ticks"},f)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?c.createElement(h.m,{className:"recharts-polar-angle-axis"},r&&this.renderAxisLine(),this.renderTicks()):null}}],r=[{key:"renderTickItem",value:function(e,t,n){return c.isValidElement(e)?c.cloneElement(e,t):p()(e)?e(t):c.createElement(j.x,I({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],n&&A(i.prototype,n),r&&A(i,r),Object.defineProperty(i,"prototype",{writable:!1}),i}(c.PureComponent);Z(B,"displayName","PolarAngleAxis"),Z(B,"axisType","angleAxis"),Z(B,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var F=n(35802),H=n.n(F),q=n(37891),W=n.n(q),K=n(26680),V=["cx","cy","angle","ticks","axisLine"],U=["ticks","tick","angle","tickFormatter","stroke"];function G(e){return(G="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function X(){return(X=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function J(e,t){for(var n=0;n0?el()(e,"paddingAngle",0):0;if(n){var l=(0,eg.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),c=ek(ek({},e),{},{startAngle:i+a,endAngle:i+l(r)+a});o.push(c),i=c.endAngle}else{var s=e.endAngle,d=e.startAngle,f=(0,eg.k4)(0,s-d)(r),p=ek(ek({},e),{},{startAngle:i+a,endAngle:i+f+a});o.push(p),i=p.endAngle}}),c.createElement(h.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!es()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,l=t.cy,s=t.innerRadius,u=t.outerRadius,d=t.isAnimationActive,f=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eg.hj)(a)||!(0,eg.hj)(l)||!(0,eg.hj)(s)||!(0,eg.hj)(u))return null;var p=(0,g.Z)("recharts-pie",o);return c.createElement(h.m,{tabIndex:this.props.rootTabIndex,className:p,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),K._.renderCallByParent(this.props,null,!1),(!d||f)&&ep.e.renderCallByParent(this.props,r,!1))}}],r=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?x:x-1)*u,S=i.reduce(function(e,t){var n=(0,ev.F$)(t,b,0);return e+((0,eg.hj)(n)?n:0)},0);return S>0&&(t=i.map(function(e,t){var r,o=(0,ev.F$)(e,b,0),i=(0,ev.F$)(e,f,t),a=((0,eg.hj)(o)?o:0)/S,s=(r=t?n.endAngle+(0,eg.uY)(v)*u*(0!==o?1:0):c)+(0,eg.uY)(v)*((0!==o?m:0)+a*w),d=(r+s)/2,p=(g.innerRadius+g.outerRadius)/2,y=[{name:i,value:o,payload:e,dataKey:b,type:h}],x=(0,N.op)(g.cx,g.cy,p,d);return n=ek(ek(ek({percent:a,cornerRadius:l,name:i,tooltipPayload:y,midAngle:d,middleRadius:p,tooltipPosition:x},e),g),{},{value:(0,ev.F$)(e,b),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,eg.uY)(v)*u})})),ek(ek({},g),{},{sectors:t,data:i})});var eI=(0,d.z)({chartName:"PieChart",GraphicalChild:eM,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:B},{axisType:"radiusAxis",AxisComp:eo}],formatAxisMap:N.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eR=n(8147),eT=n(69448),eA=n(98593);let e_=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return c.createElement(eA.$B,null,c.createElement("div",{className:(0,a.q)("px-4 py-2")},c.createElement(eA.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eD=(e,t)=>e.map((e,n)=>{let r=ne||t((0,l.vP)(n.map(e=>e[r]))),eL=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l}=e;return c.createElement("g",null,c.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l,fill:"",opacity:.3,style:{outline:"none"}}))},ez=c.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:d="name",colors:f=i.s,variant:p="donut",valueFormatter:h=l.Cj,label:m,showLabel:g=!0,animationDuration:v=900,showAnimation:y=!1,showTooltip:b=!0,noDataText:x,onValueChange:w,customTooltip:S,className:k}=e,E=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),C="donut"==p,O=eZ(m,h,n,s),[j,P]=c.useState(void 0),N=!!w;return(0,c.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[j]),c.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",k)},E),c.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?c.createElement(eI,{onClick:N&&j?()=>{P(void 0),null==w||w(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},g&&C?c.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},O):null,c.createElement(eM,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",w?"cursor-pointer":"cursor-default"),data:eD(n,f),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:C?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:d,isAnimationActive:y,animationDuration:v,onClick:function(e,t,n){n.stopPropagation(),N&&(j===t?(P(void 0),null==w||w(null)):(P(t),null==w||w(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:j,inactiveShape:eL,style:{outline:"none"}}),c.createElement(eR.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:b?e=>{var t;let{active:n,payload:r}=e;return S?c.createElement(S,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):c.createElement(e_,{active:n,payload:r,valueFormatter:h})}:c.createElement(c.Fragment,null)})):c.createElement(eT.Z,{noDataText:x})))});ez.displayName="DonutChart"},59664:function(e,t,n){"use strict";n.d(t,{Z:function(){return E}});var r=n(5853),o=n(2265),i=n(47625),a=n(93765),l=n(54061),c=n(97059),s=n(62994),u=n(25311),d=(0,a.z)({chartName:"LineChart",GraphicalChild:l.x,axisComponents:[{axisType:"xAxis",AxisComp:c.K},{axisType:"yAxis",AxisComp:s.B}],formatAxisMap:u.t9}),f=n(56940),p=n(8147),h=n(22190),m=n(81889),g=n(65278),v=n(98593),y=n(69448),b=n(32644),x=n(7084),w=n(26898),S=n(65954),k=n(1153);let E=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:u,colors:E=w.s,valueFormatter:C=k.Cj,startEndOnly:O=!1,showXAxis:j=!0,showYAxis:P=!0,yAxisWidth:N=56,intervalType:M="equidistantPreserveStart",animationDuration:I=900,showAnimation:R=!1,showTooltip:T=!0,showLegend:A=!0,showGridLines:_=!0,autoMinValue:D=!1,curveType:Z="linear",minValue:L,maxValue:z,connectNulls:B=!1,allowDecimals:F=!0,noDataText:H,className:q,onValueChange:W,enableLegendSlider:K=!1,customTooltip:V,rotateLabelX:U,tickGap:G=5}=e,X=(0,r._T)(e,["data","categories","index","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","animationDuration","showAnimation","showTooltip","showLegend","showGridLines","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","tickGap"]),$=j||P?20:0,[Y,Q]=(0,o.useState)(60),[J,ee]=(0,o.useState)(void 0),[et,en]=(0,o.useState)(void 0),er=(0,b.me)(a,E),eo=(0,b.i4)(D,L,z),ei=!!W;function ea(e){ei&&(e===et&&!J||(0,b.FB)(n,e)&&J&&J.dataKey===e?(en(void 0),null==W||W(null)):(en(e),null==W||W({eventType:"category",categoryClicked:e})),ee(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,S.q)("w-full h-80",q)},X),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(d,{data:n,onClick:ei&&(et||J)?()=>{ee(void 0),en(void 0),null==W||W(null)}:void 0},_?o.createElement(f.q,{className:(0,S.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(c.K,{padding:{left:$,right:$},hide:!j,dataKey:u,interval:O?"preserveStartEnd":M,tick:{transform:"translate(0, 6)"},ticks:O?[n[0][u],n[n.length-1][u]]:void 0,fill:"",stroke:"",className:(0,S.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,minTickGap:G,angle:null==U?void 0:U.angle,dy:null==U?void 0:U.verticalShift,height:null==U?void 0:U.xAxisHeight}),o.createElement(s.B,{width:N,hide:!P,axisLine:!1,tickLine:!1,type:"number",domain:eo,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,S.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:C,allowDecimals:F}),o.createElement(p.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:T?e=>{let{active:t,payload:n,label:r}=e;return V?o.createElement(V,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=er.get(e.dataKey))&&void 0!==t?t:x.fr.Gray})}),active:t,label:r}):o.createElement(v.ZP,{active:t,payload:n,label:r,valueFormatter:C,categoryColors:er})}:o.createElement(o.Fragment,null),position:{y:0}}),A?o.createElement(h.D,{verticalAlign:"top",height:Y,content:e=>{let{payload:t}=e;return(0,g.Z)({payload:t},er,Q,et,ei?e=>ea(e):void 0,K)}}):null,a.map(e=>{var t;return o.createElement(l.x,{className:(0,S.q)((0,k.bM)(null!==(t=er.get(e))&&void 0!==t?t:x.fr.Gray,w.K.text).strokeColor),strokeOpacity:J||et&&et!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,dataKey:u}=e;return o.createElement(m.o,{className:(0,S.q)("stroke-tremor-background dark:stroke-dark-tremor-background",W?"cursor-pointer":"",(0,k.bM)(null!==(t=er.get(u))&&void 0!==t?t:x.fr.Gray,w.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,onClick:(t,r)=>{r.stopPropagation(),ei&&(e.index===(null==J?void 0:J.index)&&e.dataKey===(null==J?void 0:J.dataKey)||(0,b.FB)(n,e.dataKey)&&et&&et===e.dataKey?(en(void 0),ee(void 0),null==W||W(null)):(en(e.dataKey),ee({index:e.index,dataKey:e.dataKey}),null==W||W(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,cx:s,cy:u,dataKey:d,index:f}=t;return(0,b.FB)(n,e)&&!(J||et&&et!==e)||(null==J?void 0:J.index)===f&&(null==J?void 0:J.dataKey)===e?o.createElement(m.o,{key:f,cx:s,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,className:(0,S.q)("stroke-tremor-background dark:stroke-dark-tremor-background",W?"cursor-pointer":"",(0,k.bM)(null!==(r=er.get(d))&&void 0!==r?r:x.fr.Gray,w.K.text).fillColor)}):o.createElement(o.Fragment,{key:f})},key:e,name:e,type:Z,dataKey:e,stroke:"",strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:R,animationDuration:I,connectNulls:B})}),W?a.map(e=>o.createElement(l.x,{className:(0,S.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:Z,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:B,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;ea(n)}})):null):o.createElement(y.Z,{noDataText:H})))});E.displayName="LineChart"},65278:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r=n(2265);let o=(e,t)=>{let[n,o]=(0,r.useState)(t);(0,r.useEffect)(()=>{let t=()=>{o(window.innerWidth),e()};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[e,n])};var i=n(5853),a=n(26898),l=n(65954),c=n(1153);let s=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M8 12L14 6V18L8 12Z"}))},u=e=>{var t=(0,i._T)(e,[]);return r.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),r.createElement("path",{d:"M16 12L10 18V6L16 12Z"}))},d=(0,c.fn)("Legend"),f=e=>{let{name:t,color:n,onClick:o,activeLegend:i}=e,s=!!o;return r.createElement("li",{className:(0,l.q)(d("legendItem"),"group inline-flex items-center px-2 py-0.5 rounded-tremor-small transition whitespace-nowrap",s?"cursor-pointer":"cursor-default","text-tremor-content",s?"hover:bg-tremor-background-subtle":"","dark:text-dark-tremor-content",s?"dark:hover:bg-dark-tremor-background-subtle":""),onClick:e=>{e.stopPropagation(),null==o||o(t,n)}},r.createElement("svg",{className:(0,l.q)("flex-none h-2 w-2 mr-1.5",(0,c.bM)(n,a.K.text).textColor,i&&i!==t?"opacity-40":"opacity-100"),fill:"currentColor",viewBox:"0 0 8 8"},r.createElement("circle",{cx:4,cy:4,r:4})),r.createElement("p",{className:(0,l.q)("whitespace-nowrap truncate text-tremor-default","text-tremor-content",s?"group-hover:text-tremor-content-emphasis":"","dark:text-dark-tremor-content",i&&i!==t?"opacity-40":"opacity-100",s?"dark:group-hover:text-dark-tremor-content-emphasis":"")},t))},p=e=>{let{icon:t,onClick:n,disabled:o}=e,[i,a]=r.useState(!1),c=r.useRef(null);return r.useEffect(()=>(i?c.current=setInterval(()=>{null==n||n()},300):clearInterval(c.current),()=>clearInterval(c.current)),[i,n]),(0,r.useEffect)(()=>{o&&(clearInterval(c.current),a(!1))},[o]),r.createElement("button",{type:"button",className:(0,l.q)(d("legendSliderButton"),"w-5 group inline-flex items-center truncate rounded-tremor-small transition",o?"cursor-not-allowed":"cursor-pointer",o?"text-tremor-content-subtle":"text-tremor-content hover:text-tremor-content-emphasis hover:bg-tremor-background-subtle",o?"dark:text-dark-tremor-subtle":"dark:text-dark-tremor dark:hover:text-tremor-content-emphasis dark:hover:bg-dark-tremor-background-subtle"),disabled:o,onClick:e=>{e.stopPropagation(),null==n||n()},onMouseDown:e=>{e.stopPropagation(),a(!0)},onMouseUp:e=>{e.stopPropagation(),a(!1)}},r.createElement(t,{className:"w-full"}))},h=r.forwardRef((e,t)=>{var n,o;let{categories:c,colors:h=a.s,className:m,onClickLegendItem:g,activeLegend:v,enableLegendSlider:y=!1}=e,b=(0,i._T)(e,["categories","colors","className","onClickLegendItem","activeLegend","enableLegendSlider"]),x=r.useRef(null),[w,S]=r.useState(null),[k,E]=r.useState(null),C=r.useRef(null),O=(0,r.useCallback)(()=>{let e=null==x?void 0:x.current;e&&S({left:e.scrollLeft>0,right:e.scrollWidth-e.clientWidth>e.scrollLeft})},[S]),j=(0,r.useCallback)(e=>{var t;let n=null==x?void 0:x.current,r=null!==(t=null==n?void 0:n.clientWidth)&&void 0!==t?t:0;n&&y&&(n.scrollTo({left:"left"===e?n.scrollLeft-r:n.scrollLeft+r,behavior:"smooth"}),setTimeout(()=>{O()},400))},[y,O]);r.useEffect(()=>{let e=e=>{"ArrowLeft"===e?j("left"):"ArrowRight"===e&&j("right")};return k?(e(k),C.current=setInterval(()=>{e(k)},300)):clearInterval(C.current),()=>clearInterval(C.current)},[k,j]);let P=e=>{e.stopPropagation(),"ArrowLeft"!==e.key&&"ArrowRight"!==e.key||(e.preventDefault(),E(e.key))},N=e=>{e.stopPropagation(),E(null)};return r.useEffect(()=>{let e=null==x?void 0:x.current;return y&&(O(),null==e||e.addEventListener("keydown",P),null==e||e.addEventListener("keyup",N)),()=>{null==e||e.removeEventListener("keydown",P),null==e||e.removeEventListener("keyup",N)}},[O,y]),r.createElement("ol",Object.assign({ref:t,className:(0,l.q)(d("root"),"relative overflow-hidden",m)},b),r.createElement("div",{ref:x,tabIndex:0,className:(0,l.q)("h-full flex",y?(null==w?void 0:w.right)||(null==w?void 0:w.left)?"pl-4 pr-12 items-center overflow-auto snap-mandatory [&::-webkit-scrollbar]:hidden [scrollbar-width:none]":"":"flex-wrap")},c.map((e,t)=>r.createElement(f,{key:"item-".concat(t),name:e,color:h[t],onClick:g,activeLegend:v}))),y&&((null==w?void 0:w.right)||(null==w?void 0:w.left))?r.createElement(r.Fragment,null,r.createElement("div",{className:(0,l.q)("from-tremor-background","dark:from-dark-tremor-background","absolute top-0 bottom-0 left-0 w-4 bg-gradient-to-r to-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("to-tremor-background","dark:to-dark-tremor-background","absolute top-0 bottom-0 right-10 w-4 bg-gradient-to-r from-transparent pointer-events-none")}),r.createElement("div",{className:(0,l.q)("bg-tremor-background","dark:bg-dark-tremor-background","absolute flex top-0 pr-1 bottom-0 right-0 items-center justify-center h-full")},r.createElement(p,{icon:s,onClick:()=>{E(null),j("left")},disabled:!(null==w?void 0:w.left)}),r.createElement(p,{icon:u,onClick:()=>{E(null),j("right")},disabled:!(null==w?void 0:w.right)}))):null)});h.displayName="Legend";let m=(e,t,n,i,a,l)=>{let{payload:c}=e,s=(0,r.useRef)(null);o(()=>{var e,t;n((t=null===(e=s.current)||void 0===e?void 0:e.clientHeight)?Number(t)+20:60)});let u=c.filter(e=>"none"!==e.type);return r.createElement("div",{ref:s,className:"flex items-center justify-end"},r.createElement(h,{categories:u.map(e=>e.value),colors:u.map(e=>t.get(e.value)),onClickLegendItem:a,activeLegend:i,enableLegendSlider:l}))}},98593:function(e,t,n){"use strict";n.d(t,{$B:function(){return c},ZP:function(){return u},zX:function(){return s}});var r=n(2265),o=n(7084),i=n(26898),a=n(65954),l=n(1153);let c=e=>{let{children:t}=e;return r.createElement("div",{className:(0,a.q)("rounded-tremor-default text-tremor-default border","bg-tremor-background shadow-tremor-dropdown border-tremor-border","dark:bg-dark-tremor-background dark:shadow-dark-tremor-dropdown dark:border-dark-tremor-border")},t)},s=e=>{let{value:t,name:n,color:o}=e;return r.createElement("div",{className:"flex items-center justify-between space-x-8"},r.createElement("div",{className:"flex items-center space-x-2"},r.createElement("span",{className:(0,a.q)("shrink-0 rounded-tremor-full border-2 h-3 w-3","border-tremor-background shadow-tremor-card","dark:border-dark-tremor-background dark:shadow-dark-tremor-card",(0,l.bM)(o,i.K.background).bgColor)}),r.createElement("p",{className:(0,a.q)("text-right whitespace-nowrap","text-tremor-content","dark:text-dark-tremor-content")},n)),r.createElement("p",{className:(0,a.q)("font-medium tabular-nums text-right whitespace-nowrap","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},t))},u=e=>{let{active:t,payload:n,label:i,categoryColors:l,valueFormatter:u}=e;if(t&&n){let e=n.filter(e=>"none"!==e.type);return r.createElement(c,null,r.createElement("div",{className:(0,a.q)("border-tremor-border border-b px-4 py-2","dark:border-dark-tremor-border")},r.createElement("p",{className:(0,a.q)("font-medium","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},i)),r.createElement("div",{className:(0,a.q)("px-4 py-2 space-y-1")},e.map((e,t)=>{var n;let{value:i,name:a}=e;return r.createElement(s,{key:"id-".concat(t),value:u(i),name:a,color:null!==(n=l.get(a))&&void 0!==n?n:o.fr.Blue})})))}return null}},69448:function(e,t,n){"use strict";n.d(t,{Z:function(){return f}});var r=n(65954),o=n(2265),i=n(5853);let a=(0,n(1153).fn)("Flex"),l={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"},c={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"},s={row:"flex-row",col:"flex-col","row-reverse":"flex-row-reverse","col-reverse":"flex-col-reverse"},u=o.forwardRef((e,t)=>{let{flexDirection:n="row",justifyContent:u="between",alignItems:d="center",children:f,className:p}=e,h=(0,i._T)(e,["flexDirection","justifyContent","alignItems","children","className"]);return o.createElement("div",Object.assign({ref:t,className:(0,r.q)(a("root"),"flex w-full",s[n],l[u],c[d],p)},h),f)});u.displayName="Flex";var d=n(84264);let f=e=>{let{noDataText:t="No data"}=e;return o.createElement(u,{alignItems:"center",justifyContent:"center",className:(0,r.q)("w-full h-full border border-dashed rounded-tremor-default","border-tremor-border","dark:border-dark-tremor-border")},o.createElement(d.Z,{className:(0,r.q)("text-tremor-content","dark:text-dark-tremor-content")},t))}},32644:function(e,t,n){"use strict";n.d(t,{FB:function(){return i},i4:function(){return o},me:function(){return r},vZ:function(){return function e(t,n){if(t===n)return!0;if("object"!=typeof t||"object"!=typeof n||null===t||null===n)return!1;let r=Object.keys(t),o=Object.keys(n);if(r.length!==o.length)return!1;for(let i of r)if(!o.includes(i)||!e(t[i],n[i]))return!1;return!0}}});let r=(e,t)=>{let n=new Map;return e.forEach((e,r)=>{n.set(e,t[r])}),n},o=(e,t,n)=>[e?"auto":null!=t?t:0,null!=n?n:"auto"];function i(e,t){let n=[];for(let r of e)if(Object.prototype.hasOwnProperty.call(r,t)&&(n.push(r[t]),n.length>1))return!1;return!0}},41649:function(e,t,n){"use strict";n.d(t,{Z:function(){return p}});var r=n(5853),o=n(2265),i=n(1526),a=n(7084),l=n(26898),c=n(65954),s=n(1153);let u={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},d={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},f=(0,s.fn)("Badge"),p=o.forwardRef((e,t)=>{let{color:n,icon:p,size:h=a.u8.SM,tooltip:m,className:g,children:v}=e,y=(0,r._T)(e,["color","icon","size","tooltip","className","children"]),b=p||null,{tooltipProps:x,getReferenceProps:w}=(0,i.l)();return o.createElement("span",Object.assign({ref:(0,s.lq)([t,x.refs.setReference]),className:(0,c.q)(f("root"),"w-max flex-shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-full",n?(0,c.q)((0,s.bM)(n,l.K.background).bgColor,(0,s.bM)(n,l.K.text).textColor,"bg-opacity-20 dark:bg-opacity-25"):(0,c.q)("bg-tremor-brand-muted text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted dark:text-dark-tremor-brand-emphasis"),u[h].paddingX,u[h].paddingY,u[h].fontSize,g)},w,y),o.createElement(i.Z,Object.assign({text:m},x)),b?o.createElement(b,{className:(0,c.q)(f("icon"),"shrink-0 -ml-1 mr-1.5",d[h].height,d[h].width)}):null,o.createElement("p",{className:(0,c.q)(f("text"),"text-sm whitespace-nowrap")},v))});p.displayName="Badge"},47323:function(e,t,n){"use strict";n.d(t,{Z:function(){return m}});var r=n(5853),o=n(2265),i=n(1526),a=n(7084),l=n(65954),c=n(1153),s=n(26898);let u={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},d={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},f={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},p=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,c.bM)(t,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,l.q)((0,c.bM)(t,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,c.bM)(t,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,l.q)((0,c.bM)(t,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},h=(0,c.fn)("Icon"),m=o.forwardRef((e,t)=>{let{icon:n,variant:s="simple",tooltip:m,size:g=a.u8.SM,color:v,className:y}=e,b=(0,r._T)(e,["icon","variant","tooltip","size","color","className"]),x=p(s,v),{tooltipProps:w,getReferenceProps:S}=(0,i.l)();return o.createElement("span",Object.assign({ref:(0,c.lq)([t,w.refs.setReference]),className:(0,l.q)(h("root"),"inline-flex flex-shrink-0 items-center",x.bgColor,x.textColor,x.borderColor,x.ringColor,f[s].rounded,f[s].border,f[s].shadow,f[s].ring,u[g].paddingX,u[g].paddingY,y)},S,b),o.createElement(i.Z,Object.assign({text:m},w)),o.createElement(n,{className:(0,l.q)(h("icon"),"shrink-0",d[g].height,d[g].width)}))});m.displayName="Icon"},53003:function(e,t,n){"use strict";let r,o,i;n.d(t,{Z:function(){return nF}});var a,l,c,s,u=n(5853),d=n(2265),f=n(54887),p=n(13323),h=n(64518),m=n(96822),g=n(40293);function v(){for(var e=arguments.length,t=Array(e),n=0;n(0,g.r)(...t),[...t])}var y=n(72238),b=n(93689);let x=(0,d.createContext)(!1);var w=n(61424),S=n(27847);let k=d.Fragment,E=d.Fragment,C=(0,d.createContext)(null),O=(0,d.createContext)(null);Object.assign((0,S.yV)(function(e,t){var n;let r,o,i=(0,d.useRef)(null),a=(0,b.T)((0,b.h)(e=>{i.current=e}),t),l=v(i),c=function(e){let t=(0,d.useContext)(x),n=(0,d.useContext)(C),r=v(e),[o,i]=(0,d.useState)(()=>{if(!t&&null!==n||w.O.isServer)return null;let e=null==r?void 0:r.getElementById("headlessui-portal-root");if(e)return e;if(null===r)return null;let o=r.createElement("div");return o.setAttribute("id","headlessui-portal-root"),r.body.appendChild(o)});return(0,d.useEffect)(()=>{null!==o&&(null!=r&&r.body.contains(o)||null==r||r.body.appendChild(o))},[o,r]),(0,d.useEffect)(()=>{t||null!==n&&i(n.current)},[n,i,t]),o}(i),[s]=(0,d.useState)(()=>{var e;return w.O.isServer?null:null!=(e=null==l?void 0:l.createElement("div"))?e:null}),u=(0,d.useContext)(O),g=(0,y.H)();return(0,h.e)(()=>{!c||!s||c.contains(s)||(s.setAttribute("data-headlessui-portal",""),c.appendChild(s))},[c,s]),(0,h.e)(()=>{if(s&&u)return u.register(s)},[u,s]),n=()=>{var e;c&&s&&(s instanceof Node&&c.contains(s)&&c.removeChild(s),c.childNodes.length<=0&&(null==(e=c.parentElement)||e.removeChild(c)))},r=(0,p.z)(n),o=(0,d.useRef)(!1),(0,d.useEffect)(()=>(o.current=!1,()=>{o.current=!0,(0,m.Y)(()=>{o.current&&r()})}),[r]),g&&c&&s?(0,f.createPortal)((0,S.sY)({ourProps:{ref:a},theirProps:e,defaultTag:k,name:"Portal"}),s):null}),{Group:(0,S.yV)(function(e,t){let{target:n,...r}=e,o={ref:(0,b.T)(t)};return d.createElement(C.Provider,{value:n},(0,S.sY)({ourProps:o,theirProps:r,defaultTag:E,name:"Popover.Group"}))})});var j=n(31948),P=n(17684),N=n(98505),M=n(80004),I=n(38198),R=n(3141),T=((r=T||{})[r.Forwards=0]="Forwards",r[r.Backwards=1]="Backwards",r);function A(){let e=(0,d.useRef)(0);return(0,R.s)("keydown",t=>{"Tab"===t.key&&(e.current=t.shiftKey?1:0)},!0),e}var _=n(37863),D=n(47634),Z=n(37105),L=n(24536),z=n(37388),B=((o=B||{})[o.Open=0]="Open",o[o.Closed=1]="Closed",o),F=((i=F||{})[i.TogglePopover=0]="TogglePopover",i[i.ClosePopover=1]="ClosePopover",i[i.SetButton=2]="SetButton",i[i.SetButtonId=3]="SetButtonId",i[i.SetPanel=4]="SetPanel",i[i.SetPanelId=5]="SetPanelId",i);let H={0:e=>{let t={...e,popoverState:(0,L.E)(e.popoverState,{0:1,1:0})};return 0===t.popoverState&&(t.__demoMode=!1),t},1:e=>1===e.popoverState?e:{...e,popoverState:1},2:(e,t)=>e.button===t.button?e:{...e,button:t.button},3:(e,t)=>e.buttonId===t.buttonId?e:{...e,buttonId:t.buttonId},4:(e,t)=>e.panel===t.panel?e:{...e,panel:t.panel},5:(e,t)=>e.panelId===t.panelId?e:{...e,panelId:t.panelId}},q=(0,d.createContext)(null);function W(e){let t=(0,d.useContext)(q);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,W),t}return t}q.displayName="PopoverContext";let K=(0,d.createContext)(null);function V(e){let t=(0,d.useContext)(K);if(null===t){let t=Error("<".concat(e," /> is missing a parent component."));throw Error.captureStackTrace&&Error.captureStackTrace(t,V),t}return t}K.displayName="PopoverAPIContext";let U=(0,d.createContext)(null);function G(){return(0,d.useContext)(U)}U.displayName="PopoverGroupContext";let X=(0,d.createContext)(null);function $(e,t){return(0,L.E)(t.type,H,e,t)}X.displayName="PopoverPanelContext";let Y=S.AN.RenderStrategy|S.AN.Static,Q=S.AN.RenderStrategy|S.AN.Static,J=Object.assign((0,S.yV)(function(e,t){var n,r,o,i;let a,l,c,s,u,f;let{__demoMode:h=!1,...m}=e,g=(0,d.useRef)(null),y=(0,b.T)(t,(0,b.h)(e=>{g.current=e})),x=(0,d.useRef)([]),w=(0,d.useReducer)($,{__demoMode:h,popoverState:h?0:1,buttons:x,button:null,buttonId:null,panel:null,panelId:null,beforePanelSentinel:(0,d.createRef)(),afterPanelSentinel:(0,d.createRef)()}),[{popoverState:k,button:E,buttonId:C,panel:P,panelId:M,beforePanelSentinel:R,afterPanelSentinel:T},A]=w,D=v(null!=(n=g.current)?n:E),z=(0,d.useMemo)(()=>{if(!E||!P)return!1;for(let e of document.querySelectorAll("body > *"))if(Number(null==e?void 0:e.contains(E))^Number(null==e?void 0:e.contains(P)))return!0;let e=(0,Z.GO)(),t=e.indexOf(E),n=(t+e.length-1)%e.length,r=(t+1)%e.length,o=e[n],i=e[r];return!P.contains(o)&&!P.contains(i)},[E,P]),B=(0,j.E)(C),F=(0,j.E)(M),H=(0,d.useMemo)(()=>({buttonId:B,panelId:F,close:()=>A({type:1})}),[B,F,A]),W=G(),V=null==W?void 0:W.registerPopover,U=(0,p.z)(()=>{var e;return null!=(e=null==W?void 0:W.isFocusWithinPopoverGroup())?e:(null==D?void 0:D.activeElement)&&((null==E?void 0:E.contains(D.activeElement))||(null==P?void 0:P.contains(D.activeElement)))});(0,d.useEffect)(()=>null==V?void 0:V(H),[V,H]);let[Y,Q]=(a=(0,d.useContext)(O),l=(0,d.useRef)([]),c=(0,p.z)(e=>(l.current.push(e),a&&a.register(e),()=>s(e))),s=(0,p.z)(e=>{let t=l.current.indexOf(e);-1!==t&&l.current.splice(t,1),a&&a.unregister(e)}),u=(0,d.useMemo)(()=>({register:c,unregister:s,portals:l}),[c,s,l]),[l,(0,d.useMemo)(()=>function(e){let{children:t}=e;return d.createElement(O.Provider,{value:u},t)},[u])]),J=function(){var e;let{defaultContainers:t=[],portals:n,mainTreeNodeRef:r}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=(0,d.useRef)(null!=(e=null==r?void 0:r.current)?e:null),i=v(o),a=(0,p.z)(()=>{var e,r,a;let l=[];for(let e of t)null!==e&&(e instanceof HTMLElement?l.push(e):"current"in e&&e.current instanceof HTMLElement&&l.push(e.current));if(null!=n&&n.current)for(let e of n.current)l.push(e);for(let t of null!=(e=null==i?void 0:i.querySelectorAll("html > *, body > *"))?e:[])t!==document.body&&t!==document.head&&t instanceof HTMLElement&&"headlessui-portal-root"!==t.id&&(t.contains(o.current)||t.contains(null==(a=null==(r=o.current)?void 0:r.getRootNode())?void 0:a.host)||l.some(e=>t.contains(e))||l.push(t));return l});return{resolveContainers:a,contains:(0,p.z)(e=>a().some(t=>t.contains(e))),mainTreeNodeRef:o,MainTreeNode:(0,d.useMemo)(()=>function(){return null!=r?null:d.createElement(I._,{features:I.A.Hidden,ref:o})},[o,r])}}({mainTreeNodeRef:null==W?void 0:W.mainTreeNodeRef,portals:Y,defaultContainers:[E,P]});r=null==D?void 0:D.defaultView,o="focus",i=e=>{var t,n,r,o;e.target!==window&&e.target instanceof HTMLElement&&0===k&&(U()||E&&P&&(J.contains(e.target)||null!=(n=null==(t=R.current)?void 0:t.contains)&&n.call(t,e.target)||null!=(o=null==(r=T.current)?void 0:r.contains)&&o.call(r,e.target)||A({type:1})))},f=(0,j.E)(i),(0,d.useEffect)(()=>{function e(e){f.current(e)}return(r=null!=r?r:window).addEventListener(o,e,!0),()=>r.removeEventListener(o,e,!0)},[r,o,!0]),(0,N.O)(J.resolveContainers,(e,t)=>{A({type:1}),(0,Z.sP)(t,Z.tJ.Loose)||(e.preventDefault(),null==E||E.focus())},0===k);let ee=(0,p.z)(e=>{A({type:1});let t=e?e instanceof HTMLElement?e:"current"in e&&e.current instanceof HTMLElement?e.current:E:E;null==t||t.focus()}),et=(0,d.useMemo)(()=>({close:ee,isPortalled:z}),[ee,z]),en=(0,d.useMemo)(()=>({open:0===k,close:ee}),[k,ee]);return d.createElement(X.Provider,{value:null},d.createElement(q.Provider,{value:w},d.createElement(K.Provider,{value:et},d.createElement(_.up,{value:(0,L.E)(k,{0:_.ZM.Open,1:_.ZM.Closed})},d.createElement(Q,null,(0,S.sY)({ourProps:{ref:y},theirProps:m,slot:en,defaultTag:"div",name:"Popover"}),d.createElement(J.MainTreeNode,null))))))}),{Button:(0,S.yV)(function(e,t){let n=(0,P.M)(),{id:r="headlessui-popover-button-".concat(n),...o}=e,[i,a]=W("Popover.Button"),{isPortalled:l}=V("Popover.Button"),c=(0,d.useRef)(null),s="headlessui-focus-sentinel-".concat((0,P.M)()),u=G(),f=null==u?void 0:u.closeOthers,h=null!==(0,d.useContext)(X);(0,d.useEffect)(()=>{if(!h)return a({type:3,buttonId:r}),()=>{a({type:3,buttonId:null})}},[h,r,a]);let[m]=(0,d.useState)(()=>Symbol()),g=(0,b.T)(c,t,h?null:e=>{if(e)i.buttons.current.push(m);else{let e=i.buttons.current.indexOf(m);-1!==e&&i.buttons.current.splice(e,1)}i.buttons.current.length>1&&console.warn("You are already using a but only 1 is supported."),e&&a({type:2,button:e})}),y=(0,b.T)(c,t),x=v(c),w=(0,p.z)(e=>{var t,n,r;if(h){if(1===i.popoverState)return;switch(e.key){case z.R.Space:case z.R.Enter:e.preventDefault(),null==(n=(t=e.target).click)||n.call(t),a({type:1}),null==(r=i.button)||r.focus()}}else switch(e.key){case z.R.Space:case z.R.Enter:e.preventDefault(),e.stopPropagation(),1===i.popoverState&&(null==f||f(i.buttonId)),a({type:0});break;case z.R.Escape:if(0!==i.popoverState)return null==f?void 0:f(i.buttonId);if(!c.current||null!=x&&x.activeElement&&!c.current.contains(x.activeElement))return;e.preventDefault(),e.stopPropagation(),a({type:1})}}),k=(0,p.z)(e=>{h||e.key===z.R.Space&&e.preventDefault()}),E=(0,p.z)(t=>{var n,r;(0,D.P)(t.currentTarget)||e.disabled||(h?(a({type:1}),null==(n=i.button)||n.focus()):(t.preventDefault(),t.stopPropagation(),1===i.popoverState&&(null==f||f(i.buttonId)),a({type:0}),null==(r=i.button)||r.focus()))}),C=(0,p.z)(e=>{e.preventDefault(),e.stopPropagation()}),O=0===i.popoverState,j=(0,d.useMemo)(()=>({open:O}),[O]),N=(0,M.f)(e,c),R=h?{ref:y,type:N,onKeyDown:w,onClick:E}:{ref:g,id:i.buttonId,type:N,"aria-expanded":0===i.popoverState,"aria-controls":i.panel?i.panelId:void 0,onKeyDown:w,onKeyUp:k,onClick:E,onMouseDown:C},_=A(),B=(0,p.z)(()=>{let e=i.panel;e&&(0,L.E)(_.current,{[T.Forwards]:()=>(0,Z.jA)(e,Z.TO.First),[T.Backwards]:()=>(0,Z.jA)(e,Z.TO.Last)})===Z.fE.Error&&(0,Z.jA)((0,Z.GO)().filter(e=>"true"!==e.dataset.headlessuiFocusGuard),(0,L.E)(_.current,{[T.Forwards]:Z.TO.Next,[T.Backwards]:Z.TO.Previous}),{relativeTo:i.button})});return d.createElement(d.Fragment,null,(0,S.sY)({ourProps:R,theirProps:o,slot:j,defaultTag:"button",name:"Popover.Button"}),O&&!h&&l&&d.createElement(I._,{id:s,features:I.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:B}))}),Overlay:(0,S.yV)(function(e,t){let n=(0,P.M)(),{id:r="headlessui-popover-overlay-".concat(n),...o}=e,[{popoverState:i},a]=W("Popover.Overlay"),l=(0,b.T)(t),c=(0,_.oJ)(),s=null!==c?(c&_.ZM.Open)===_.ZM.Open:0===i,u=(0,p.z)(e=>{if((0,D.P)(e.currentTarget))return e.preventDefault();a({type:1})}),f=(0,d.useMemo)(()=>({open:0===i}),[i]);return(0,S.sY)({ourProps:{ref:l,id:r,"aria-hidden":!0,onClick:u},theirProps:o,slot:f,defaultTag:"div",features:Y,visible:s,name:"Popover.Overlay"})}),Panel:(0,S.yV)(function(e,t){let n=(0,P.M)(),{id:r="headlessui-popover-panel-".concat(n),focus:o=!1,...i}=e,[a,l]=W("Popover.Panel"),{close:c,isPortalled:s}=V("Popover.Panel"),u="headlessui-focus-sentinel-before-".concat((0,P.M)()),f="headlessui-focus-sentinel-after-".concat((0,P.M)()),m=(0,d.useRef)(null),g=(0,b.T)(m,t,e=>{l({type:4,panel:e})}),y=v(m),x=(0,S.Y2)();(0,h.e)(()=>(l({type:5,panelId:r}),()=>{l({type:5,panelId:null})}),[r,l]);let w=(0,_.oJ)(),k=null!==w?(w&_.ZM.Open)===_.ZM.Open:0===a.popoverState,E=(0,p.z)(e=>{var t;if(e.key===z.R.Escape){if(0!==a.popoverState||!m.current||null!=y&&y.activeElement&&!m.current.contains(y.activeElement))return;e.preventDefault(),e.stopPropagation(),l({type:1}),null==(t=a.button)||t.focus()}});(0,d.useEffect)(()=>{var t;e.static||1===a.popoverState&&(null==(t=e.unmount)||t)&&l({type:4,panel:null})},[a.popoverState,e.unmount,e.static,l]),(0,d.useEffect)(()=>{if(a.__demoMode||!o||0!==a.popoverState||!m.current)return;let e=null==y?void 0:y.activeElement;m.current.contains(e)||(0,Z.jA)(m.current,Z.TO.First)},[a.__demoMode,o,m,a.popoverState]);let C=(0,d.useMemo)(()=>({open:0===a.popoverState,close:c}),[a,c]),O={ref:g,id:r,onKeyDown:E,onBlur:o&&0===a.popoverState?e=>{var t,n,r,o,i;let c=e.relatedTarget;c&&m.current&&(null!=(t=m.current)&&t.contains(c)||(l({type:1}),(null!=(r=null==(n=a.beforePanelSentinel.current)?void 0:n.contains)&&r.call(n,c)||null!=(i=null==(o=a.afterPanelSentinel.current)?void 0:o.contains)&&i.call(o,c))&&c.focus({preventScroll:!0})))}:void 0,tabIndex:-1},j=A(),N=(0,p.z)(()=>{let e=m.current;e&&(0,L.E)(j.current,{[T.Forwards]:()=>{var t;(0,Z.jA)(e,Z.TO.First)===Z.fE.Error&&(null==(t=a.afterPanelSentinel.current)||t.focus())},[T.Backwards]:()=>{var e;null==(e=a.button)||e.focus({preventScroll:!0})}})}),M=(0,p.z)(()=>{let e=m.current;e&&(0,L.E)(j.current,{[T.Forwards]:()=>{var e;if(!a.button)return;let t=(0,Z.GO)(),n=t.indexOf(a.button),r=t.slice(0,n+1),o=[...t.slice(n+1),...r];for(let t of o.slice())if("true"===t.dataset.headlessuiFocusGuard||null!=(e=a.panel)&&e.contains(t)){let e=o.indexOf(t);-1!==e&&o.splice(e,1)}(0,Z.jA)(o,Z.TO.First,{sorted:!1})},[T.Backwards]:()=>{var t;(0,Z.jA)(e,Z.TO.Previous)===Z.fE.Error&&(null==(t=a.button)||t.focus())}})});return d.createElement(X.Provider,{value:r},k&&s&&d.createElement(I._,{id:u,ref:a.beforePanelSentinel,features:I.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:N}),(0,S.sY)({mergeRefs:x,ourProps:O,theirProps:i,slot:C,defaultTag:"div",features:Q,visible:k,name:"Popover.Panel"}),k&&s&&d.createElement(I._,{id:f,ref:a.afterPanelSentinel,features:I.A.Focusable,"data-headlessui-focus-guard":!0,as:"button",type:"button",onFocus:M}))}),Group:(0,S.yV)(function(e,t){let n;let r=(0,d.useRef)(null),o=(0,b.T)(r,t),[i,a]=(0,d.useState)([]),l={mainTreeNodeRef:n=(0,d.useRef)(null),MainTreeNode:(0,d.useMemo)(()=>function(){return d.createElement(I._,{features:I.A.Hidden,ref:n})},[n])},c=(0,p.z)(e=>{a(t=>{let n=t.indexOf(e);if(-1!==n){let e=t.slice();return e.splice(n,1),e}return t})}),s=(0,p.z)(e=>(a(t=>[...t,e]),()=>c(e))),u=(0,p.z)(()=>{var e;let t=(0,g.r)(r);if(!t)return!1;let n=t.activeElement;return!!(null!=(e=r.current)&&e.contains(n))||i.some(e=>{var r,o;return(null==(r=t.getElementById(e.buttonId.current))?void 0:r.contains(n))||(null==(o=t.getElementById(e.panelId.current))?void 0:o.contains(n))})}),f=(0,p.z)(e=>{for(let t of i)t.buttonId.current!==e&&t.close()}),h=(0,d.useMemo)(()=>({registerPopover:s,unregisterPopover:c,isFocusWithinPopoverGroup:u,closeOthers:f,mainTreeNodeRef:l.mainTreeNodeRef}),[s,c,u,f,l.mainTreeNodeRef]),m=(0,d.useMemo)(()=>({}),[]);return d.createElement(U.Provider,{value:h},(0,S.sY)({ourProps:{ref:o},theirProps:e,slot:m,defaultTag:"div",name:"Popover.Group"}),d.createElement(l.MainTreeNode,null))})});var ee=n(33044),et=n(28517);let en=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor"}),d.createElement("path",{fillRule:"evenodd",d:"M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z",clipRule:"evenodd"}))};var er=n(4537),eo=n(99735),ei=n(7656);function ea(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return t.setHours(0,0,0,0),t}function el(){return ea(Date.now())}function ec(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return t.setDate(1),t.setHours(0,0,0,0),t}var es=n(65954),eu=n(96398),ed=n(41154);function ef(e){var t,n;if((0,ei.Z)(1,arguments),e&&"function"==typeof e.forEach)t=e;else{if("object"!==(0,ed.Z)(e)||null===e)return new Date(NaN);t=Array.prototype.slice.call(e)}return t.forEach(function(e){var t=(0,eo.Z)(e);(void 0===n||nt||isNaN(t.getDate()))&&(n=t)}),n||new Date(NaN)}var eh=n(25721),em=n(47869);function eg(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,eh.Z)(e,-n)}var ev=n(55463);function ey(e,t){if((0,ei.Z)(2,arguments),!t||"object"!==(0,ed.Z)(t))return new Date(NaN);var n=t.years?(0,em.Z)(t.years):0,r=t.months?(0,em.Z)(t.months):0,o=t.weeks?(0,em.Z)(t.weeks):0,i=t.days?(0,em.Z)(t.days):0,a=t.hours?(0,em.Z)(t.hours):0,l=t.minutes?(0,em.Z)(t.minutes):0,c=t.seconds?(0,em.Z)(t.seconds):0;return new Date(eg(function(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,ev.Z)(e,-n)}(e,r+12*n),i+7*o).getTime()-1e3*(c+60*(l+60*a)))}function eb(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=new Date(0);return n.setFullYear(t.getFullYear(),0,1),n.setHours(0,0,0,0),n}function ex(e){return(0,ei.Z)(1,arguments),e instanceof Date||"object"===(0,ed.Z)(e)&&"[object Date]"===Object.prototype.toString.call(e)}function ew(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getUTCDay();return t.setUTCDate(t.getUTCDate()-((n<1?7:0)+n-1)),t.setUTCHours(0,0,0,0),t}function eS(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getUTCFullYear(),r=new Date(0);r.setUTCFullYear(n+1,0,4),r.setUTCHours(0,0,0,0);var o=ew(r),i=new Date(0);i.setUTCFullYear(n,0,4),i.setUTCHours(0,0,0,0);var a=ew(i);return t.getTime()>=o.getTime()?n+1:t.getTime()>=a.getTime()?n:n-1}var ek={};function eE(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,c,s,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.weekStartsOn)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.weekStartsOn)&&void 0!==o?o:ek.weekStartsOn)&&void 0!==r?r:null===(c=ek.locale)||void 0===c?void 0:null===(s=c.options)||void 0===s?void 0:s.weekStartsOn)&&void 0!==n?n:0);if(!(u>=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getUTCDay();return d.setUTCDate(d.getUTCDate()-((f=1&&f<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var p=new Date(0);p.setUTCFullYear(d+1,0,f),p.setUTCHours(0,0,0,0);var h=eE(p,t),m=new Date(0);m.setUTCFullYear(d,0,f),m.setUTCHours(0,0,0,0);var g=eE(m,t);return u.getTime()>=h.getTime()?d+1:u.getTime()>=g.getTime()?d:d-1}function eO(e,t){for(var n=Math.abs(e).toString();n.length0?n:1-n;return eO("yy"===t?r%100:r,t.length)},M:function(e,t){var n=e.getUTCMonth();return"M"===t?String(n+1):eO(n+1,2)},d:function(e,t){return eO(e.getUTCDate(),t.length)},h:function(e,t){return eO(e.getUTCHours()%12||12,t.length)},H:function(e,t){return eO(e.getUTCHours(),t.length)},m:function(e,t){return eO(e.getUTCMinutes(),t.length)},s:function(e,t){return eO(e.getUTCSeconds(),t.length)},S:function(e,t){var n=t.length;return eO(Math.floor(e.getUTCMilliseconds()*Math.pow(10,n-3)),t.length)}},eP={midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"};function eN(e,t){var n=e>0?"-":"+",r=Math.abs(e),o=Math.floor(r/60),i=r%60;return 0===i?n+String(o):n+String(o)+(t||"")+eO(i,2)}function eM(e,t){return e%60==0?(e>0?"-":"+")+eO(Math.abs(e)/60,2):eI(e,t)}function eI(e,t){var n=Math.abs(e);return(e>0?"-":"+")+eO(Math.floor(n/60),2)+(t||"")+eO(n%60,2)}var eR={G:function(e,t,n){var r=e.getUTCFullYear()>0?1:0;switch(t){case"G":case"GG":case"GGG":return n.era(r,{width:"abbreviated"});case"GGGGG":return n.era(r,{width:"narrow"});default:return n.era(r,{width:"wide"})}},y:function(e,t,n){if("yo"===t){var r=e.getUTCFullYear();return n.ordinalNumber(r>0?r:1-r,{unit:"year"})}return ej.y(e,t)},Y:function(e,t,n,r){var o=eC(e,r),i=o>0?o:1-o;return"YY"===t?eO(i%100,2):"Yo"===t?n.ordinalNumber(i,{unit:"year"}):eO(i,t.length)},R:function(e,t){return eO(eS(e),t.length)},u:function(e,t){return eO(e.getUTCFullYear(),t.length)},Q:function(e,t,n){var r=Math.ceil((e.getUTCMonth()+1)/3);switch(t){case"Q":return String(r);case"QQ":return eO(r,2);case"Qo":return n.ordinalNumber(r,{unit:"quarter"});case"QQQ":return n.quarter(r,{width:"abbreviated",context:"formatting"});case"QQQQQ":return n.quarter(r,{width:"narrow",context:"formatting"});default:return n.quarter(r,{width:"wide",context:"formatting"})}},q:function(e,t,n){var r=Math.ceil((e.getUTCMonth()+1)/3);switch(t){case"q":return String(r);case"qq":return eO(r,2);case"qo":return n.ordinalNumber(r,{unit:"quarter"});case"qqq":return n.quarter(r,{width:"abbreviated",context:"standalone"});case"qqqqq":return n.quarter(r,{width:"narrow",context:"standalone"});default:return n.quarter(r,{width:"wide",context:"standalone"})}},M:function(e,t,n){var r=e.getUTCMonth();switch(t){case"M":case"MM":return ej.M(e,t);case"Mo":return n.ordinalNumber(r+1,{unit:"month"});case"MMM":return n.month(r,{width:"abbreviated",context:"formatting"});case"MMMMM":return n.month(r,{width:"narrow",context:"formatting"});default:return n.month(r,{width:"wide",context:"formatting"})}},L:function(e,t,n){var r=e.getUTCMonth();switch(t){case"L":return String(r+1);case"LL":return eO(r+1,2);case"Lo":return n.ordinalNumber(r+1,{unit:"month"});case"LLL":return n.month(r,{width:"abbreviated",context:"standalone"});case"LLLLL":return n.month(r,{width:"narrow",context:"standalone"});default:return n.month(r,{width:"wide",context:"standalone"})}},w:function(e,t,n,r){var o=function(e,t){(0,ei.Z)(1,arguments);var n=(0,eo.Z)(e);return Math.round((eE(n,t).getTime()-(function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,c,s,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:ek.firstWeekContainsDate)&&void 0!==r?r:null===(c=ek.locale)||void 0===c?void 0:null===(s=c.options)||void 0===s?void 0:s.firstWeekContainsDate)&&void 0!==n?n:1),d=eC(e,t),f=new Date(0);return f.setUTCFullYear(d,0,u),f.setUTCHours(0,0,0,0),eE(f,t)})(n,t).getTime())/6048e5)+1}(e,r);return"wo"===t?n.ordinalNumber(o,{unit:"week"}):eO(o,t.length)},I:function(e,t,n){var r=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return Math.round((ew(t).getTime()-(function(e){(0,ei.Z)(1,arguments);var t=eS(e),n=new Date(0);return n.setUTCFullYear(t,0,4),n.setUTCHours(0,0,0,0),ew(n)})(t).getTime())/6048e5)+1}(e);return"Io"===t?n.ordinalNumber(r,{unit:"week"}):eO(r,t.length)},d:function(e,t,n){return"do"===t?n.ordinalNumber(e.getUTCDate(),{unit:"date"}):ej.d(e,t)},D:function(e,t,n){var r=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getTime();return t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0),Math.floor((n-t.getTime())/864e5)+1}(e);return"Do"===t?n.ordinalNumber(r,{unit:"dayOfYear"}):eO(r,t.length)},E:function(e,t,n){var r=e.getUTCDay();switch(t){case"E":case"EE":case"EEE":return n.day(r,{width:"abbreviated",context:"formatting"});case"EEEEE":return n.day(r,{width:"narrow",context:"formatting"});case"EEEEEE":return n.day(r,{width:"short",context:"formatting"});default:return n.day(r,{width:"wide",context:"formatting"})}},e:function(e,t,n,r){var o=e.getUTCDay(),i=(o-r.weekStartsOn+8)%7||7;switch(t){case"e":return String(i);case"ee":return eO(i,2);case"eo":return n.ordinalNumber(i,{unit:"day"});case"eee":return n.day(o,{width:"abbreviated",context:"formatting"});case"eeeee":return n.day(o,{width:"narrow",context:"formatting"});case"eeeeee":return n.day(o,{width:"short",context:"formatting"});default:return n.day(o,{width:"wide",context:"formatting"})}},c:function(e,t,n,r){var o=e.getUTCDay(),i=(o-r.weekStartsOn+8)%7||7;switch(t){case"c":return String(i);case"cc":return eO(i,t.length);case"co":return n.ordinalNumber(i,{unit:"day"});case"ccc":return n.day(o,{width:"abbreviated",context:"standalone"});case"ccccc":return n.day(o,{width:"narrow",context:"standalone"});case"cccccc":return n.day(o,{width:"short",context:"standalone"});default:return n.day(o,{width:"wide",context:"standalone"})}},i:function(e,t,n){var r=e.getUTCDay(),o=0===r?7:r;switch(t){case"i":return String(o);case"ii":return eO(o,t.length);case"io":return n.ordinalNumber(o,{unit:"day"});case"iii":return n.day(r,{width:"abbreviated",context:"formatting"});case"iiiii":return n.day(r,{width:"narrow",context:"formatting"});case"iiiiii":return n.day(r,{width:"short",context:"formatting"});default:return n.day(r,{width:"wide",context:"formatting"})}},a:function(e,t,n){var r=e.getUTCHours()/12>=1?"pm":"am";switch(t){case"a":case"aa":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"aaa":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},b:function(e,t,n){var r,o=e.getUTCHours();switch(r=12===o?eP.noon:0===o?eP.midnight:o/12>=1?"pm":"am",t){case"b":case"bb":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"bbb":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},B:function(e,t,n){var r,o=e.getUTCHours();switch(r=o>=17?eP.evening:o>=12?eP.afternoon:o>=4?eP.morning:eP.night,t){case"B":case"BB":case"BBB":return n.dayPeriod(r,{width:"abbreviated",context:"formatting"});case"BBBBB":return n.dayPeriod(r,{width:"narrow",context:"formatting"});default:return n.dayPeriod(r,{width:"wide",context:"formatting"})}},h:function(e,t,n){if("ho"===t){var r=e.getUTCHours()%12;return 0===r&&(r=12),n.ordinalNumber(r,{unit:"hour"})}return ej.h(e,t)},H:function(e,t,n){return"Ho"===t?n.ordinalNumber(e.getUTCHours(),{unit:"hour"}):ej.H(e,t)},K:function(e,t,n){var r=e.getUTCHours()%12;return"Ko"===t?n.ordinalNumber(r,{unit:"hour"}):eO(r,t.length)},k:function(e,t,n){var r=e.getUTCHours();return(0===r&&(r=24),"ko"===t)?n.ordinalNumber(r,{unit:"hour"}):eO(r,t.length)},m:function(e,t,n){return"mo"===t?n.ordinalNumber(e.getUTCMinutes(),{unit:"minute"}):ej.m(e,t)},s:function(e,t,n){return"so"===t?n.ordinalNumber(e.getUTCSeconds(),{unit:"second"}):ej.s(e,t)},S:function(e,t){return ej.S(e,t)},X:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();if(0===o)return"Z";switch(t){case"X":return eM(o);case"XXXX":case"XX":return eI(o);default:return eI(o,":")}},x:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"x":return eM(o);case"xxxx":case"xx":return eI(o);default:return eI(o,":")}},O:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"O":case"OO":case"OOO":return"GMT"+eN(o,":");default:return"GMT"+eI(o,":")}},z:function(e,t,n,r){var o=(r._originalDate||e).getTimezoneOffset();switch(t){case"z":case"zz":case"zzz":return"GMT"+eN(o,":");default:return"GMT"+eI(o,":")}},t:function(e,t,n,r){return eO(Math.floor((r._originalDate||e).getTime()/1e3),t.length)},T:function(e,t,n,r){return eO((r._originalDate||e).getTime(),t.length)}},eT=function(e,t){switch(e){case"P":return t.date({width:"short"});case"PP":return t.date({width:"medium"});case"PPP":return t.date({width:"long"});default:return t.date({width:"full"})}},eA=function(e,t){switch(e){case"p":return t.time({width:"short"});case"pp":return t.time({width:"medium"});case"ppp":return t.time({width:"long"});default:return t.time({width:"full"})}},e_={p:eA,P:function(e,t){var n,r=e.match(/(P+)(p+)?/)||[],o=r[1],i=r[2];if(!i)return eT(e,t);switch(o){case"P":n=t.dateTime({width:"short"});break;case"PP":n=t.dateTime({width:"medium"});break;case"PPP":n=t.dateTime({width:"long"});break;default:n=t.dateTime({width:"full"})}return n.replace("{{date}}",eT(o,t)).replace("{{time}}",eA(i,t))}};function eD(e){var t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()));return t.setUTCFullYear(e.getFullYear()),e.getTime()-t.getTime()}var eZ=["D","DD"],eL=["YY","YYYY"];function ez(e,t,n){if("YYYY"===e)throw RangeError("Use `yyyy` instead of `YYYY` (in `".concat(t,"`) for formatting years to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("YY"===e)throw RangeError("Use `yy` instead of `YY` (in `".concat(t,"`) for formatting years to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("D"===e)throw RangeError("Use `d` instead of `D` (in `".concat(t,"`) for formatting days of the month to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));if("DD"===e)throw RangeError("Use `dd` instead of `DD` (in `".concat(t,"`) for formatting days of the month to the input `").concat(n,"`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"))}var eB={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};function eF(e){return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.width?String(t.width):e.defaultWidth;return e.formats[n]||e.formats[e.defaultWidth]}}var eH={date:eF({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:eF({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:eF({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},eq={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function eW(e){return function(t,n){var r;if("formatting"===(null!=n&&n.context?String(n.context):"standalone")&&e.formattingValues){var o=e.defaultFormattingWidth||e.defaultWidth,i=null!=n&&n.width?String(n.width):o;r=e.formattingValues[i]||e.formattingValues[o]}else{var a=e.defaultWidth,l=null!=n&&n.width?String(n.width):e.defaultWidth;r=e.values[l]||e.values[a]}return r[e.argumentCallback?e.argumentCallback(t):t]}}function eK(e){return function(t){var n,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=r.width,i=o&&e.matchPatterns[o]||e.matchPatterns[e.defaultMatchWidth],a=t.match(i);if(!a)return null;var l=a[0],c=o&&e.parsePatterns[o]||e.parsePatterns[e.defaultParseWidth],s=Array.isArray(c)?function(e,t){for(var n=0;n0?"in "+r:r+" ago":r},formatLong:eH,formatRelative:function(e,t,n,r){return eq[e]},localize:{ordinalNumber:function(e,t){var n=Number(e),r=n%100;if(r>20||r<10)switch(r%10){case 1:return n+"st";case 2:return n+"nd";case 3:return n+"rd"}return n+"th"},era:eW({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:eW({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(e){return e-1}}),month:eW({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:eW({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:eW({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(a={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(e){return parseInt(e,10)}},function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.match(a.matchPattern);if(!n)return null;var r=n[0],o=e.match(a.parsePattern);if(!o)return null;var i=a.valueCallback?a.valueCallback(o[0]):o[0];return{value:i=t.valueCallback?t.valueCallback(i):i,rest:e.slice(r.length)}}),era:eK({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:eK({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(e){return e+1}}),month:eK({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:eK({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:eK({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}},eU=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,eG=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,eX=/^'([^]*?)'?$/,e$=/''/g,eY=/[a-zA-Z]/;function eQ(e,t,n){(0,ei.Z)(2,arguments);var r,o,i,a,l,c,s,u,d,f,p,h,m,g,v,y,b,x,w=String(t),S=null!==(r=null!==(o=null==n?void 0:n.locale)&&void 0!==o?o:ek.locale)&&void 0!==r?r:eV,k=(0,em.Z)(null!==(i=null!==(a=null!==(l=null!==(c=null==n?void 0:n.firstWeekContainsDate)&&void 0!==c?c:null==n?void 0:null===(s=n.locale)||void 0===s?void 0:null===(u=s.options)||void 0===u?void 0:u.firstWeekContainsDate)&&void 0!==l?l:ek.firstWeekContainsDate)&&void 0!==a?a:null===(d=ek.locale)||void 0===d?void 0:null===(f=d.options)||void 0===f?void 0:f.firstWeekContainsDate)&&void 0!==i?i:1);if(!(k>=1&&k<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var E=(0,em.Z)(null!==(p=null!==(h=null!==(m=null!==(g=null==n?void 0:n.weekStartsOn)&&void 0!==g?g:null==n?void 0:null===(v=n.locale)||void 0===v?void 0:null===(y=v.options)||void 0===y?void 0:y.weekStartsOn)&&void 0!==m?m:ek.weekStartsOn)&&void 0!==h?h:null===(b=ek.locale)||void 0===b?void 0:null===(x=b.options)||void 0===x?void 0:x.weekStartsOn)&&void 0!==p?p:0);if(!(E>=0&&E<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");if(!S.localize)throw RangeError("locale must contain localize property");if(!S.formatLong)throw RangeError("locale must contain formatLong property");var C=(0,eo.Z)(e);if(!function(e){return(0,ei.Z)(1,arguments),(!!ex(e)||"number"==typeof e)&&!isNaN(Number((0,eo.Z)(e)))}(C))throw RangeError("Invalid time value");var O=eD(C),j=function(e,t){return(0,ei.Z)(2,arguments),function(e,t){return(0,ei.Z)(2,arguments),new Date((0,eo.Z)(e).getTime()+(0,em.Z)(t))}(e,-(0,em.Z)(t))}(C,O),P={firstWeekContainsDate:k,weekStartsOn:E,locale:S,_originalDate:C};return w.match(eG).map(function(e){var t=e[0];return"p"===t||"P"===t?(0,e_[t])(e,S.formatLong):e}).join("").match(eU).map(function(r){if("''"===r)return"'";var o,i=r[0];if("'"===i)return(o=r.match(eX))?o[1].replace(e$,"'"):r;var a=eR[i];if(a)return null!=n&&n.useAdditionalWeekYearTokens||-1===eL.indexOf(r)||ez(r,t,String(e)),null!=n&&n.useAdditionalDayOfYearTokens||-1===eZ.indexOf(r)||ez(r,t,String(e)),a(j,r,S.localize,P);if(i.match(eY))throw RangeError("Format string contains an unescaped latin alphabet character `"+i+"`");return r}).join("")}var eJ=n(1153);let e0=(0,eJ.fn)("DateRangePicker"),e1=(e,t,n,r)=>{var o;if(n&&(e=null===(o=r.get(n))||void 0===o?void 0:o.from),e)return ea(e&&!t?e:ef([e,t]))},e2=(e,t,n,r)=>{var o,i;if(n&&(e=ea(null!==(i=null===(o=r.get(n))||void 0===o?void 0:o.to)&&void 0!==i?i:el())),e)return ea(e&&!t?e:ep([e,t]))},e6=[{value:"tdy",text:"Today",from:el()},{value:"w",text:"Last 7 days",from:ey(el(),{days:7})},{value:"t",text:"Last 30 days",from:ey(el(),{days:30})},{value:"m",text:"Month to Date",from:ec(el())},{value:"y",text:"Year to Date",from:eb(el())}],e4=(e,t,n,r)=>{let o=(null==n?void 0:n.code)||"en-US";if(!e&&!t)return"";if(e&&!t)return r?eQ(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e&&t){if(function(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getTime()===r.getTime()}(e,t))return r?eQ(e,r):e.toLocaleDateString(o,{year:"numeric",month:"short",day:"numeric"});if(e.getMonth()===t.getMonth()&&e.getFullYear()===t.getFullYear())return r?"".concat(eQ(e,r)," - ").concat(eQ(t,r)):"".concat(e.toLocaleDateString(o,{month:"short",day:"numeric"})," - \n ").concat(t.getDate(),", ").concat(t.getFullYear());{if(r)return"".concat(eQ(e,r)," - ").concat(eQ(t,r));let n={year:"numeric",month:"short",day:"numeric"};return"".concat(e.toLocaleDateString(o,n)," - \n ").concat(t.toLocaleDateString(o,n))}}return""};function e3(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(23,59,59,999),t}function e5(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,em.Z)(t),o=n.getFullYear(),i=n.getDate(),a=new Date(0);a.setFullYear(o,r,15),a.setHours(0,0,0,0);var l=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getFullYear(),r=t.getMonth(),o=new Date(0);return o.setFullYear(n,r+1,0),o.setHours(0,0,0,0),o.getDate()}(a);return n.setMonth(r,Math.min(i,l)),n}function e8(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,em.Z)(t);return isNaN(n.getTime())?new Date(NaN):(n.setFullYear(r),n)}function e7(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return 12*(n.getFullYear()-r.getFullYear())+(n.getMonth()-r.getMonth())}function e9(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getFullYear()===r.getFullYear()&&n.getMonth()===r.getMonth()}function te(e,t){(0,ei.Z)(2,arguments);var n=(0,eo.Z)(e),r=(0,eo.Z)(t);return n.getTime()=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getDay();return d.setDate(d.getDate()-((fr.getTime()}function ti(e,t){(0,ei.Z)(2,arguments);var n=ea(e),r=ea(t);return Math.round((n.getTime()-eD(n)-(r.getTime()-eD(r)))/864e5)}function ta(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,eh.Z)(e,7*n)}function tl(e,t){(0,ei.Z)(2,arguments);var n=(0,em.Z)(t);return(0,ev.Z)(e,12*n)}function tc(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,c,s,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.weekStartsOn)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.weekStartsOn)&&void 0!==o?o:ek.weekStartsOn)&&void 0!==r?r:null===(c=ek.locale)||void 0===c?void 0:null===(s=c.options)||void 0===s?void 0:s.weekStartsOn)&&void 0!==n?n:0);if(!(u>=0&&u<=6))throw RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=(0,eo.Z)(e),f=d.getDay();return d.setDate(d.getDate()+((fe7(l,a)&&(a=(0,ev.Z)(l,-1*((void 0===s?1:s)-1))),c&&0>e7(a,c)&&(a=c),u=ec(a),f=t.month,h=(p=(0,d.useState)(u))[0],m=[void 0===f?h:f,p[1]])[0],v=m[1],[g,function(e){if(!t.disableNavigation){var n,r=ec(e);v(r),null===(n=t.onMonthChange)||void 0===n||n.call(t,r)}}]),x=b[0],w=b[1],S=function(e,t){for(var n=t.reverseMonths,r=t.numberOfMonths,o=ec(e),i=e7(ec((0,ev.Z)(o,r)),o),a=[],l=0;l=e7(i,n)))return(0,ev.Z)(i,-(r?void 0===o?1:o:1))}}(x,y),C=function(e){return S.some(function(t){return e9(e,t)})};return th.jsx(tN.Provider,{value:{currentMonth:x,displayMonths:S,goToMonth:w,goToDate:function(e,t){C(e)||(t&&te(e,t)?w((0,ev.Z)(e,1+-1*y.numberOfMonths)):w(e))},previousMonth:E,nextMonth:k,isDateDisplayed:C},children:e.children})}function tI(){var e=(0,d.useContext)(tN);if(!e)throw Error("useNavigation must be used within a NavigationProvider");return e}function tR(e){var t,n=tk(),r=n.classNames,o=n.styles,i=n.components,a=tI().goToMonth,l=function(t){a((0,ev.Z)(t,e.displayIndex?-e.displayIndex:0))},c=null!==(t=null==i?void 0:i.CaptionLabel)&&void 0!==t?t:tE,s=th.jsx(c,{id:e.id,displayMonth:e.displayMonth});return th.jsxs("div",{className:r.caption_dropdowns,style:o.caption_dropdowns,children:[th.jsx("div",{className:r.vhidden,children:s}),th.jsx(tj,{onChange:l,displayMonth:e.displayMonth}),th.jsx(tP,{onChange:l,displayMonth:e.displayMonth})]})}function tT(e){return th.jsx("svg",tu({width:"16px",height:"16px",viewBox:"0 0 120 120"},e,{children:th.jsx("path",{d:"M69.490332,3.34314575 C72.6145263,0.218951416 77.6798462,0.218951416 80.8040405,3.34314575 C83.8617626,6.40086786 83.9268205,11.3179931 80.9992143,14.4548388 L80.8040405,14.6568542 L35.461,60 L80.8040405,105.343146 C83.8617626,108.400868 83.9268205,113.317993 80.9992143,116.454839 L80.8040405,116.656854 C77.7463184,119.714576 72.8291931,119.779634 69.6923475,116.852028 L69.490332,116.656854 L18.490332,65.6568542 C15.4326099,62.5991321 15.367552,57.6820069 18.2951583,54.5451612 L18.490332,54.3431458 L69.490332,3.34314575 Z",fill:"currentColor",fillRule:"nonzero"})}))}function tA(e){return th.jsx("svg",tu({width:"16px",height:"16px",viewBox:"0 0 120 120"},e,{children:th.jsx("path",{d:"M49.8040405,3.34314575 C46.6798462,0.218951416 41.6145263,0.218951416 38.490332,3.34314575 C35.4326099,6.40086786 35.367552,11.3179931 38.2951583,14.4548388 L38.490332,14.6568542 L83.8333725,60 L38.490332,105.343146 C35.4326099,108.400868 35.367552,113.317993 38.2951583,116.454839 L38.490332,116.656854 C41.5480541,119.714576 46.4651794,119.779634 49.602025,116.852028 L49.8040405,116.656854 L100.804041,65.6568542 C103.861763,62.5991321 103.926821,57.6820069 100.999214,54.5451612 L100.804041,54.3431458 L49.8040405,3.34314575 Z",fill:"currentColor"})}))}var t_=(0,d.forwardRef)(function(e,t){var n=tk(),r=n.classNames,o=n.styles,i=[r.button_reset,r.button];e.className&&i.push(e.className);var a=i.join(" "),l=tu(tu({},o.button_reset),o.button);return e.style&&Object.assign(l,e.style),th.jsx("button",tu({},e,{ref:t,type:"button",className:a,style:l}))});function tD(e){var t,n,r=tk(),o=r.dir,i=r.locale,a=r.classNames,l=r.styles,c=r.labels,s=c.labelPrevious,u=c.labelNext,d=r.components;if(!e.nextMonth&&!e.previousMonth)return th.jsx(th.Fragment,{});var f=s(e.previousMonth,{locale:i}),p=[a.nav_button,a.nav_button_previous].join(" "),h=u(e.nextMonth,{locale:i}),m=[a.nav_button,a.nav_button_next].join(" "),g=null!==(t=null==d?void 0:d.IconRight)&&void 0!==t?t:tA,v=null!==(n=null==d?void 0:d.IconLeft)&&void 0!==n?n:tT;return th.jsxs("div",{className:a.nav,style:l.nav,children:[!e.hidePrevious&&th.jsx(t_,{name:"previous-month","aria-label":f,className:p,style:l.nav_button_previous,disabled:!e.previousMonth,onClick:e.onPreviousClick,children:"rtl"===o?th.jsx(g,{className:a.nav_icon,style:l.nav_icon}):th.jsx(v,{className:a.nav_icon,style:l.nav_icon})}),!e.hideNext&&th.jsx(t_,{name:"next-month","aria-label":h,className:m,style:l.nav_button_next,disabled:!e.nextMonth,onClick:e.onNextClick,children:"rtl"===o?th.jsx(v,{className:a.nav_icon,style:l.nav_icon}):th.jsx(g,{className:a.nav_icon,style:l.nav_icon})})]})}function tZ(e){var t=tk().numberOfMonths,n=tI(),r=n.previousMonth,o=n.nextMonth,i=n.goToMonth,a=n.displayMonths,l=a.findIndex(function(t){return e9(e.displayMonth,t)}),c=0===l,s=l===a.length-1;return th.jsx(tD,{displayMonth:e.displayMonth,hideNext:t>1&&(c||!s),hidePrevious:t>1&&(s||!c),nextMonth:o,previousMonth:r,onPreviousClick:function(){r&&i(r)},onNextClick:function(){o&&i(o)}})}function tL(e){var t,n,r=tk(),o=r.classNames,i=r.disableNavigation,a=r.styles,l=r.captionLayout,c=r.components,s=null!==(t=null==c?void 0:c.CaptionLabel)&&void 0!==t?t:tE;return n=i?th.jsx(s,{id:e.id,displayMonth:e.displayMonth}):"dropdown"===l?th.jsx(tR,{displayMonth:e.displayMonth,id:e.id}):"dropdown-buttons"===l?th.jsxs(th.Fragment,{children:[th.jsx(tR,{displayMonth:e.displayMonth,displayIndex:e.displayIndex,id:e.id}),th.jsx(tZ,{displayMonth:e.displayMonth,displayIndex:e.displayIndex,id:e.id})]}):th.jsxs(th.Fragment,{children:[th.jsx(s,{id:e.id,displayMonth:e.displayMonth,displayIndex:e.displayIndex}),th.jsx(tZ,{displayMonth:e.displayMonth,id:e.id})]}),th.jsx("div",{className:o.caption,style:a.caption,children:n})}function tz(e){var t=tk(),n=t.footer,r=t.styles,o=t.classNames.tfoot;return n?th.jsx("tfoot",{className:o,style:r.tfoot,children:th.jsx("tr",{children:th.jsx("td",{colSpan:8,children:n})})}):th.jsx(th.Fragment,{})}function tB(){var e=tk(),t=e.classNames,n=e.styles,r=e.showWeekNumber,o=e.locale,i=e.weekStartsOn,a=e.ISOWeek,l=e.formatters.formatWeekdayName,c=e.labels.labelWeekday,s=function(e,t,n){for(var r=n?tn(new Date):tt(new Date,{locale:e,weekStartsOn:t}),o=[],i=0;i<7;i++){var a=(0,eh.Z)(r,i);o.push(a)}return o}(o,i,a);return th.jsxs("tr",{style:n.head_row,className:t.head_row,children:[r&&th.jsx("td",{style:n.head_cell,className:t.head_cell}),s.map(function(e,r){return th.jsx("th",{scope:"col",className:t.head_cell,style:n.head_cell,"aria-label":c(e,{locale:o}),children:l(e,{locale:o})},r)})]})}function tF(){var e,t=tk(),n=t.classNames,r=t.styles,o=t.components,i=null!==(e=null==o?void 0:o.HeadRow)&&void 0!==e?e:tB;return th.jsx("thead",{style:r.head,className:n.head,children:th.jsx(i,{})})}function tH(e){var t=tk(),n=t.locale,r=t.formatters.formatDay;return th.jsx(th.Fragment,{children:r(e.date,{locale:n})})}var tq=(0,d.createContext)(void 0);function tW(e){return tm(e.initialProps)?th.jsx(tK,{initialProps:e.initialProps,children:e.children}):th.jsx(tq.Provider,{value:{selected:void 0,modifiers:{disabled:[]}},children:e.children})}function tK(e){var t=e.initialProps,n=e.children,r=t.selected,o=t.min,i=t.max,a={disabled:[]};return r&&a.disabled.push(function(e){var t=i&&r.length>i-1,n=r.some(function(t){return tr(t,e)});return!!(t&&!n)}),th.jsx(tq.Provider,{value:{selected:r,onDayClick:function(e,n,a){if(null===(l=t.onDayClick)||void 0===l||l.call(t,e,n,a),(!n.selected||!o||(null==r?void 0:r.length)!==o)&&(n.selected||!i||(null==r?void 0:r.length)!==i)){var l,c,s=r?td([],r,!0):[];if(n.selected){var u=s.findIndex(function(t){return tr(e,t)});s.splice(u,1)}else s.push(e);null===(c=t.onSelect)||void 0===c||c.call(t,s,e,n,a)}},modifiers:a},children:n})}function tV(){var e=(0,d.useContext)(tq);if(!e)throw Error("useSelectMultiple must be used within a SelectMultipleProvider");return e}var tU=(0,d.createContext)(void 0);function tG(e){return tg(e.initialProps)?th.jsx(tX,{initialProps:e.initialProps,children:e.children}):th.jsx(tU.Provider,{value:{selected:void 0,modifiers:{range_start:[],range_end:[],range_middle:[],disabled:[]}},children:e.children})}function tX(e){var t=e.initialProps,n=e.children,r=t.selected,o=r||{},i=o.from,a=o.to,l=t.min,c=t.max,s={range_start:[],range_end:[],range_middle:[],disabled:[]};if(i?(s.range_start=[i],a?(s.range_end=[a],tr(i,a)||(s.range_middle=[{after:i,before:a}])):s.range_end=[i]):a&&(s.range_start=[a],s.range_end=[a]),l&&(i&&!a&&s.disabled.push({after:eg(i,l-1),before:(0,eh.Z)(i,l-1)}),i&&a&&s.disabled.push({after:i,before:(0,eh.Z)(i,l-1)}),!i&&a&&s.disabled.push({after:eg(a,l-1),before:(0,eh.Z)(a,l-1)})),c){if(i&&!a&&(s.disabled.push({before:(0,eh.Z)(i,-c+1)}),s.disabled.push({after:(0,eh.Z)(i,c-1)})),i&&a){var u=c-(ti(a,i)+1);s.disabled.push({before:eg(i,u)}),s.disabled.push({after:(0,eh.Z)(a,u)})}!i&&a&&(s.disabled.push({before:(0,eh.Z)(a,-c+1)}),s.disabled.push({after:(0,eh.Z)(a,c-1)}))}return th.jsx(tU.Provider,{value:{selected:r,onDayClick:function(e,n,o){null===(c=t.onDayClick)||void 0===c||c.call(t,e,n,o);var i,a,l,c,s,u=(a=(i=r||{}).from,l=i.to,a&&l?tr(l,e)&&tr(a,e)?void 0:tr(l,e)?{from:l,to:void 0}:tr(a,e)?void 0:to(a,e)?{from:e,to:l}:{from:a,to:e}:l?to(e,l)?{from:l,to:e}:{from:e,to:l}:a?te(e,a)?{from:e,to:a}:{from:a,to:e}:{from:e,to:void 0});null===(s=t.onSelect)||void 0===s||s.call(t,u,e,n,o)},modifiers:s},children:n})}function t$(){var e=(0,d.useContext)(tU);if(!e)throw Error("useSelectRange must be used within a SelectRangeProvider");return e}function tY(e){return Array.isArray(e)?td([],e,!0):void 0!==e?[e]:[]}(l=s||(s={})).Outside="outside",l.Disabled="disabled",l.Selected="selected",l.Hidden="hidden",l.Today="today",l.RangeStart="range_start",l.RangeEnd="range_end",l.RangeMiddle="range_middle";var tQ=s.Selected,tJ=s.Disabled,t0=s.Hidden,t1=s.Today,t2=s.RangeEnd,t6=s.RangeMiddle,t4=s.RangeStart,t3=s.Outside,t5=(0,d.createContext)(void 0);function t8(e){var t,n,r,o=tk(),i=tV(),a=t$(),l=((t={})[tQ]=tY(o.selected),t[tJ]=tY(o.disabled),t[t0]=tY(o.hidden),t[t1]=[o.today],t[t2]=[],t[t6]=[],t[t4]=[],t[t3]=[],o.fromDate&&t[tJ].push({before:o.fromDate}),o.toDate&&t[tJ].push({after:o.toDate}),tm(o)?t[tJ]=t[tJ].concat(i.modifiers[tJ]):tg(o)&&(t[tJ]=t[tJ].concat(a.modifiers[tJ]),t[t4]=a.modifiers[t4],t[t6]=a.modifiers[t6],t[t2]=a.modifiers[t2]),t),c=(n=o.modifiers,r={},Object.entries(n).forEach(function(e){var t=e[0],n=e[1];r[t]=tY(n)}),r),s=tu(tu({},l),c);return th.jsx(t5.Provider,{value:s,children:e.children})}function t7(){var e=(0,d.useContext)(t5);if(!e)throw Error("useModifiers must be used within a ModifiersProvider");return e}function t9(e,t,n){var r=Object.keys(t).reduce(function(n,r){return t[r].some(function(t){if("boolean"==typeof t)return t;if(ex(t))return tr(e,t);if(Array.isArray(t)&&t.every(ex))return t.includes(e);if(t&&"object"==typeof t&&"from"in t)return r=t.from,o=t.to,r&&o?(0>ti(o,r)&&(r=(n=[o,r])[0],o=n[1]),ti(e,r)>=0&&ti(o,e)>=0):o?tr(o,e):!!r&&tr(r,e);if(t&&"object"==typeof t&&"dayOfWeek"in t)return t.dayOfWeek.includes(e.getDay());if(t&&"object"==typeof t&&"before"in t&&"after"in t){var n,r,o,i=ti(t.before,e),a=ti(t.after,e),l=i>0,c=a<0;return to(t.before,t.after)?c&&l:l||c}return t&&"object"==typeof t&&"after"in t?ti(e,t.after)>0:t&&"object"==typeof t&&"before"in t?ti(t.before,e)>0:"function"==typeof t&&t(e)})&&n.push(r),n},[]),o={};return r.forEach(function(e){return o[e]=!0}),n&&!e9(e,n)&&(o.outside=!0),o}var ne=(0,d.createContext)(void 0);function nt(e){var t=tI(),n=t7(),r=(0,d.useState)(),o=r[0],i=r[1],a=(0,d.useState)(),l=a[0],c=a[1],s=function(e,t){for(var n,r,o=ec(e[0]),i=e3(e[e.length-1]),a=o;a<=i;){var l=t9(a,t);if(!(!l.disabled&&!l.hidden)){a=(0,eh.Z)(a,1);continue}if(l.selected)return a;l.today&&!r&&(r=a),n||(n=a),a=(0,eh.Z)(a,1)}return r||n}(t.displayMonths,n),u=(null!=o?o:l&&t.isDateDisplayed(l))?l:s,f=function(e){i(e)},p=tk(),h=function(e,r){if(o){var i=function e(t,n){var r=n.moveBy,o=n.direction,i=n.context,a=n.modifiers,l=n.retry,c=void 0===l?{count:0,lastFocused:t}:l,s=i.weekStartsOn,u=i.fromDate,d=i.toDate,f=i.locale,p=({day:eh.Z,week:ta,month:ev.Z,year:tl,startOfWeek:function(e){return i.ISOWeek?tn(e):tt(e,{locale:f,weekStartsOn:s})},endOfWeek:function(e){return i.ISOWeek?ts(e):tc(e,{locale:f,weekStartsOn:s})}})[r](t,"after"===o?1:-1);"before"===o&&u?p=ef([u,p]):"after"===o&&d&&(p=ep([d,p]));var h=!0;if(a){var m=t9(p,a);h=!m.disabled&&!m.hidden}return h?p:c.count>365?c.lastFocused:e(p,{moveBy:r,direction:o,context:i,modifiers:a,retry:tu(tu({},c),{count:c.count+1})})}(o,{moveBy:e,direction:r,context:p,modifiers:n});tr(o,i)||(t.goToDate(i,o),f(i))}};return th.jsx(ne.Provider,{value:{focusedDay:o,focusTarget:u,blur:function(){c(o),i(void 0)},focus:f,focusDayAfter:function(){return h("day","after")},focusDayBefore:function(){return h("day","before")},focusWeekAfter:function(){return h("week","after")},focusWeekBefore:function(){return h("week","before")},focusMonthBefore:function(){return h("month","before")},focusMonthAfter:function(){return h("month","after")},focusYearBefore:function(){return h("year","before")},focusYearAfter:function(){return h("year","after")},focusStartOfWeek:function(){return h("startOfWeek","before")},focusEndOfWeek:function(){return h("endOfWeek","after")}},children:e.children})}function nn(){var e=(0,d.useContext)(ne);if(!e)throw Error("useFocusContext must be used within a FocusProvider");return e}var nr=(0,d.createContext)(void 0);function no(e){return tv(e.initialProps)?th.jsx(ni,{initialProps:e.initialProps,children:e.children}):th.jsx(nr.Provider,{value:{selected:void 0},children:e.children})}function ni(e){var t=e.initialProps,n=e.children,r={selected:t.selected,onDayClick:function(e,n,r){var o,i,a;if(null===(o=t.onDayClick)||void 0===o||o.call(t,e,n,r),n.selected&&!t.required){null===(i=t.onSelect)||void 0===i||i.call(t,void 0,e,n,r);return}null===(a=t.onSelect)||void 0===a||a.call(t,e,e,n,r)}};return th.jsx(nr.Provider,{value:r,children:n})}function na(){var e=(0,d.useContext)(nr);if(!e)throw Error("useSelectSingle must be used within a SelectSingleProvider");return e}function nl(e){var t,n,r,o,i,a,l,c,u,f,p,h,m,g,v,y,b,x,w,S,k,E,C,O,j,P,N,M,I,R,T,A,_,D,Z,L,z,B,F,H,q,W,K=(0,d.useRef)(null),V=(t=e.date,n=e.displayMonth,a=tk(),l=nn(),c=t9(t,t7(),n),u=tk(),f=na(),p=tV(),h=t$(),g=(m=nn()).focusDayAfter,v=m.focusDayBefore,y=m.focusWeekAfter,b=m.focusWeekBefore,x=m.blur,w=m.focus,S=m.focusMonthBefore,k=m.focusMonthAfter,E=m.focusYearBefore,C=m.focusYearAfter,O=m.focusStartOfWeek,j=m.focusEndOfWeek,P={onClick:function(e){var n,r,o,i;tv(u)?null===(n=f.onDayClick)||void 0===n||n.call(f,t,c,e):tm(u)?null===(r=p.onDayClick)||void 0===r||r.call(p,t,c,e):tg(u)?null===(o=h.onDayClick)||void 0===o||o.call(h,t,c,e):null===(i=u.onDayClick)||void 0===i||i.call(u,t,c,e)},onFocus:function(e){var n;w(t),null===(n=u.onDayFocus)||void 0===n||n.call(u,t,c,e)},onBlur:function(e){var n;x(),null===(n=u.onDayBlur)||void 0===n||n.call(u,t,c,e)},onKeyDown:function(e){var n;switch(e.key){case"ArrowLeft":e.preventDefault(),e.stopPropagation(),"rtl"===u.dir?g():v();break;case"ArrowRight":e.preventDefault(),e.stopPropagation(),"rtl"===u.dir?v():g();break;case"ArrowDown":e.preventDefault(),e.stopPropagation(),y();break;case"ArrowUp":e.preventDefault(),e.stopPropagation(),b();break;case"PageUp":e.preventDefault(),e.stopPropagation(),e.shiftKey?E():S();break;case"PageDown":e.preventDefault(),e.stopPropagation(),e.shiftKey?C():k();break;case"Home":e.preventDefault(),e.stopPropagation(),O();break;case"End":e.preventDefault(),e.stopPropagation(),j()}null===(n=u.onDayKeyDown)||void 0===n||n.call(u,t,c,e)},onKeyUp:function(e){var n;null===(n=u.onDayKeyUp)||void 0===n||n.call(u,t,c,e)},onMouseEnter:function(e){var n;null===(n=u.onDayMouseEnter)||void 0===n||n.call(u,t,c,e)},onMouseLeave:function(e){var n;null===(n=u.onDayMouseLeave)||void 0===n||n.call(u,t,c,e)},onPointerEnter:function(e){var n;null===(n=u.onDayPointerEnter)||void 0===n||n.call(u,t,c,e)},onPointerLeave:function(e){var n;null===(n=u.onDayPointerLeave)||void 0===n||n.call(u,t,c,e)},onTouchCancel:function(e){var n;null===(n=u.onDayTouchCancel)||void 0===n||n.call(u,t,c,e)},onTouchEnd:function(e){var n;null===(n=u.onDayTouchEnd)||void 0===n||n.call(u,t,c,e)},onTouchMove:function(e){var n;null===(n=u.onDayTouchMove)||void 0===n||n.call(u,t,c,e)},onTouchStart:function(e){var n;null===(n=u.onDayTouchStart)||void 0===n||n.call(u,t,c,e)}},N=tk(),M=na(),I=tV(),R=t$(),T=tv(N)?M.selected:tm(N)?I.selected:tg(N)?R.selected:void 0,A=!!(a.onDayClick||"default"!==a.mode),(0,d.useEffect)(function(){var e;!c.outside&&l.focusedDay&&A&&tr(l.focusedDay,t)&&(null===(e=K.current)||void 0===e||e.focus())},[l.focusedDay,t,K,A,c.outside]),D=(_=[a.classNames.day],Object.keys(c).forEach(function(e){var t=a.modifiersClassNames[e];if(t)_.push(t);else if(Object.values(s).includes(e)){var n=a.classNames["day_".concat(e)];n&&_.push(n)}}),_).join(" "),Z=tu({},a.styles.day),Object.keys(c).forEach(function(e){var t;Z=tu(tu({},Z),null===(t=a.modifiersStyles)||void 0===t?void 0:t[e])}),L=Z,z=!!(c.outside&&!a.showOutsideDays||c.hidden),B=null!==(i=null===(o=a.components)||void 0===o?void 0:o.DayContent)&&void 0!==i?i:tH,F={style:L,className:D,children:th.jsx(B,{date:t,displayMonth:n,activeModifiers:c}),role:"gridcell"},H=l.focusTarget&&tr(l.focusTarget,t)&&!c.outside,q=l.focusedDay&&tr(l.focusedDay,t),W=tu(tu(tu({},F),((r={disabled:c.disabled,role:"gridcell"})["aria-selected"]=c.selected,r.tabIndex=q||H?0:-1,r)),P),{isButton:A,isHidden:z,activeModifiers:c,selectedDays:T,buttonProps:W,divProps:F});return V.isHidden?th.jsx("div",{role:"gridcell"}):V.isButton?th.jsx(t_,tu({name:"day",ref:K},V.buttonProps)):th.jsx("div",tu({},V.divProps))}function nc(e){var t=e.number,n=e.dates,r=tk(),o=r.onWeekNumberClick,i=r.styles,a=r.classNames,l=r.locale,c=r.labels.labelWeekNumber,s=(0,r.formatters.formatWeekNumber)(Number(t),{locale:l});if(!o)return th.jsx("span",{className:a.weeknumber,style:i.weeknumber,children:s});var u=c(Number(t),{locale:l});return th.jsx(t_,{name:"week-number","aria-label":u,className:a.weeknumber,style:i.weeknumber,onClick:function(e){o(t,n,e)},children:s})}function ns(e){var t,n,r,o=tk(),i=o.styles,a=o.classNames,l=o.showWeekNumber,c=o.components,s=null!==(t=null==c?void 0:c.Day)&&void 0!==t?t:nl,u=null!==(n=null==c?void 0:c.WeekNumber)&&void 0!==n?n:nc;return l&&(r=th.jsx("td",{className:a.cell,style:i.cell,children:th.jsx(u,{number:e.weekNumber,dates:e.dates})})),th.jsxs("tr",{className:a.row,style:i.row,children:[r,e.dates.map(function(t){return th.jsx("td",{className:a.cell,style:i.cell,role:"presentation",children:th.jsx(s,{displayMonth:e.displayMonth,date:t})},function(e){return(0,ei.Z)(1,arguments),Math.floor(function(e){return(0,ei.Z)(1,arguments),(0,eo.Z)(e).getTime()}(e)/1e3)}(t))})]})}function nu(e,t,n){for(var r=(null==n?void 0:n.ISOWeek)?ts(t):tc(t,n),o=(null==n?void 0:n.ISOWeek)?tn(e):tt(e,n),i=ti(r,o),a=[],l=0;l<=i;l++)a.push((0,eh.Z)(o,l));return a.reduce(function(e,t){var r=(null==n?void 0:n.ISOWeek)?function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e);return Math.round((tn(t).getTime()-(function(e){(0,ei.Z)(1,arguments);var t=function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getFullYear(),r=new Date(0);r.setFullYear(n+1,0,4),r.setHours(0,0,0,0);var o=tn(r),i=new Date(0);i.setFullYear(n,0,4),i.setHours(0,0,0,0);var a=tn(i);return t.getTime()>=o.getTime()?n+1:t.getTime()>=a.getTime()?n:n-1}(e),n=new Date(0);return n.setFullYear(t,0,4),n.setHours(0,0,0,0),tn(n)})(t).getTime())/6048e5)+1}(t):function(e,t){(0,ei.Z)(1,arguments);var n=(0,eo.Z)(e);return Math.round((tt(n,t).getTime()-(function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,c,s,u=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:ek.firstWeekContainsDate)&&void 0!==r?r:null===(c=ek.locale)||void 0===c?void 0:null===(s=c.options)||void 0===s?void 0:s.firstWeekContainsDate)&&void 0!==n?n:1),d=function(e,t){(0,ei.Z)(1,arguments);var n,r,o,i,a,l,c,s,u=(0,eo.Z)(e),d=u.getFullYear(),f=(0,em.Z)(null!==(n=null!==(r=null!==(o=null!==(i=null==t?void 0:t.firstWeekContainsDate)&&void 0!==i?i:null==t?void 0:null===(a=t.locale)||void 0===a?void 0:null===(l=a.options)||void 0===l?void 0:l.firstWeekContainsDate)&&void 0!==o?o:ek.firstWeekContainsDate)&&void 0!==r?r:null===(c=ek.locale)||void 0===c?void 0:null===(s=c.options)||void 0===s?void 0:s.firstWeekContainsDate)&&void 0!==n?n:1);if(!(f>=1&&f<=7))throw RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var p=new Date(0);p.setFullYear(d+1,0,f),p.setHours(0,0,0,0);var h=tt(p,t),m=new Date(0);m.setFullYear(d,0,f),m.setHours(0,0,0,0);var g=tt(m,t);return u.getTime()>=h.getTime()?d+1:u.getTime()>=g.getTime()?d:d-1}(e,t),f=new Date(0);return f.setFullYear(d,0,u),f.setHours(0,0,0,0),tt(f,t)})(n,t).getTime())/6048e5)+1}(t,n),o=e.find(function(e){return e.weekNumber===r});return o?o.dates.push(t):e.push({weekNumber:r,dates:[t]}),e},[])}function nd(e){var t,n,r,o=tk(),i=o.locale,a=o.classNames,l=o.styles,c=o.hideHead,s=o.fixedWeeks,u=o.components,d=o.weekStartsOn,f=o.firstWeekContainsDate,p=o.ISOWeek,h=function(e,t){var n=nu(ec(e),e3(e),t);if(null==t?void 0:t.useFixedWeeks){var r=function(e,t){return(0,ei.Z)(1,arguments),function(e,t,n){(0,ei.Z)(2,arguments);var r=tt(e,n),o=tt(t,n);return Math.round((r.getTime()-eD(r)-(o.getTime()-eD(o)))/6048e5)}(function(e){(0,ei.Z)(1,arguments);var t=(0,eo.Z)(e),n=t.getMonth();return t.setFullYear(t.getFullYear(),n+1,0),t.setHours(0,0,0,0),t}(e),ec(e),t)+1}(e,t);if(r<6){var o=n[n.length-1],i=o.dates[o.dates.length-1],a=ta(i,6-r),l=nu(ta(i,1),a,t);n.push.apply(n,l)}}return n}(e.displayMonth,{useFixedWeeks:!!s,ISOWeek:p,locale:i,weekStartsOn:d,firstWeekContainsDate:f}),m=null!==(t=null==u?void 0:u.Head)&&void 0!==t?t:tF,g=null!==(n=null==u?void 0:u.Row)&&void 0!==n?n:ns,v=null!==(r=null==u?void 0:u.Footer)&&void 0!==r?r:tz;return th.jsxs("table",{id:e.id,className:a.table,style:l.table,role:"grid","aria-labelledby":e["aria-labelledby"],children:[!c&&th.jsx(m,{}),th.jsx("tbody",{className:a.tbody,style:l.tbody,children:h.map(function(t){return th.jsx(g,{displayMonth:e.displayMonth,dates:t.dates,weekNumber:t.weekNumber},t.weekNumber)})}),th.jsx(v,{displayMonth:e.displayMonth})]})}var nf="undefined"!=typeof window&&window.document&&window.document.createElement?d.useLayoutEffect:d.useEffect,np=!1,nh=0;function nm(){return"react-day-picker-".concat(++nh)}function ng(e){var t,n,r,o,i,a,l,c,s=tk(),u=s.dir,f=s.classNames,p=s.styles,h=s.components,m=tI().displayMonths,g=(r=null!=(t=s.id?"".concat(s.id,"-").concat(e.displayIndex):void 0)?t:np?nm():null,i=(o=(0,d.useState)(r))[0],a=o[1],nf(function(){null===i&&a(nm())},[]),(0,d.useEffect)(function(){!1===np&&(np=!0)},[]),null!==(n=null!=t?t:i)&&void 0!==n?n:void 0),v=s.id?"".concat(s.id,"-grid-").concat(e.displayIndex):void 0,y=[f.month],b=p.month,x=0===e.displayIndex,w=e.displayIndex===m.length-1,S=!x&&!w;"rtl"===u&&(w=(l=[x,w])[0],x=l[1]),x&&(y.push(f.caption_start),b=tu(tu({},b),p.caption_start)),w&&(y.push(f.caption_end),b=tu(tu({},b),p.caption_end)),S&&(y.push(f.caption_between),b=tu(tu({},b),p.caption_between));var k=null!==(c=null==h?void 0:h.Caption)&&void 0!==c?c:tL;return th.jsxs("div",{className:y.join(" "),style:b,children:[th.jsx(k,{id:g,displayMonth:e.displayMonth,displayIndex:e.displayIndex}),th.jsx(nd,{id:v,"aria-labelledby":g,displayMonth:e.displayMonth})]},e.displayIndex)}function nv(e){var t=tk(),n=t.classNames,r=t.styles;return th.jsx("div",{className:n.months,style:r.months,children:e.children})}function ny(e){var t,n,r=e.initialProps,o=tk(),i=nn(),a=tI(),l=(0,d.useState)(!1),c=l[0],s=l[1];(0,d.useEffect)(function(){o.initialFocus&&i.focusTarget&&(c||(i.focus(i.focusTarget),s(!0)))},[o.initialFocus,c,i.focus,i.focusTarget,i]);var u=[o.classNames.root,o.className];o.numberOfMonths>1&&u.push(o.classNames.multiple_months),o.showWeekNumber&&u.push(o.classNames.with_weeknumber);var f=tu(tu({},o.styles.root),o.style),p=Object.keys(r).filter(function(e){return e.startsWith("data-")}).reduce(function(e,t){var n;return tu(tu({},e),((n={})[t]=r[t],n))},{}),h=null!==(n=null===(t=r.components)||void 0===t?void 0:t.Months)&&void 0!==n?n:nv;return th.jsx("div",tu({className:u.join(" "),style:f,dir:o.dir,id:o.id,nonce:r.nonce,title:r.title,lang:r.lang},p,{children:th.jsx(h,{children:a.displayMonths.map(function(e,t){return th.jsx(ng,{displayIndex:t,displayMonth:e},t)})})}))}function nb(e){var t=e.children,n=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,r=Object.getOwnPropertySymbols(e);ot.indexOf(r[o])&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]]);return n}(e,["children"]);return th.jsx(tS,{initialProps:n,children:th.jsx(tM,{children:th.jsx(no,{initialProps:n,children:th.jsx(tW,{initialProps:n,children:th.jsx(tG,{initialProps:n,children:th.jsx(t8,{children:th.jsx(nt,{children:t})})})})})})})}function nx(e){return th.jsx(nb,tu({},e,{children:th.jsx(ny,{initialProps:e})}))}let nw=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"}))},nS=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"}))},nk=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M4.83582 12L11.0429 18.2071L12.4571 16.7929L7.66424 12L12.4571 7.20712L11.0429 5.79291L4.83582 12ZM10.4857 12L16.6928 18.2071L18.107 16.7929L13.3141 12L18.107 7.20712L16.6928 5.79291L10.4857 12Z"}))},nE=e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M19.1642 12L12.9571 5.79291L11.5429 7.20712L16.3358 12L11.5429 16.7929L12.9571 18.2071L19.1642 12ZM13.5143 12L7.30722 5.79291L5.89301 7.20712L10.6859 12L5.89301 16.7929L7.30722 18.2071L13.5143 12Z"}))};var nC=n(84264);n(41649);var nO=n(1526),nj=n(7084),nP=n(26898);let nN={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-1",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-1.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-lg"},xl:{paddingX:"px-3.5",paddingY:"py-1.5",fontSize:"text-xl"}},nM={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},nI={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},nR={[nj.wu.Increase]:{bgColor:(0,eJ.bM)(nj.fr.Emerald,nP.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Emerald,nP.K.text).textColor},[nj.wu.ModerateIncrease]:{bgColor:(0,eJ.bM)(nj.fr.Emerald,nP.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Emerald,nP.K.text).textColor},[nj.wu.Decrease]:{bgColor:(0,eJ.bM)(nj.fr.Rose,nP.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Rose,nP.K.text).textColor},[nj.wu.ModerateDecrease]:{bgColor:(0,eJ.bM)(nj.fr.Rose,nP.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Rose,nP.K.text).textColor},[nj.wu.Unchanged]:{bgColor:(0,eJ.bM)(nj.fr.Orange,nP.K.background).bgColor,textColor:(0,eJ.bM)(nj.fr.Orange,nP.K.text).textColor}},nT={[nj.wu.Increase]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.0001 7.82843V20H11.0001V7.82843L5.63614 13.1924L4.22192 11.7782L12.0001 4L19.7783 11.7782L18.3641 13.1924L13.0001 7.82843Z"}))},[nj.wu.ModerateIncrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M16.0037 9.41421L7.39712 18.0208L5.98291 16.6066L14.5895 8H7.00373V6H18.0037V17H16.0037V9.41421Z"}))},[nj.wu.Decrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M13.0001 16.1716L18.3641 10.8076L19.7783 12.2218L12.0001 20L4.22192 12.2218L5.63614 10.8076L11.0001 16.1716V4H13.0001V16.1716Z"}))},[nj.wu.ModerateDecrease]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M14.5895 16.0032L5.98291 7.39664L7.39712 5.98242L16.0037 14.589V7.00324H18.0037V18.0032H7.00373V16.0032H14.5895Z"}))},[nj.wu.Unchanged]:e=>{var t=(0,u._T)(e,[]);return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),d.createElement("path",{d:"M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"}))}},nA=(0,eJ.fn)("BadgeDelta");d.forwardRef((e,t)=>{let{deltaType:n=nj.wu.Increase,isIncreasePositive:r=!0,size:o=nj.u8.SM,tooltip:i,children:a,className:l}=e,c=(0,u._T)(e,["deltaType","isIncreasePositive","size","tooltip","children","className"]),s=nT[n],f=(0,eJ.Fo)(n,r),p=a?nM:nN,{tooltipProps:h,getReferenceProps:m}=(0,nO.l)();return d.createElement("span",Object.assign({ref:(0,eJ.lq)([t,h.refs.setReference]),className:(0,es.q)(nA("root"),"w-max flex-shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-full bg-opacity-20 dark:bg-opacity-25",nR[f].bgColor,nR[f].textColor,p[o].paddingX,p[o].paddingY,p[o].fontSize,l)},m,c),d.createElement(nO.Z,Object.assign({text:i},h)),d.createElement(s,{className:(0,es.q)(nA("icon"),"shrink-0",a?(0,es.q)("-ml-1 mr-1.5"):nI[o].height,nI[o].width)}),a?d.createElement("p",{className:(0,es.q)(nA("text"),"text-sm whitespace-nowrap")},a):null)}).displayName="BadgeDelta";var n_=n(47323);let nD=e=>{var{onClick:t,icon:n}=e,r=(0,u._T)(e,["onClick","icon"]);return d.createElement("button",Object.assign({type:"button",className:(0,es.q)("flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle select-none dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content")},r),d.createElement(n_.Z,{onClick:t,icon:n,variant:"simple",color:"slate",size:"sm"}))};function nZ(e){var{mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:a,enableYearNavigation:l,classNames:c,weekStartsOn:s=0}=e,f=(0,u._T)(e,["mode","defaultMonth","selected","onSelect","locale","disabled","enableYearNavigation","classNames","weekStartsOn"]);return d.createElement(nx,Object.assign({showOutsideDays:!0,mode:t,defaultMonth:n,selected:r,onSelect:o,locale:i,disabled:a,weekStartsOn:s,classNames:Object.assign({months:"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",month:"space-y-4",caption:"flex justify-center pt-2 relative items-center",caption_label:"text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium",nav:"space-x-1 flex items-center",nav_button:"flex items-center justify-center p-1 h-7 w-7 outline-none focus:ring-2 transition duration-100 border border-tremor-border dark:border-dark-tremor-border hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted rounded-tremor-small focus:border-tremor-brand-subtle dark:focus:border-dark-tremor-brand-subtle focus:ring-tremor-brand-muted dark:focus:ring-dark-tremor-brand-muted text-tremor-content-subtle dark:text-dark-tremor-content-subtle hover:text-tremor-content dark:hover:text-dark-tremor-content",nav_button_previous:"absolute left-1",nav_button_next:"absolute right-1",table:"w-full border-collapse space-y-1",head_row:"flex",head_cell:"w-9 font-normal text-center text-tremor-content-subtle dark:text-dark-tremor-content-subtle",row:"flex w-full mt-0.5",cell:"text-center p-0 relative focus-within:relative text-tremor-default text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis",day:"h-9 w-9 p-0 hover:bg-tremor-background-subtle dark:hover:bg-dark-tremor-background-subtle outline-tremor-brand dark:outline-dark-tremor-brand rounded-tremor-default",day_today:"font-bold",day_selected:"aria-selected:bg-tremor-background-emphasis aria-selected:text-tremor-content-inverted dark:aria-selected:bg-dark-tremor-background-emphasis dark:aria-selected:text-dark-tremor-content-inverted ",day_disabled:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle disabled:hover:bg-transparent",day_outside:"text-tremor-content-subtle dark:text-dark-tremor-content-subtle"},c),components:{IconLeft:e=>{var t=(0,u._T)(e,[]);return d.createElement(nw,Object.assign({className:"h-4 w-4"},t))},IconRight:e=>{var t=(0,u._T)(e,[]);return d.createElement(nS,Object.assign({className:"h-4 w-4"},t))},Caption:e=>{var t=(0,u._T)(e,[]);let{goToMonth:n,nextMonth:r,previousMonth:o,currentMonth:a}=tI();return d.createElement("div",{className:"flex justify-between items-center"},d.createElement("div",{className:"flex items-center space-x-1"},l&&d.createElement(nD,{onClick:()=>a&&n(tl(a,-1)),icon:nk}),d.createElement(nD,{onClick:()=>o&&n(o),icon:nw})),d.createElement(nC.Z,{className:"text-tremor-default tabular-nums capitalize text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis font-medium"},eQ(t.displayMonth,"LLLL yyy",{locale:i})),d.createElement("div",{className:"flex items-center space-x-1"},d.createElement(nD,{onClick:()=>r&&n(r),icon:nS}),l&&d.createElement(nD,{onClick:()=>a&&n(tl(a,1)),icon:nE})))}}},f))}nZ.displayName="DateRangePicker",n(27281);var nL=n(57365),nz=n(44140);let nB=el(),nF=d.forwardRef((e,t)=>{var n,r;let{value:o,defaultValue:i,onValueChange:a,enableSelect:l=!0,minDate:c,maxDate:s,placeholder:f="Select range",selectPlaceholder:p="Select range",disabled:h=!1,locale:m=eV,enableClear:g=!0,displayFormat:v,children:y,className:b,enableYearNavigation:x=!1,weekStartsOn:w=0,disabledDates:S}=e,k=(0,u._T)(e,["value","defaultValue","onValueChange","enableSelect","minDate","maxDate","placeholder","selectPlaceholder","disabled","locale","enableClear","displayFormat","children","className","enableYearNavigation","weekStartsOn","disabledDates"]),[E,C]=(0,nz.Z)(i,o),[O,j]=(0,d.useState)(!1),[P,N]=(0,d.useState)(!1),M=(0,d.useMemo)(()=>{let e=[];return c&&e.push({before:c}),s&&e.push({after:s}),[...e,...null!=S?S:[]]},[c,s,S]),I=(0,d.useMemo)(()=>{let e=new Map;return y?d.Children.forEach(y,t=>{var n;e.set(t.props.value,{text:null!==(n=(0,eu.qg)(t))&&void 0!==n?n:t.props.value,from:t.props.from,to:t.props.to})}):e6.forEach(t=>{e.set(t.value,{text:t.text,from:t.from,to:nB})}),e},[y]),R=(0,d.useMemo)(()=>{if(y)return(0,eu.sl)(y);let e=new Map;return e6.forEach(t=>e.set(t.value,t.text)),e},[y]),T=(null==E?void 0:E.selectValue)||"",A=e1(null==E?void 0:E.from,c,T,I),_=e2(null==E?void 0:E.to,s,T,I),D=A||_?e4(A,_,m,v):f,Z=ec(null!==(r=null!==(n=null!=_?_:A)&&void 0!==n?n:s)&&void 0!==r?r:nB),L=g&&!h;return d.createElement("div",Object.assign({ref:t,className:(0,es.q)("w-full min-w-[10rem] relative flex justify-between text-tremor-default max-w-sm shadow-tremor-input dark:shadow-dark-tremor-input rounded-tremor-default",b)},k),d.createElement(J,{as:"div",className:(0,es.q)("w-full",l?"rounded-l-tremor-default":"rounded-tremor-default",O&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10")},d.createElement("div",{className:"relative w-full"},d.createElement(J.Button,{onFocus:()=>j(!0),onBlur:()=>j(!1),disabled:h,className:(0,es.q)("w-full outline-none text-left whitespace-nowrap truncate focus:ring-2 transition duration-100 rounded-l-tremor-default flex flex-nowrap border pl-3 py-2","rounded-l-tremor-default border-tremor-border text-tremor-content-emphasis focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",l?"rounded-l-tremor-default":"rounded-tremor-default",L?"pr-8":"pr-4",(0,eu.um)((0,eu.Uh)(A||_),h))},d.createElement(en,{className:(0,es.q)(e0("calendarIcon"),"flex-none shrink-0 h-5 w-5 -ml-0.5 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle"),"aria-hidden":"true"}),d.createElement("p",{className:"truncate"},D)),L&&A?d.createElement("button",{type:"button",className:(0,es.q)("absolute outline-none inset-y-0 right-0 flex items-center transition duration-100 mr-4"),onClick:e=>{e.preventDefault(),null==a||a({}),C({})}},d.createElement(er.Z,{className:(0,es.q)(e0("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null),d.createElement(ee.u,{className:"absolute z-10 min-w-min left-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},d.createElement(J.Panel,{focus:!0,className:(0,es.q)("divide-y overflow-y-auto outline-none rounded-tremor-default p-3 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},d.createElement(nZ,Object.assign({mode:"range",showOutsideDays:!0,defaultMonth:Z,selected:{from:A,to:_},onSelect:e=>{null==a||a({from:null==e?void 0:e.from,to:null==e?void 0:e.to}),C({from:null==e?void 0:e.from,to:null==e?void 0:e.to})},locale:m,disabled:M,enableYearNavigation:x,classNames:{day_range_middle:(0,es.q)("!rounded-none aria-selected:!bg-tremor-background-subtle aria-selected:dark:!bg-dark-tremor-background-subtle aria-selected:!text-tremor-content aria-selected:dark:!bg-dark-tremor-background-subtle"),day_range_start:"rounded-r-none rounded-l-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted",day_range_end:"rounded-l-none rounded-r-tremor-small aria-selected:text-tremor-brand-inverted dark:aria-selected:text-dark-tremor-brand-inverted"},weekStartsOn:w},e))))),l&&d.createElement(et.R,{as:"div",className:(0,es.q)("w-48 -ml-px rounded-r-tremor-default",P&&"ring-2 ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted z-10"),value:T,onChange:e=>{let{from:t,to:n}=I.get(e),r=null!=n?n:nB;null==a||a({from:t,to:r,selectValue:e}),C({from:t,to:r,selectValue:e})},disabled:h},e=>{var t;let{value:n}=e;return d.createElement(d.Fragment,null,d.createElement(et.R.Button,{onFocus:()=>N(!0),onBlur:()=>N(!1),className:(0,es.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-r-tremor-default transition duration-100 border px-4 py-2","border-tremor-border shadow-tremor-input text-tremor-content-emphasis focus:border-tremor-brand-subtle","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle",(0,eu.um)((0,eu.Uh)(n),h))},n&&null!==(t=R.get(n))&&void 0!==t?t:p),d.createElement(ee.u,{className:"absolute z-10 w-full inset-x-0 right-0",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},d.createElement(et.R.Options,{className:(0,es.q)("divide-y overflow-y-auto outline-none border my-1","shadow-tremor-dropdown bg-tremor-background border-tremor-border divide-tremor-border rounded-tremor-default","dark:shadow-dark-tremor-dropdown dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border")},null!=y?y:e6.map(e=>d.createElement(nL.Z,{key:e.value,value:e.value},e.text)))))}))});nF.displayName="DateRangePicker"},92414:function(e,t,n){"use strict";n.d(t,{Z:function(){return v}});var r=n(5853),o=n(2265);n(42698),n(64016),n(8710);var i=n(33232),a=n(44140),l=n(58747);let c=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"}))};var s=n(4537),u=n(28517),d=n(33044);let f=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:"100%",height:"100%",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},t),o.createElement("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),o.createElement("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))};var p=n(65954),h=n(1153),m=n(96398);let g=(0,h.fn)("MultiSelect"),v=o.forwardRef((e,t)=>{let{defaultValue:n,value:h,onValueChange:v,placeholder:y="Select...",placeholderSearch:b="Search",disabled:x=!1,icon:w,children:S,className:k}=e,E=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","placeholderSearch","disabled","icon","children","className"]),[C,O]=(0,a.Z)(n,h),{reactElementChildren:j,optionsAvailable:P}=(0,o.useMemo)(()=>{let e=o.Children.toArray(S).filter(o.isValidElement);return{reactElementChildren:e,optionsAvailable:(0,m.n0)("",e)}},[S]),[N,M]=(0,o.useState)(""),I=(null!=C?C:[]).length>0,R=(0,o.useMemo)(()=>N?(0,m.n0)(N,j):P,[N,j,P]),T=()=>{M("")};return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:C,value:C,onChange:e=>{null==v||v(e),O(e)},disabled:x,className:(0,p.q)("w-full min-w-[10rem] relative text-tremor-default",k)},E,{multiple:!0}),e=>{let{value:t}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,p.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-1.5","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",w?"pl-11 -ml-0.5":"pl-3",(0,m.um)(t.length>0,x))},w&&o.createElement("span",{className:(0,p.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(w,{className:(0,p.q)(g("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("div",{className:"h-6 flex items-center"},t.length>0?o.createElement("div",{className:"flex flex-nowrap overflow-x-scroll [&::-webkit-scrollbar]:hidden [scrollbar-width:none] gap-x-1 mr-5 -ml-1.5 relative"},P.filter(e=>t.includes(e.props.value)).map((e,n)=>{var r;return o.createElement("div",{key:n,className:(0,p.q)("max-w-[100px] lg:max-w-[200px] flex justify-center items-center pl-2 pr-1.5 py-1 font-medium","rounded-tremor-small","bg-tremor-background-muted dark:bg-dark-tremor-background-muted","bg-tremor-background-subtle dark:bg-dark-tremor-background-subtle","text-tremor-content-default dark:text-dark-tremor-content-default","text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis")},o.createElement("div",{className:"text-xs truncate "},null!==(r=e.props.children)&&void 0!==r?r:e.props.value),o.createElement("div",{onClick:n=>{n.preventDefault();let r=t.filter(t=>t!==e.props.value);null==v||v(r),O(r)}},o.createElement(f,{className:(0,p.q)(g("clearIconItem"),"cursor-pointer rounded-tremor-full w-3.5 h-3.5 ml-2","text-tremor-content-subtle hover:text-tremor-content","dark:text-dark-tremor-content-subtle dark:hover:text-tremor-content")})))})):o.createElement("span",null,y)),o.createElement("span",{className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-2.5")},o.createElement(l.Z,{className:(0,p.q)(g("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),I&&!x?o.createElement("button",{type:"button",className:(0,p.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),O([]),null==v||v([])}},o.createElement(s.Z,{className:(0,p.q)(g("clearIconAllItems"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,p.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},o.createElement("div",{className:(0,p.q)("flex items-center w-full px-2.5","bg-tremor-background-muted","dark:bg-dark-tremor-background-muted")},o.createElement("span",null,o.createElement(c,{className:(0,p.q)("flex-none w-4 h-4 mr-2","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("input",{name:"search",type:"input",autoComplete:"off",placeholder:b,className:(0,p.q)("w-full focus:outline-none focus:ring-none bg-transparent text-tremor-default py-2","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis"),onKeyDown:e=>{"Space"===e.code&&""!==e.target.value&&e.stopPropagation()},onChange:e=>M(e.target.value),value:N})),o.createElement(i.Z.Provider,Object.assign({},{onBlur:{handleResetSearch:T}},{value:{selectedValue:t}}),R))))})});v.displayName="MultiSelect"},46030:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var r=n(5853);n(42698),n(64016),n(8710);var o=n(33232),i=n(2265),a=n(65954),l=n(1153),c=n(28517);let s=(0,l.fn)("MultiSelectItem"),u=i.forwardRef((e,t)=>{let{value:n,className:u,children:d}=e,f=(0,r._T)(e,["value","className","children"]),{selectedValue:p}=(0,i.useContext)(o.Z),h=(0,l.NZ)(n,p);return i.createElement(c.R.Option,Object.assign({className:(0,a.q)(s("root"),"flex justify-start items-center cursor-default text-tremor-default p-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",u),ref:t,key:n,value:n},f),i.createElement("input",{type:"checkbox",className:(0,a.q)(s("checkbox"),"flex-none focus:ring-none focus:outline-none cursor-pointer mr-2.5","accent-tremor-brand","dark:accent-dark-tremor-brand"),checked:h,readOnly:!0}),i.createElement("span",{className:"whitespace-nowrap truncate"},null!=d?d:n))});u.displayName="MultiSelectItem"},30150:function(e,t,n){"use strict";n.d(t,{Z:function(){return f}});var r=n(5853),o=n(2265);let i=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),o.createElement("path",{d:"M12 4v16m8-8H4"}))},a=e=>{var t=(0,r._T)(e,[]);return o.createElement("svg",Object.assign({},t,{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2.5"}),o.createElement("path",{d:"M20 12H4"}))};var l=n(65954),c=n(1153),s=n(69262);let u="flex mx-auto text-tremor-content-subtle dark:text-dark-tremor-content-subtle",d="cursor-pointer hover:text-tremor-content dark:hover:text-dark-tremor-content",f=o.forwardRef((e,t)=>{let{onSubmit:n,enableStepper:f=!0,disabled:p,onValueChange:h,onChange:m}=e,g=(0,r._T)(e,["onSubmit","enableStepper","disabled","onValueChange","onChange"]),v=(0,o.useRef)(null),[y,b]=o.useState(!1),x=o.useCallback(()=>{b(!0)},[]),w=o.useCallback(()=>{b(!1)},[]),[S,k]=o.useState(!1),E=o.useCallback(()=>{k(!0)},[]),C=o.useCallback(()=>{k(!1)},[]);return o.createElement(s.Z,Object.assign({type:"number",ref:(0,c.lq)([v,t]),disabled:p,makeInputClassName:(0,c.fn)("NumberInput"),onKeyDown:e=>{var t;if("Enter"===e.key&&!e.ctrlKey&&!e.altKey&&!e.shiftKey){let e=null===(t=v.current)||void 0===t?void 0:t.value;null==n||n(parseFloat(null!=e?e:""))}"ArrowDown"===e.key&&x(),"ArrowUp"===e.key&&E()},onKeyUp:e=>{"ArrowDown"===e.key&&w(),"ArrowUp"===e.key&&C()},onChange:e=>{p||(null==h||h(parseFloat(e.target.value)),null==m||m(e))},stepper:f?o.createElement("div",{className:(0,l.q)("flex justify-center align-middle")},o.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;p||(null===(e=v.current)||void 0===e||e.stepDown(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!p&&d,u,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},o.createElement(a,{"data-testid":"step-down",className:(y?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"})),o.createElement("div",{tabIndex:-1,onClick:e=>e.preventDefault(),onMouseDown:e=>e.preventDefault(),onTouchStart:e=>{e.cancelable&&e.preventDefault()},onMouseUp:()=>{var e,t;p||(null===(e=v.current)||void 0===e||e.stepUp(),null===(t=v.current)||void 0===t||t.dispatchEvent(new Event("input",{bubbles:!0})))},className:(0,l.q)(!p&&d,u,"group py-[10px] px-2.5 border-l border-tremor-border dark:border-dark-tremor-border")},o.createElement(i,{"data-testid":"step-up",className:(S?"scale-95":"")+" h-4 w-4 duration-75 transition group-active:scale-95"}))):null},g))});f.displayName="NumberInput"},27281:function(e,t,n){"use strict";n.d(t,{Z:function(){return h}});var r=n(5853),o=n(2265),i=n(58747),a=n(4537),l=n(65954),c=n(1153),s=n(96398),u=n(28517),d=n(33044),f=n(44140);let p=(0,c.fn)("Select"),h=o.forwardRef((e,t)=>{let{defaultValue:n,value:c,onValueChange:h,placeholder:m="Select...",disabled:g=!1,icon:v,enableClear:y=!0,children:b,className:x}=e,w=(0,r._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","children","className"]),[S,k]=(0,f.Z)(n,c),E=(0,o.useMemo)(()=>{let e=o.Children.toArray(b).filter(o.isValidElement);return(0,s.sl)(e)},[b]);return o.createElement(u.R,Object.assign({as:"div",ref:t,defaultValue:S,value:S,onChange:e=>{null==h||h(e),k(e)},disabled:g,className:(0,l.q)("w-full min-w-[10rem] relative text-tremor-default",x)},w),e=>{var t;let{value:n}=e;return o.createElement(o.Fragment,null,o.createElement(u.R.Button,{className:(0,l.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",v?"pl-10":"pl-3",(0,s.um)((0,s.Uh)(n),g))},v&&o.createElement("span",{className:(0,l.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},o.createElement(v,{className:(0,l.q)(p("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),o.createElement("span",{className:"w-[90%] block truncate"},n&&null!==(t=E.get(n))&&void 0!==t?t:m),o.createElement("span",{className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-3")},o.createElement(i.Z,{className:(0,l.q)(p("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),y&&S?o.createElement("button",{type:"button",className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),k(""),null==h||h("")}},o.createElement(a.Z,{className:(0,l.q)(p("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,o.createElement(d.u,{className:"absolute z-10 w-full",enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},o.createElement(u.R.Options,{className:(0,l.q)("divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] left-0 border my-1","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},b)))})});h.displayName="Select"},57365:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var r=n(5853),o=n(2265),i=n(28517),a=n(65954);let l=(0,n(1153).fn)("SelectItem"),c=o.forwardRef((e,t)=>{let{value:n,icon:c,className:s,children:u}=e,d=(0,r._T)(e,["value","icon","className","children"]);return o.createElement(i.R.Option,Object.assign({className:(0,a.q)(l("root"),"flex justify-start items-center cursor-default text-tremor-default px-2.5 py-2.5","ui-active:bg-tremor-background-muted ui-active:text-tremor-content-strong ui-selected:text-tremor-content-strong ui-selected:bg-tremor-background-muted text-tremor-content-emphasis","dark:ui-active:bg-dark-tremor-background-muted dark:ui-active:text-dark-tremor-content-strong dark:ui-selected:text-dark-tremor-content-strong dark:ui-selected:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",s),ref:t,key:n,value:n},d),c&&o.createElement(c,{className:(0,a.q)(l("icon"),"flex-none w-5 h-5 mr-1.5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),o.createElement("span",{className:"whitespace-nowrap truncate"},null!=u?u:n))});c.displayName="SelectItem"},92858:function(e,t,n){"use strict";n.d(t,{Z:function(){return M}});var r=n(5853),o=n(2265),i=n(62963),a=n(90945),l=n(13323),c=n(17684),s=n(80004),u=n(93689),d=n(38198),f=n(47634),p=n(56314),h=n(27847),m=n(64518);let g=(0,o.createContext)(null),v=Object.assign((0,h.yV)(function(e,t){let n=(0,c.M)(),{id:r="headlessui-description-".concat(n),...i}=e,a=function e(){let t=(0,o.useContext)(g);if(null===t){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return t}(),l=(0,u.T)(t);(0,m.e)(()=>a.register(r),[r,a.register]);let s={ref:l,...a.props,id:r};return(0,h.sY)({ourProps:s,theirProps:i,slot:a.slot||{},defaultTag:"p",name:a.name||"Description"})}),{});var y=n(37388);let b=(0,o.createContext)(null),x=Object.assign((0,h.yV)(function(e,t){let n=(0,c.M)(),{id:r="headlessui-label-".concat(n),passive:i=!1,...a}=e,l=function e(){let t=(0,o.useContext)(b);if(null===t){let t=Error("You used a
- Date: Sat, 29 Mar 2025 17:55:37 -0700 Subject: [PATCH 217/244] list_tool_rest_api --- litellm/proxy/_experimental/mcp_server/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 4cfed9a603..510bd6ab3b 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -224,7 +224,7 @@ if MCP_AVAILABLE: ############ MCP Server REST API Routes ################# ######################################################## @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) - async def list_tool_rest_api() -> List[ListMCPToolsRestAPIResponseObject]: + async def list_tool_rest_api(): """ List all available tools with information about the server they belong to. From 8ea6caeff0081549e3d479e22cb0c1ffe8a857e0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:06:36 -0700 Subject: [PATCH 218/244] docs fix release notes --- .../release_notes/v1.65.0-stable/index.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/my-website/release_notes/v1.65.0-stable/index.md diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md new file mode 100644 index 0000000000..5556afd0ad --- /dev/null +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -0,0 +1,75 @@ +--- +title: v1.65.0-stable - Model Context Protocol (MCP) +slug: v1.65.0-stable +date: 2025-03-30T10:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://media.licdn.com/dms/image/v2/D4D03AQGrlsJ3aqpHmQ/profile-displayphoto-shrink_400_400/B4DZSAzgP7HYAg-/0/1737327772964?e=1743638400&v=beta&t=39KOXMUFedvukiWWVPHf3qI45fuQD7lNglICwN31DrI + - name: Ishaan Jaffer + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +tags: [mcp] +hide_table_of_contents: false +--- + +LiteLLM v1.65.0 introduces significant enhancements including Model Context Protocol (MCP) tools, new models, and various performance improvements. + +## New Models / Updated Models +- Support for Vertex AI gemini-2.0-flash-lite & Google AI Studio gemini-2.0-flash-lite [PR](https://github.com/BerriAI/litellm/pull/9523) +- Support for Vertex AI Fine-Tuned LLMs [PR](https://github.com/BerriAI/litellm/pull/9542) +- Nova Canvas image generation support [PR](https://github.com/BerriAI/litellm/pull/9525) +- OpenAI gpt-4o-transcribe support [PR](https://github.com/BerriAI/litellm/pull/9517) +- Added new Vertex AI text embedding model [PR](https://github.com/BerriAI/litellm/pull/9476) +- Updated model prices and context windows [PR](https://github.com/BerriAI/litellm/pull/9459) + +## LLM Translation +- OpenAI Web Search Tool Call Support [PR](https://github.com/BerriAI/litellm/pull/9465) +- Vertex AI topLogprobs support [PR](https://github.com/BerriAI/litellm/pull/9518) +- Fixed Vertex AI multimodal embedding translation [PR](https://github.com/BerriAI/litellm/pull/9471) +- Support litellm.api_base for Vertex AI + Gemini across completion, embedding, image_generation [PR](https://github.com/BerriAI/litellm/pull/9516) +- Fixed Mistral chat transformation [PR](https://github.com/BerriAI/litellm/pull/9606) + +## Spend Tracking Improvements +- Log 'api_base' on spend logs [PR](https://github.com/BerriAI/litellm/pull/9509) +- Support for Gemini audio token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) +- Fixed OpenAI audio input token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) +- Added Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows [PR](https://github.com/BerriAI/litellm/pull/9538) +- Connected UI to "LiteLLM_DailyUserSpend" spend table [PR](https://github.com/BerriAI/litellm/pull/9603) + +## UI +- Allowed team admins to add/update/delete models on UI [PR](https://github.com/BerriAI/litellm/pull/9572) +- Show API base and model ID on request logs [PR](https://github.com/BerriAI/litellm/pull/9572) +- Allow viewing keyinfo on request logs [PR](https://github.com/BerriAI/litellm/pull/9568) +- Enabled viewing all wildcard models on /model/info [PR](https://github.com/BerriAI/litellm/pull/9473) +- Added render supports_web_search on model hub [PR](https://github.com/BerriAI/litellm/pull/9469) + +## Logging Integrations +- Fixed StandardLoggingPayload for GCS Pub Sub Logging Integration [PR](https://github.com/BerriAI/litellm/pull/9508) + +## Performance / Reliability Improvements +- LiteLLM Redis semantic caching implementation [PR](https://github.com/BerriAI/litellm/pull/9356) +- Gracefully handle exceptions when DB is having an outage [PR](https://github.com/BerriAI/litellm/pull/9533) +- Allow Pods to startup + passing /health/readiness when allow_requests_on_db_unavailable: True and DB is down [PR](https://github.com/BerriAI/litellm/pull/9569) +- Removed hard coded final usage chunk on Bedrock streaming usage [PR](https://github.com/BerriAI/litellm/pull/9512) +- Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) + +## General Improvements +- Support for exposing MCP tools on litellm proxy [PR](https://github.com/BerriAI/litellm/pull/9426) +- Support discovering Gemini, Anthropic, xAI models by calling their /v1/model endpoint [PR](https://github.com/BerriAI/litellm/pull/9530) +- Fixed route check for non-proxy admins on JWT auth [PR](https://github.com/BerriAI/litellm/pull/9454) +- Added baseline Prisma database migrations [PR](https://github.com/BerriAI/litellm/pull/9565) +- Get master key from environment, if not set [PR](https://github.com/BerriAI/litellm/pull/9617) + +## Documentation +- Fixed Predibase typo [PR](https://github.com/BerriAI/litellm/pull/9464) +- Updated README.md [PR](https://github.com/BerriAI/litellm/pull/9616) + +## Security +- Bumped next from 14.2.21 to 14.2.25 in UI dashboard [PR](https://github.com/BerriAI/litellm/pull/9458) + + + + From cb83584c0eacdb20c3620903516e6fcdf9f0f1cd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:09:54 -0700 Subject: [PATCH 219/244] add Complete Git Diff --- docs/my-website/release_notes/v1.65.0-stable/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 5556afd0ad..97aea832d2 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -72,4 +72,6 @@ LiteLLM v1.65.0 introduces significant enhancements including Model Context Prot +## Complete Git Diff +[Here's the complete git diff](https://github.com/BerriAI/litellm/compare/v1.63.14-stable.patch1...v1.65.0-stable) From 22f9a93e254b72266fd7b9512f09380ae7ff8764 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:21:42 -0700 Subject: [PATCH 220/244] docs stable release notes --- .../release_notes/v1.65.0-stable/index.md | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 97aea832d2..0a1610f788 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -17,7 +17,17 @@ hide_table_of_contents: false LiteLLM v1.65.0 introduces significant enhancements including Model Context Protocol (MCP) tools, new models, and various performance improvements. -## New Models / Updated Models +## Model Context Protocol (MCP) + +This release introduces support for centrally adding MCP servers on LiteLLM. This allows you to add MCP endpoints and your developers can `list` and `call` MCP tools through LiteLLM. + +## Custom Prompt Management + +This release allows you to connect LiteLLM to any prompt management service through our custom prompt management hooks. As proxy admin all you need to do is implement a `get_chat_completion_prompt` hook which accepts a prompt_id and prompt_variables and returns a formatted prompt. + + +## Categorized Improvements and Fixes +### New Models / Updated Models - Support for Vertex AI gemini-2.0-flash-lite & Google AI Studio gemini-2.0-flash-lite [PR](https://github.com/BerriAI/litellm/pull/9523) - Support for Vertex AI Fine-Tuned LLMs [PR](https://github.com/BerriAI/litellm/pull/9542) - Nova Canvas image generation support [PR](https://github.com/BerriAI/litellm/pull/9525) @@ -25,53 +35,51 @@ LiteLLM v1.65.0 introduces significant enhancements including Model Context Prot - Added new Vertex AI text embedding model [PR](https://github.com/BerriAI/litellm/pull/9476) - Updated model prices and context windows [PR](https://github.com/BerriAI/litellm/pull/9459) -## LLM Translation +### LLM Translation - OpenAI Web Search Tool Call Support [PR](https://github.com/BerriAI/litellm/pull/9465) - Vertex AI topLogprobs support [PR](https://github.com/BerriAI/litellm/pull/9518) - Fixed Vertex AI multimodal embedding translation [PR](https://github.com/BerriAI/litellm/pull/9471) - Support litellm.api_base for Vertex AI + Gemini across completion, embedding, image_generation [PR](https://github.com/BerriAI/litellm/pull/9516) - Fixed Mistral chat transformation [PR](https://github.com/BerriAI/litellm/pull/9606) -## Spend Tracking Improvements +### Spend Tracking Improvements - Log 'api_base' on spend logs [PR](https://github.com/BerriAI/litellm/pull/9509) - Support for Gemini audio token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) - Fixed OpenAI audio input token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) - Added Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows [PR](https://github.com/BerriAI/litellm/pull/9538) - Connected UI to "LiteLLM_DailyUserSpend" spend table [PR](https://github.com/BerriAI/litellm/pull/9603) -## UI +### UI - Allowed team admins to add/update/delete models on UI [PR](https://github.com/BerriAI/litellm/pull/9572) - Show API base and model ID on request logs [PR](https://github.com/BerriAI/litellm/pull/9572) - Allow viewing keyinfo on request logs [PR](https://github.com/BerriAI/litellm/pull/9568) - Enabled viewing all wildcard models on /model/info [PR](https://github.com/BerriAI/litellm/pull/9473) - Added render supports_web_search on model hub [PR](https://github.com/BerriAI/litellm/pull/9469) -## Logging Integrations +### Logging Integrations - Fixed StandardLoggingPayload for GCS Pub Sub Logging Integration [PR](https://github.com/BerriAI/litellm/pull/9508) -## Performance / Reliability Improvements +### Performance / Reliability Improvements - LiteLLM Redis semantic caching implementation [PR](https://github.com/BerriAI/litellm/pull/9356) - Gracefully handle exceptions when DB is having an outage [PR](https://github.com/BerriAI/litellm/pull/9533) - Allow Pods to startup + passing /health/readiness when allow_requests_on_db_unavailable: True and DB is down [PR](https://github.com/BerriAI/litellm/pull/9569) - Removed hard coded final usage chunk on Bedrock streaming usage [PR](https://github.com/BerriAI/litellm/pull/9512) - Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) -## General Improvements +### General Improvements - Support for exposing MCP tools on litellm proxy [PR](https://github.com/BerriAI/litellm/pull/9426) - Support discovering Gemini, Anthropic, xAI models by calling their /v1/model endpoint [PR](https://github.com/BerriAI/litellm/pull/9530) - Fixed route check for non-proxy admins on JWT auth [PR](https://github.com/BerriAI/litellm/pull/9454) - Added baseline Prisma database migrations [PR](https://github.com/BerriAI/litellm/pull/9565) - Get master key from environment, if not set [PR](https://github.com/BerriAI/litellm/pull/9617) -## Documentation +### Documentation - Fixed Predibase typo [PR](https://github.com/BerriAI/litellm/pull/9464) - Updated README.md [PR](https://github.com/BerriAI/litellm/pull/9616) -## Security +### Security - Bumped next from 14.2.21 to 14.2.25 in UI dashboard [PR](https://github.com/BerriAI/litellm/pull/9458) - - ## Complete Git Diff [Here's the complete git diff](https://github.com/BerriAI/litellm/compare/v1.63.14-stable.patch1...v1.65.0-stable) From 385e8bff4cc4b43384bb81a7c41881d90f91621d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:32:06 -0700 Subject: [PATCH 221/244] fix order of imports --- .../_experimental/mcp_server/mcp_server_manager.py | 14 ++++---------- litellm/proxy/_experimental/mcp_server/server.py | 2 +- litellm/proxy/proxy_server.py | 7 ++++--- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 9eb44f5597..df9ae0ea57 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -8,17 +8,11 @@ This is a Proxy import asyncio import json -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import Any, Dict, List, Optional -if TYPE_CHECKING: - from mcp import ClientSession - from mcp.client.sse import sse_client - from mcp.types import Tool as MCPTool -else: - # Provide fallback types for runtime incase `mcp` is not installed - ClientSession = None - MCPTool = object - sse_client = None +from mcp import ClientSession +from mcp.client.sse import sse_client +from mcp.types import Tool as MCPTool from litellm._logging import verbose_logger from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPSSEServer diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 510bd6ab3b..603370711f 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -265,7 +265,7 @@ if MCP_AVAILABLE: ) ) except Exception as e: - verbose_logger.error(f"Error getting tools from {server.name}: {e}") + verbose_logger.exception(f"Error getting tools from {server.name}: {e}") continue return list_tools_result diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 1a469d2c27..99d665d94c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -127,9 +127,6 @@ from litellm.litellm_core_utils.core_helpers import ( from litellm.litellm_core_utils.credential_accessor import CredentialAccessor from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler -from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( - global_mcp_server_manager, -) from litellm.proxy._experimental.mcp_server.server import router as mcp_router from litellm.proxy._experimental.mcp_server.tool_registry import ( global_mcp_tool_registry, @@ -1966,6 +1963,10 @@ class ProxyConfig: mcp_servers_config = config.get("mcp_servers", None) if mcp_servers_config: + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + global_mcp_server_manager.load_servers_from_config(mcp_servers_config) ## CREDENTIALS From 194327bb7cf9f4a5c77b1f324a0a1f1d1522f590 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:34:58 -0700 Subject: [PATCH 222/244] test fixes --- .../proxy/spend_tracking/test_spend_management_endpoints.py | 2 +- tests/mcp_tests/test_mcp_server.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 9ced59e4dc..78da1b5dda 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -651,7 +651,7 @@ class TestSpendLogsPayload: "model": "claude-3-7-sonnet-20250219", "user": "", "team_id": "", - "metadata": '{"applied_guardrails": [], "batch_models": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}', + "metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}', "cache_key": "Cache OFF", "spend": 0.01383, "total_tokens": 2598, diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index cf64d16029..2cf9193871 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -17,6 +17,7 @@ mcp_server_manager = MCPServerManager() @pytest.mark.asyncio +@pytest.mark.skip(reason="Local only test") async def test_mcp_server_manager(): mcp_server_manager.load_servers_from_config( { From 3919e242561660b83b80c54d3f8074a7fa8052e1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:36:13 -0700 Subject: [PATCH 223/244] test fix --- tests/pass_through_tests/test_mcp_routes.py | 69 +++++++-------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/tests/pass_through_tests/test_mcp_routes.py b/tests/pass_through_tests/test_mcp_routes.py index f7f0a0d17f..687efe6195 100644 --- a/tests/pass_through_tests/test_mcp_routes.py +++ b/tests/pass_through_tests/test_mcp_routes.py @@ -1,60 +1,35 @@ +# Create server parameters for stdio connection import asyncio -from openai import AsyncOpenAI -from openai.types.chat import ChatCompletionUserMessageParam +import os + +from langchain_mcp_adapters.tools import load_mcp_tools +from langchain_openai import ChatOpenAI +from langgraph.prebuilt import create_react_agent from mcp import ClientSession from mcp.client.sse import sse_client -from litellm.experimental_mcp_client.tools import ( - transform_mcp_tool_to_openai_tool, - transform_openai_tool_call_request_to_mcp_tool_call_request, -) async def main(): - # Initialize clients - client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") + model = ChatOpenAI(model="gpt-4o", api_key="sk-12") - # Connect to MCP - async with sse_client("http://localhost:4000/mcp/") as (read, write): + async with sse_client(url="http://localhost:4000/mcp/") as (read, write): async with ClientSession(read, write) as session: + # Initialize the connection + print("Initializing session") await session.initialize() - mcp_tools = await session.list_tools() - print("List of MCP tools for MCP server:", mcp_tools.tools) + print("Session initialized") - # Create message - messages = [ - ChatCompletionUserMessageParam( - content="Send an email about LiteLLM supporting MCP", role="user" - ) - ] + # Get tools + print("Loading tools") + tools = await load_mcp_tools(session) + print("Tools loaded") + print(tools) - # Request with tools - response = await client.chat.completions.create( - model="gpt-4o", - messages=messages, - tools=[ - transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools - ], - tool_choice="auto", - ) - - # Handle tool call - if response.choices[0].message.tool_calls: - tool_call = response.choices[0].message.tool_calls[0] - if tool_call: - # Convert format - mcp_call = ( - transform_openai_tool_call_request_to_mcp_tool_call_request( - openai_tool=tool_call.model_dump() - ) - ) - - # Execute tool - result = await session.call_tool( - name=mcp_call.name, arguments=mcp_call.arguments - ) - - print("Result:", result) + # # Create and run the agent + # agent = create_react_agent(model, tools) + # agent_response = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"}) -# Run it -asyncio.run(main()) +# Run the async function +if __name__ == "__main__": + asyncio.run(main()) From 10486dd17ebed082cf8833f451b3341b2c25f1be Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 18:40:58 -0700 Subject: [PATCH 224/244] fix listing mcp tools --- .../proxy/_experimental/mcp_server/server.py | 21 ++++++++++++++----- .../types/mcp_server/mcp_server_manager.py | 17 --------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 603370711f..fe1eccb048 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -8,16 +8,14 @@ from typing import Any, Dict, List, Optional, Union from anyio import BrokenResourceError from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import StreamingResponse -from pydantic import ValidationError +from pydantic import ConfigDict, ValidationError from litellm._logging import verbose_logger from litellm.constants import MCP_TOOL_NAME_PREFIX from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth -from litellm.types.mcp_server.mcp_server_manager import ( - ListMCPToolsRestAPIResponseObject, -) +from litellm.types.mcp_server.mcp_server_manager import MCPInfo from litellm.types.utils import StandardLoggingMCPToolCall from litellm.utils import client @@ -49,6 +47,19 @@ if MCP_AVAILABLE: from .sse_transport import SseServerTransport from .tool_registry import global_mcp_tool_registry + ###################################################### + ############ MCP Tools List REST API Response Object # + # Defined here because we don't want to add `mcp` as a + # required dependency for `litellm` pip package + ###################################################### + class ListMCPToolsRestAPIResponseObject(MCPTool): + """ + Object returned by the /tools/list REST API route. + """ + + mcp_info: Optional[MCPInfo] = None + model_config = ConfigDict(arbitrary_types_allowed=True) + ######################################################## ############ Initialize the MCP Server ################# ######################################################## @@ -224,7 +235,7 @@ if MCP_AVAILABLE: ############ MCP Server REST API Routes ################# ######################################################## @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) - async def list_tool_rest_api(): + async def list_tool_rest_api() -> List[ListMCPToolsRestAPIResponseObject]: """ List all available tools with information about the server they belong to. diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index 9752423e7e..aecd11aa1a 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -3,14 +3,6 @@ from typing import TYPE_CHECKING, Optional from pydantic import BaseModel, ConfigDict from typing_extensions import TypedDict -if TYPE_CHECKING: - from mcp import ClientSession - from mcp.types import Tool as MCPTool -else: - # Provide fallback types for runtime incase `mcp` is not installed - ClientSession = None - MCPTool = object - class MCPInfo(TypedDict, total=False): server_name: str @@ -22,12 +14,3 @@ class MCPSSEServer(BaseModel): url: str mcp_info: Optional[MCPInfo] = None model_config = ConfigDict(arbitrary_types_allowed=True) - - -class ListMCPToolsRestAPIResponseObject(MCPTool): - """ - Object returned by the /tools/list REST API route. - """ - - mcp_info: Optional[MCPInfo] = None - model_config = ConfigDict(arbitrary_types_allowed=True) From 46e5ebe3c7da4620474a5ad6ab0f0a1b740703f7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 19:00:26 -0700 Subject: [PATCH 225/244] docs add mcp graphic to stable release notes --- docs/my-website/img/mcp_ui.png | Bin 0 -> 95583 bytes docs/my-website/img/release_notes/mcp_ui.png | Bin 0 -> 95583 bytes .../release_notes/v1.65.0-stable/index.md | 11 ++++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 docs/my-website/img/mcp_ui.png create mode 100644 docs/my-website/img/release_notes/mcp_ui.png diff --git a/docs/my-website/img/mcp_ui.png b/docs/my-website/img/mcp_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..6731fba71be9802379cae96b9c7758fad3525811 GIT binary patch literal 95583 zcmdSBXH-+&);0_(>K#_Pyrj;0q>)Sflu^Q0?dFve|X$A@nmD;Jk9!LXG?r{2KbQO z6Rx4gMs7Pd2mEvBzA8+Wjjb?-bJv=KjooWa^KVszFZ;sCkx=fbk>;QK47KIwY5j$d zzyZsDIu~ph+iKfHHaX;V_Qih0^+!Ry7t9Slf-){#5x#KXLd|eJW2J5RMvAA~0OTNQ zdZ{*bV`O+_Vd0C6XL;Gq(Ee&_Tx@Kt)kd>?e{_GnTzia{5n+0`_qd3*O74V zQD8W%pOemqjDH_JhaM3Bee@AsxtCL3-aqn}j7)b?ak0dSGbq`Y5WXhx@yUp%57a-D zXIPtcUL`yHyN_iX%ey3NHz!EDcktEK!$B}Ty)qmEfw=dQH2&|d|98ze5+C4kadCOn zs{=*TP5*CC`(N+QCN!yY{c8}!=?j-yK7A^{{hzgfCEr0~u~;KC8eNllU6yI{za8rT z`fy-kx)GCFqN1V%+28XhoIU%9bYiSaWEs>~sd1mw7_mdUM($l61IVlgV zwd2^d$6vWAAy&92X0ugv%9=9t9lw8iy|i$~@5T>E(xAD0-4%g8hQ6aS_E2y|beOMewjgq^ynz9&zf+m!NstjgiCo3C_uf8Pwst zFIFa%`0Q*nI?>Ifmz!H5*>5^Q@@?fzO2Du*RGUjLJ>Wrwm1 z7pKkBGT#$X6yjXui|%D|`2Ft%n=hXj0vtygE-DP;85j=$Q|2v|Jo~A|#=j1iX(H$A_j)=e=nMW-alOGTcXkjf# z&Q*)0o@GPsbt4?cpzK&Aobb7-L72lL-ooE8>b=JK-l=#?w;1KU^5f^OcRW3IwrirU z+4{rNil0t8waci9Wdp-w>L>+-h>@ogWPd4%^FMRskr5F@dAiR9iJ%UHCOnZKqCL%V zzRkDw(>46?wvbNOQ=s@&W~{gSzW9%wh^dWD~O#?OH$m?KpjUK7}QP}fz6oP$W}4e zp0~Gl=5jddE9={*@J1HsdyTC#hfT$cTOx=~#EA^*;TUbjlElQiiL@|rA^UKVq?R+! z#%#;nMqDPNr7^E=SiT5#I%GrQ(f^Qn0O2^%$!sq1ac;|B zjHfF%BW`z7wwp?<3~Cj3w)=2APc&BJb;}&Pafj!#jJK*adtDnj4uS7TF7E^g{V=saySYCh0;cV?(?N*4-v-w zq_>Yuw5}8t71^u?^!QRL>0xwN8;XIzzY`@b@jj@gfEm61w2xs)QE{wt-ASn ziAHAvOX9wLv==X-MyP3XmBzou*_zCa&;{bv@Y6>rA2({c*4|IM`PNpJ4mv^RhbxL< z3DS+(y2w`>Py_>x3he@`F8KOWgE>t1Yeb}JU)oF0hj zZV7vHI!jO01cS5SwqrH-84<(F$j@?E4F>*Lw{024+%2EEGB5t-pgEz5Agk?BaEY zG1r9=)N%=@)UC0@T%%pR<~}3B<#TrCm0m?JUwT0#Bs@i&ku_v7voxfI1&vet5iXeA zLWPHQKwKN!kysOn`e(Wzzf+>s@NbzO^G*EjUe?IBmch*eKK*a>lpG&;cBmeyph(s2 zKE$l9FP09q{qyleirSTts_*?3IfApbs7(YMelL(VAYmIzm=4b$9kW5IP@pQjyGhT@JoY;(oks_Sg0meTo0!~K-;@y=^674sP$a#< zCy-T)!4^m(gc0M*L-t+-r077^@?P`fANZK#%HM@esUE5xmWlaQdxv{TeSELila2o@ z5$iR%FTiW8xcTidZOHI#2@t_6m%#^^lS2C*U?Wh5($0d6&*}!Deh0|6&?7$-Umqaf z>pop$*NPJ{A3f82Nh$O+%=6H%jyXL41z4PK^GJOzQ#M;q!^R5XF>~w8hg$a6&2n+h0Q~u><9BiwmNdjEtils zqOagqc!m=v`SlTw^;O_E{pYiElo{L zJ8SE7AWJb?z9I>$`_}*i&ct)&!{KmZJQ9H@f7l23^1JCg|6`M9;yDYBu?-e}{N}W^ zGF9p^t~TGD69Xi2yD-} z0LZ-vzh2$7L97L}xAq1RYkxs?>aEdab_bt^aU_sUk}Bc8_^Du`@Hm)t)5jyr9c)y{ z_zx}1o8Akv4PsD76xgWz(M{G#VL<@c11DQZd3kyC!;8d*U+sN7tq6R~U)zV}%0u`~ z#s69a6Oka+(|repf89CCx|6fEi!~ZtLc$#Gn(Il{3G@+GCnL5kU)25M%DrD#5^K?= zE(sWV4`KTk3{ja^3FS!px=J$wyx~JjjEEZRZk73Zc6U zn0l<#U(?<`+wT5-({ukcKE5VeBJyxq++ng&#l*A2d0ea^eEq8?H#c|tYwG5*={8LG zzq0XsldnIB)kR$`Vqe6N*d9;CStv6sK70F`%H@ho;kzZJcG{J-Ce zQa|}O6K-r{$jdwME<@VX2=3SjDi9-HAinJ3Zar}-kDNd#HJZ^Gx>`P!EmL(4=!i;n z7C&1W>}Vevj79awM@G2VoHYB|0x1jYdZ?|P56UyUgri)37w zM)iXQEw0M><&Zb&aBD!dDXu zw>iR8?|H=!mG^UKdsZXZSbH!STUwnrtdwl%Wc&E4bL8ki>2hdW?~RMJMk6d%)NSxQ zt9jWnR={X!Xix*fJBD+j=TO2wJrH&M!PL}Lg$K!VrmYwE#FWg7MGZbrU2z@Wy#VA! zN5e?M9a@^mm7e$cNJc%8N>no062bOfJ4!hr_}1BU?GwfsAgPR<#0-zI7Kf}9uhm&L z>I2ss@8P~B<m4Bju$ zPYv!d6)I|w2;y87t0|S@^dmfMR5fJkEd-3;41sp@2`TGX;S|f$@J?(^4|FuLi%S!o z>N|qZ>(_p^CabM26A-Zd5CS0-SRbr9JdO-U8f8l-v(}v+H{c4c~D0x4R9%76$KG1X7ycNh@|0$amZ9 zKb0j*{kBAU6=i5GBV7)E;F2tIMIVb2jQ-5B6sga@vRQj|b5&nIf#=Y@Iiq`XOQ5Ib zVWA6AXImF`MAVTn`S&fcSQ3v1q1`BM3TT*ZP$~Q50Tl>?HgSPpzOKjrUQU&;n23!! z0=b_T@om2=?*eq>p^TyU<&v(hR#1LX)?c4Wv?Pj)hIvfM653x&@`OK%fqDZJj}{*G zZlS)UZYv)&c#b#Kx~jB~gwx-K^wLUHrN^J^PVJL-_iQx!R7ThC&rhbyF_;Nyd>5W{@w zqcufg@Bhjf&hr|OHobbE4BHtGfI!gpqGxa5u*tTU7X~^@1$z3laJ;7I*J6Q0L5iTM zfK1l{iG!omd@QHE`Tyx%uMz?T0yx(U$DdYJA~0$FFLn$Cf6% zc{8b&2Vmm9!(4(cQRB>wu`*uEz)p_)px2V+-@^QbGSlX}Ro)o(!DgWlmz&Ggo)|2ai{iD%ZRO~~mm^lTva%u$fv|8- zXlXR-9VVD?h^(&`Nw;e1LF2V(-8wZr+=uai`Th_~DC@ER^47T(Cs}A=UYsAY9L;kN zZDeruLsx5LSRJTQ&Np9JR&&njY(fPtIoWv*KWAh@WM-ioBYGDd>4qicA4?ao&J3#G z!|}*E=#v9UN$#}+$ZMkS4LUMAnhcL-Pl1BDN7v{y(za5#Qn01c8qu}x^HPj-TfvWO zcl-i3uYuaQ6CL>`b#E?G;YYo97R!d#=DJ;zF};M1{$E3Q`fCWwyx^ggSWs)5CdpG# z{hYd}WA8P^5e3QiV$IXpgR$Y9F8OW=r6Wxvd6qNBTHCIay<9ssAJ^UJzhO31baVY! z*qblHB={;u-ICC|I3v8-0x1~ovHTD_yvChWk@pG59dBH=pyR9qvcrj+Oo&AzIs(?M~LNj<>lQnZ$yz}jI``%B_|lRs(umkA87sTII|!Ijr^i&+ssjG z^!w*Gu3z3>lXL06%4_dPPxfg9qsl$BHwf~$fY&I1Vv>#HvZdOc^iF%9-c}k2vT>>7i{5JQS1Na60B&%XKaK=%3@(#-7$bT2=DeZDE2ZJ z__^Ay>;oGzt&E9_MmA583M>K^q5L-% zeG=oro(2|`&@0UNhNBwY4SHSFmG7LRyUu*Rk?%DTFXzFGa;>SaGN#ndANH9sRtTEJKJ~44#v8gU?fR`xQs>rbeTJ?JNqFd*k2J4Nrc-CQm|JXfjLeFB+`-$5rf+z^OwfJC9( zD(5b3FaXqDRN*;O>6uP17Vz$`7r4lz0Or||@+$d|a)?3h?Tc>VQ67-6pNOOc7H1Ju2~9e_2rO!*F(7$vy7zuX-d^$&@~w6$=Z zBB3#OO_43xZG2K67k;ppnT{C5hAz*Ar#n z;C1=9xYSg4Q`Zt~a>(9R{jz;JsdRff$$g>zh>%zJ1=>f8<_yPJVCG_20g>fu)}M$z zVe8;IbY4&QnM;zKe@2v_>pKj+uX`oFy^vtYGtgktEpZY5Wl9eZCK$yHsB;}vs=rdm z5vkDcDb-%O*0qJh0_hJNZ)Bu2))grut|%dMv

Y#FJm%k-m4ok+wYSQ_FAE-9=SE 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

%$J-Y8msgy)7J+xoM0^8ja!dq| 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

Y#FJm%k-m4ok+wYSQ_FAE-9=SE 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

%$J-Y8msgy)7J+xoM0^8ja!dq| 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 ## Custom Prompt Management This release allows you to connect LiteLLM to any prompt management service through our custom prompt management hooks. As proxy admin all you need to do is implement a `get_chat_completion_prompt` hook which accepts a prompt_id and prompt_variables and returns a formatted prompt. + + + ## Categorized Improvements and Fixes ### New Models / Updated Models From d04cc6c81e5074237d5dbe71a00024c917aec3ee Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 19:03:55 -0700 Subject: [PATCH 226/244] docs release notes --- .../release_notes/v1.65.0-stable/index.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 60c0054fb1..2ce7a91ed4 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -1,5 +1,5 @@ --- -title: v1.65.0-stable - Model Context Protocol (MCP) +title: v1.65.0-stable - Model Context Protocol slug: v1.65.0-stable date: 2025-03-30T10:00:00 authors: @@ -11,7 +11,7 @@ authors: title: CTO, LiteLLM url: https://www.linkedin.com/in/reffajnaahsi/ image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg -tags: [mcp] +tags: [mcp, custom_prompt_management] hide_table_of_contents: false --- import Image from '@theme/IdealImage'; @@ -22,12 +22,19 @@ LiteLLM v1.65.0 introduces significant enhancements including Model Context Prot This release introduces support for centrally adding MCP servers on LiteLLM. This allows you to add MCP server endpoints and your developers can `list` and `call` MCP tools through LiteLLM. - +Read more about MCP [here](https://docs.litellm.ai/docs/mcp). + + ## Custom Prompt Management This release allows you to connect LiteLLM to any prompt management service through our custom prompt management hooks. As proxy admin all you need to do is implement a `get_chat_completion_prompt` hook which accepts a prompt_id and prompt_variables and returns a formatted prompt. +Read more about custom prompt management [here](https://docs.litellm.ai/docs/proxy/custom_prompt_management). + Date: Sat, 29 Mar 2025 19:08:23 -0700 Subject: [PATCH 227/244] docs release notes --- .../release_notes/v1.65.0-stable/index.md | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 2ce7a91ed4..b36eac1603 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -28,6 +28,9 @@ Read more about MCP [here](https://docs.litellm.ai/docs/mcp). img={require('../../img/release_notes/mcp_ui.png')} style={{width: '100%', display: 'block', margin: '2rem auto'}} /> +

+ Expose and use MCP servers through LiteLLM +

## Custom Prompt Management @@ -39,6 +42,9 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro img={require('../../img/custom_prompt_management.png')} style={{width: '100%', display: 'block', margin: '2rem auto'}} /> +

+ Connect LiteLLM to your prompt management service through custom hooks +

@@ -49,7 +55,6 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro - Nova Canvas image generation support [PR](https://github.com/BerriAI/litellm/pull/9525) - OpenAI gpt-4o-transcribe support [PR](https://github.com/BerriAI/litellm/pull/9517) - Added new Vertex AI text embedding model [PR](https://github.com/BerriAI/litellm/pull/9476) -- Updated model prices and context windows [PR](https://github.com/BerriAI/litellm/pull/9459) ### LLM Translation - OpenAI Web Search Tool Call Support [PR](https://github.com/BerriAI/litellm/pull/9465) @@ -62,15 +67,20 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro - Log 'api_base' on spend logs [PR](https://github.com/BerriAI/litellm/pull/9509) - Support for Gemini audio token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) - Fixed OpenAI audio input token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) -- Added Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows [PR](https://github.com/BerriAI/litellm/pull/9538) -- Connected UI to "LiteLLM_DailyUserSpend" spend table [PR](https://github.com/BerriAI/litellm/pull/9603) ### UI + +#### Model Management - Allowed team admins to add/update/delete models on UI [PR](https://github.com/BerriAI/litellm/pull/9572) +- Added render supports_web_search on model hub [PR](https://github.com/BerriAI/litellm/pull/9469) + +#### Request Logs - Show API base and model ID on request logs [PR](https://github.com/BerriAI/litellm/pull/9572) - Allow viewing keyinfo on request logs [PR](https://github.com/BerriAI/litellm/pull/9568) -- Enabled viewing all wildcard models on /model/info [PR](https://github.com/BerriAI/litellm/pull/9473) -- Added render supports_web_search on model hub [PR](https://github.com/BerriAI/litellm/pull/9469) + +#### Usage Tab +- Added Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows [PR](https://github.com/BerriAI/litellm/pull/9538) +- Connected UI to "LiteLLM_DailyUserSpend" spend table [PR](https://github.com/BerriAI/litellm/pull/9603) ### Logging Integrations - Fixed StandardLoggingPayload for GCS Pub Sub Logging Integration [PR](https://github.com/BerriAI/litellm/pull/9508) From 5c107c64dd2197cd689c0e6485a430631074748f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Sat, 29 Mar 2025 19:23:09 -0700 Subject: [PATCH 228/244] Add gemini audio input support + handle special tokens in sagemaker response (#9640) * fix(internal_user_endpoints.py): cleanup unused variables on beta endpoint no team/org split on daily user endpoint * build(model_prices_and_context_window.json): gemini-2.0-flash supports audio input * feat(gemini/transformation.py): support passing audio input to gemini * test: fix test * fix(gemini/transformation.py): support audio input as a url enables passing google cloud bucket urls * fix(gemini/transformation.py): support explicitly passing format of file * fix(gemini/transformation.py): expand support for inferred file types from url * fix(sagemaker/completion/transformation.py): fix special token error when counting sagemaker tokens * test: fix import --- .../sagemaker/completion/transformation.py | 10 +- .../llms/vertex_ai/gemini/transformation.py | 100 +++++++++++++++--- ...odel_prices_and_context_window_backup.json | 2 + litellm/proxy/_new_secret_config.yaml | 16 +-- .../internal_user_endpoints.py | 24 +---- litellm/types/llms/openai.py | 9 +- model_prices_and_context_window.json | 2 + tests/llm_translation/base_llm_unit_tests.py | 71 +++++++++++++ tests/llm_translation/test_gemini.py | 2 +- tests/llm_translation/test_gpt4o_audio.py | 7 +- .../test_key_generate_prisma.py | 6 +- 11 files changed, 191 insertions(+), 58 deletions(-) diff --git a/litellm/llms/sagemaker/completion/transformation.py b/litellm/llms/sagemaker/completion/transformation.py index 9923c0e45d..df3d028c99 100644 --- a/litellm/llms/sagemaker/completion/transformation.py +++ b/litellm/llms/sagemaker/completion/transformation.py @@ -19,6 +19,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import ( from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import ModelResponse, Usage +from litellm.utils import token_counter from ..common_utils import SagemakerError @@ -238,9 +239,12 @@ class SagemakerConfig(BaseConfig): ) ## CALCULATING USAGE - baseten charges on time, not tokens - have some mapping of cost here. - prompt_tokens = len(encoding.encode(prompt)) - completion_tokens = len( - encoding.encode(model_response["choices"][0]["message"].get("content", "")) + prompt_tokens = token_counter( + text=prompt, count_response_tokens=True + ) # doesn't apply any default token count from openai's chat template + completion_tokens = token_counter( + text=model_response["choices"][0]["message"].get("content", ""), + count_response_tokens=True, ) model_response.created = int(time.time()) diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 96b33ee187..8067d51c87 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -27,6 +27,8 @@ from litellm.types.files import ( from litellm.types.llms.openai import ( AllMessageValues, ChatCompletionAssistantMessage, + ChatCompletionAudioObject, + ChatCompletionFileObject, ChatCompletionImageObject, ChatCompletionTextObject, ) @@ -103,24 +105,53 @@ def _get_image_mime_type_from_url(url: str) -> Optional[str]: See gemini mime types: https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/image-understanding#image-requirements Supported by Gemini: - - PNG (`image/png`) - - JPEG (`image/jpeg`) - - WebP (`image/webp`) - Example: - url = https://example.com/image.jpg - Returns: image/jpeg + application/pdf + audio/mpeg + audio/mp3 + audio/wav + image/png + image/jpeg + image/webp + text/plain + video/mov + video/mpeg + video/mp4 + video/mpg + video/avi + video/wmv + video/mpegps + video/flv """ url = url.lower() - if url.endswith((".jpg", ".jpeg")): - return "image/jpeg" - elif url.endswith(".png"): - return "image/png" - elif url.endswith(".webp"): - return "image/webp" - elif url.endswith(".mp4"): - return "video/mp4" - elif url.endswith(".pdf"): - return "application/pdf" + + # Map file extensions to mime types + mime_types = { + # Images + (".jpg", ".jpeg"): "image/jpeg", + (".png",): "image/png", + (".webp",): "image/webp", + # Videos + (".mp4",): "video/mp4", + (".mov",): "video/mov", + (".mpeg", ".mpg"): "video/mpeg", + (".avi",): "video/avi", + (".wmv",): "video/wmv", + (".mpegps",): "video/mpegps", + (".flv",): "video/flv", + # Audio + (".mp3",): "audio/mp3", + (".wav",): "audio/wav", + (".mpeg",): "audio/mpeg", + # Documents + (".pdf",): "application/pdf", + (".txt",): "text/plain", + } + + # Check each extension group against the URL + for extensions, mime_type in mime_types.items(): + if any(url.endswith(ext) for ext in extensions): + return mime_type + return None @@ -152,7 +183,7 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915 _message_content = messages[msg_i].get("content") if _message_content is not None and isinstance(_message_content, list): _parts: List[PartType] = [] - for element in _message_content: + for element_idx, element in enumerate(_message_content): if ( element["type"] == "text" and "text" in element @@ -174,6 +205,41 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915 image_url=image_url, format=format ) _parts.append(_part) + elif element["type"] == "input_audio": + audio_element = cast(ChatCompletionAudioObject, element) + if audio_element["input_audio"].get("data") is not None: + _part = PartType( + inline_data=BlobType( + data=audio_element["input_audio"]["data"], + mime_type="audio/{}".format( + audio_element["input_audio"]["format"] + ), + ) + ) + _parts.append(_part) + elif element["type"] == "file": + file_element = cast(ChatCompletionFileObject, element) + file_id = file_element["file"].get("file_id") + format = file_element["file"].get("format") + + if not file_id: + continue + mime_type = format or _get_image_mime_type_from_url(file_id) + + if mime_type is not None: + _part = PartType( + file_data=FileDataType( + file_uri=file_id, + mime_type=mime_type, + ) + ) + _parts.append(_part) + else: + raise Exception( + "Unable to determine mime type for file_id: {}, set this explicitly using message[{}].content[{}].file.format".format( + file_id, msg_i, element_idx + ) + ) user_content.extend(_parts) elif ( _message_content is not None diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 4ce592d7b0..64525d660b 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -4696,6 +4696,8 @@ "supports_vision": true, "supports_response_schema": true, "supports_audio_output": true, + "supports_audio_input": true, + "supported_modalities": ["text", "image", "audio", "video"], "supports_tool_choice": true, "source": "https://ai.google.dev/pricing#2_0flash" }, diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 72edeb55ca..6c57afc76e 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -16,14 +16,18 @@ model_list: - model_name: "bedrock-nova" litellm_params: model: us.amazon.nova-pro-v1:0 + - model_name: "gemini-2.0-flash" + litellm_params: + model: gemini/gemini-2.0-flash + api_key: os.environ/GEMINI_API_KEY litellm_settings: num_retries: 0 callbacks: ["prometheus"] - json_logs: true + # json_logs: true -router_settings: - routing_strategy: usage-based-routing-v2 # 👈 KEY CHANGE - redis_host: os.environ/REDIS_HOST - redis_password: os.environ/REDIS_PASSWORD - redis_port: os.environ/REDIS_PORT +# router_settings: +# routing_strategy: usage-based-routing-v2 # 👈 KEY CHANGE +# redis_host: os.environ/REDIS_HOST +# redis_password: os.environ/REDIS_PASSWORD +# redis_port: os.environ/REDIS_PORT diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 90444013a8..8124b7fd20 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -1370,22 +1370,6 @@ async def get_user_daily_activity( default=None, description="End date in YYYY-MM-DD format", ), - group_by: List[GroupByDimension] = fastapi.Query( - default=[GroupByDimension.DATE], - description="Dimensions to group by. Can combine multiple (e.g. date,team)", - ), - view_by: Literal["team", "organization", "user"] = fastapi.Query( - default="user", - description="View spend at team/org/user level", - ), - team_id: Optional[str] = fastapi.Query( - default=None, - description="Filter by specific team", - ), - org_id: Optional[str] = fastapi.Query( - default=None, - description="Filter by specific organization", - ), model: Optional[str] = fastapi.Query( default=None, description="Filter by specific model", @@ -1408,13 +1392,13 @@ async def get_user_daily_activity( Meant to optimize querying spend data for analytics for a user. Returns: - (by date/team/org/user/model/api_key/model_group/provider) + (by date) - spend - prompt_tokens - completion_tokens - total_tokens - api_requests - - breakdown by team, organization, user, model, api_key, model_group, provider + - breakdown by model, api_key, provider """ from litellm.proxy.proxy_server import prisma_client @@ -1439,10 +1423,6 @@ async def get_user_daily_activity( } } - if team_id: - where_conditions["team_id"] = team_id - if org_id: - where_conditions["organization_id"] = org_id if model: where_conditions["model"] = model if api_key: diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 3ba5a3a4e0..6378d02888 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -505,10 +505,11 @@ class ChatCompletionDocumentObject(TypedDict): citations: Optional[CitationsObject] -class ChatCompletionFileObjectFile(TypedDict): - file_data: Optional[str] - file_id: Optional[str] - filename: Optional[str] +class ChatCompletionFileObjectFile(TypedDict, total=False): + file_data: str + file_id: str + filename: str + format: str class ChatCompletionFileObject(TypedDict): diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 4ce592d7b0..64525d660b 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -4696,6 +4696,8 @@ "supports_vision": true, "supports_response_schema": true, "supports_audio_output": true, + "supports_audio_input": true, + "supported_modalities": ["text", "image", "audio", "video"], "supports_tool_choice": true, "source": "https://ai.google.dev/pricing#2_0flash" }, diff --git a/tests/llm_translation/base_llm_unit_tests.py b/tests/llm_translation/base_llm_unit_tests.py index eb58512197..432fd848b5 100644 --- a/tests/llm_translation/base_llm_unit_tests.py +++ b/tests/llm_translation/base_llm_unit_tests.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock, Mock, patch import os import uuid import time +import base64 sys.path.insert( 0, os.path.abspath("../..") @@ -889,6 +890,74 @@ class BaseLLMChatTest(ABC): assert cost > 0 + @pytest.mark.parametrize("input_type", ["input_audio", "audio_url"]) + def test_supports_audio_input(self, input_type): + from litellm.utils import return_raw_request, supports_audio_input + from litellm.types.utils import CallTypes + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + + litellm.drop_params = True + base_completion_call_args = self.get_base_completion_call_args() + if not supports_audio_input(base_completion_call_args["model"], None): + print("Model does not support audio input") + pytest.skip("Model does not support audio input") + + url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav" + response = httpx.get(url) + response.raise_for_status() + wav_data = response.content + audio_format = "wav" + encoded_string = base64.b64encode(wav_data).decode("utf-8") + + audio_content = [ + { + "type": "text", + "text": "What is in this recording?" + } + ] + + test_file_id = "gs://bucket/file.wav" + + if input_type == "input_audio": + audio_content.append({ + "type": "input_audio", + "input_audio": {"data": encoded_string, "format": audio_format}, + }) + elif input_type == "audio_url": + audio_content.append( + { + "type": "file", + "file": { + "file_id": test_file_id, + "filename": "my-sample-audio-file", + } + } + ) + + + + raw_request = return_raw_request( + endpoint=CallTypes.completion, + kwargs={ + **base_completion_call_args, + "modalities": ["text", "audio"], + "audio": {"voice": "alloy", "format": audio_format}, + "messages": [ + { + "role": "user", + "content": audio_content, + }, + ] + } + ) + print("raw_request: ", raw_request) + + if input_type == "input_audio": + assert encoded_string in json.dumps(raw_request), "Audio data not sent to gemini" + elif input_type == "audio_url": + assert test_file_id in json.dumps(raw_request), "Audio URL not sent to gemini" class BaseOSeriesModelsTest(ABC): # test across azure/openai @abstractmethod @@ -1089,3 +1158,5 @@ class BaseAnthropicChatTest(ABC): ) print(response) + + \ No newline at end of file diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py index f592337593..7c7c10daee 100644 --- a/tests/llm_translation/test_gemini.py +++ b/tests/llm_translation/test_gemini.py @@ -15,7 +15,7 @@ from litellm.llms.vertex_ai.context_caching.transformation import ( class TestGoogleAIStudioGemini(BaseLLMChatTest): def get_base_completion_call_args(self) -> dict: - return {"model": "gemini/gemini-1.5-flash-002"} + return {"model": "gemini/gemini-2.0-flash"} def test_tool_call_no_arguments(self, tool_call_no_arguments): """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" diff --git a/tests/llm_translation/test_gpt4o_audio.py b/tests/llm_translation/test_gpt4o_audio.py index 822cfb0356..f41dabb666 100644 --- a/tests/llm_translation/test_gpt4o_audio.py +++ b/tests/llm_translation/test_gpt4o_audio.py @@ -84,12 +84,14 @@ async def test_audio_output_from_model(stream): @pytest.mark.asyncio @pytest.mark.parametrize("stream", [True, False]) -async def test_audio_input_to_model(stream): +@pytest.mark.parametrize("model", ["gpt-4o-audio-preview"]) # "gpt-4o-audio-preview", +async def test_audio_input_to_model(stream, model): # Fetch the audio file and convert it to a base64 encoded string audio_format = "pcm16" if stream is False: audio_format = "wav" litellm._turn_on_debug() + litellm.drop_params = True url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav" response = requests.get(url) response.raise_for_status() @@ -97,7 +99,7 @@ async def test_audio_input_to_model(stream): encoded_string = base64.b64encode(wav_data).decode("utf-8") try: completion = await litellm.acompletion( - model="gpt-4o-audio-preview", + model=model, modalities=["text", "audio"], audio={"voice": "alloy", "format": audio_format}, stream=stream, @@ -120,6 +122,7 @@ async def test_audio_input_to_model(stream): except Exception as e: if "openai-internal" in str(e): pytest.skip("Skipping test due to openai-internal error") + raise e if stream is True: await check_streaming_response(completion) else: diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index 99fc415c1b..ea837b717b 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -2285,10 +2285,10 @@ def test_update_logs_with_spend_logs_url(prisma_client): """ Unit test for making sure spend logs list is still updated when url passed in """ - from litellm.proxy.proxy_server import _set_spend_logs_payload + from litellm.proxy.db.db_spend_update_writer import DBSpendUpdateWriter payload = {"startTime": datetime.now(), "endTime": datetime.now()} - _set_spend_logs_payload(payload=payload, prisma_client=prisma_client) + DBSpendUpdateWriter._set_spend_logs_payload(payload=payload, prisma_client=prisma_client) assert len(prisma_client.spend_log_transactions) > 0 @@ -2296,7 +2296,7 @@ def test_update_logs_with_spend_logs_url(prisma_client): spend_logs_url = "" payload = {"startTime": datetime.now(), "endTime": datetime.now()} - _set_spend_logs_payload( + DBSpendUpdateWriter._set_spend_logs_payload( payload=payload, spend_logs_url=spend_logs_url, prisma_client=prisma_client ) From e9b743e3c6a8eba13c789f1a0bc1dbbf79a2151b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 19:50:13 -0700 Subject: [PATCH 229/244] docs release notes --- .../release_notes/v1.65.0-stable/index.md | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index b36eac1603..68154f90e9 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -47,63 +47,61 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro

- -## Categorized Improvements and Fixes -### New Models / Updated Models +## New Models / Updated Models - Support for Vertex AI gemini-2.0-flash-lite & Google AI Studio gemini-2.0-flash-lite [PR](https://github.com/BerriAI/litellm/pull/9523) - Support for Vertex AI Fine-Tuned LLMs [PR](https://github.com/BerriAI/litellm/pull/9542) - Nova Canvas image generation support [PR](https://github.com/BerriAI/litellm/pull/9525) - OpenAI gpt-4o-transcribe support [PR](https://github.com/BerriAI/litellm/pull/9517) - Added new Vertex AI text embedding model [PR](https://github.com/BerriAI/litellm/pull/9476) -### LLM Translation +## LLM Translation - OpenAI Web Search Tool Call Support [PR](https://github.com/BerriAI/litellm/pull/9465) - Vertex AI topLogprobs support [PR](https://github.com/BerriAI/litellm/pull/9518) - Fixed Vertex AI multimodal embedding translation [PR](https://github.com/BerriAI/litellm/pull/9471) - Support litellm.api_base for Vertex AI + Gemini across completion, embedding, image_generation [PR](https://github.com/BerriAI/litellm/pull/9516) - Fixed Mistral chat transformation [PR](https://github.com/BerriAI/litellm/pull/9606) -### Spend Tracking Improvements +## Spend Tracking Improvements - Log 'api_base' on spend logs [PR](https://github.com/BerriAI/litellm/pull/9509) - Support for Gemini audio token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) - Fixed OpenAI audio input token cost tracking [PR](https://github.com/BerriAI/litellm/pull/9535) -### UI +## UI -#### Model Management +### Model Management - Allowed team admins to add/update/delete models on UI [PR](https://github.com/BerriAI/litellm/pull/9572) - Added render supports_web_search on model hub [PR](https://github.com/BerriAI/litellm/pull/9469) -#### Request Logs +### Request Logs - Show API base and model ID on request logs [PR](https://github.com/BerriAI/litellm/pull/9572) - Allow viewing keyinfo on request logs [PR](https://github.com/BerriAI/litellm/pull/9568) -#### Usage Tab +### Usage Tab - Added Daily User Spend Aggregate view - allows UI Usage tab to work > 1m rows [PR](https://github.com/BerriAI/litellm/pull/9538) - Connected UI to "LiteLLM_DailyUserSpend" spend table [PR](https://github.com/BerriAI/litellm/pull/9603) -### Logging Integrations +## Logging Integrations - Fixed StandardLoggingPayload for GCS Pub Sub Logging Integration [PR](https://github.com/BerriAI/litellm/pull/9508) -### Performance / Reliability Improvements +## Performance / Reliability Improvements - LiteLLM Redis semantic caching implementation [PR](https://github.com/BerriAI/litellm/pull/9356) - Gracefully handle exceptions when DB is having an outage [PR](https://github.com/BerriAI/litellm/pull/9533) - Allow Pods to startup + passing /health/readiness when allow_requests_on_db_unavailable: True and DB is down [PR](https://github.com/BerriAI/litellm/pull/9569) - Removed hard coded final usage chunk on Bedrock streaming usage [PR](https://github.com/BerriAI/litellm/pull/9512) - Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) -### General Improvements +## General Improvements - Support for exposing MCP tools on litellm proxy [PR](https://github.com/BerriAI/litellm/pull/9426) - Support discovering Gemini, Anthropic, xAI models by calling their /v1/model endpoint [PR](https://github.com/BerriAI/litellm/pull/9530) - Fixed route check for non-proxy admins on JWT auth [PR](https://github.com/BerriAI/litellm/pull/9454) - Added baseline Prisma database migrations [PR](https://github.com/BerriAI/litellm/pull/9565) - Get master key from environment, if not set [PR](https://github.com/BerriAI/litellm/pull/9617) -### Documentation +## Documentation - Fixed Predibase typo [PR](https://github.com/BerriAI/litellm/pull/9464) - Updated README.md [PR](https://github.com/BerriAI/litellm/pull/9616) -### Security +## Security - Bumped next from 14.2.21 to 14.2.25 in UI dashboard [PR](https://github.com/BerriAI/litellm/pull/9458) ## Complete Git Diff From 1f10d985fb68637de63ccb405c10790490e24b40 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 19:51:38 -0700 Subject: [PATCH 230/244] docs updates release notes --- docs/my-website/release_notes/v1.65.0-stable/index.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 68154f90e9..3435c919dc 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -87,7 +87,6 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro - LiteLLM Redis semantic caching implementation [PR](https://github.com/BerriAI/litellm/pull/9356) - Gracefully handle exceptions when DB is having an outage [PR](https://github.com/BerriAI/litellm/pull/9533) - Allow Pods to startup + passing /health/readiness when allow_requests_on_db_unavailable: True and DB is down [PR](https://github.com/BerriAI/litellm/pull/9569) -- Removed hard coded final usage chunk on Bedrock streaming usage [PR](https://github.com/BerriAI/litellm/pull/9512) - Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) ## General Improvements @@ -95,11 +94,6 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro - Support discovering Gemini, Anthropic, xAI models by calling their /v1/model endpoint [PR](https://github.com/BerriAI/litellm/pull/9530) - Fixed route check for non-proxy admins on JWT auth [PR](https://github.com/BerriAI/litellm/pull/9454) - Added baseline Prisma database migrations [PR](https://github.com/BerriAI/litellm/pull/9565) -- Get master key from environment, if not set [PR](https://github.com/BerriAI/litellm/pull/9617) - -## Documentation -- Fixed Predibase typo [PR](https://github.com/BerriAI/litellm/pull/9464) -- Updated README.md [PR](https://github.com/BerriAI/litellm/pull/9616) ## Security - Bumped next from 14.2.21 to 14.2.25 in UI dashboard [PR](https://github.com/BerriAI/litellm/pull/9458) From 9b187d89f2d152e88d3d25054c450dbb84a59459 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 19:55:41 -0700 Subject: [PATCH 231/244] add litellm model name on SLP --- docs/my-website/release_notes/v1.65.0-stable/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 3435c919dc..cd85cf8322 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -60,6 +60,7 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro - Fixed Vertex AI multimodal embedding translation [PR](https://github.com/BerriAI/litellm/pull/9471) - Support litellm.api_base for Vertex AI + Gemini across completion, embedding, image_generation [PR](https://github.com/BerriAI/litellm/pull/9516) - Fixed Mistral chat transformation [PR](https://github.com/BerriAI/litellm/pull/9606) +- Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) ## Spend Tracking Improvements - Log 'api_base' on spend logs [PR](https://github.com/BerriAI/litellm/pull/9509) @@ -82,18 +83,21 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro ## Logging Integrations - Fixed StandardLoggingPayload for GCS Pub Sub Logging Integration [PR](https://github.com/BerriAI/litellm/pull/9508) +- Track `litellm_model_name` on `StandardLoggingPayload` [Docs](https://docs.litellm.ai/docs/proxy/logging_spec#standardlogginghiddenparams) ## Performance / Reliability Improvements - LiteLLM Redis semantic caching implementation [PR](https://github.com/BerriAI/litellm/pull/9356) - Gracefully handle exceptions when DB is having an outage [PR](https://github.com/BerriAI/litellm/pull/9533) - Allow Pods to startup + passing /health/readiness when allow_requests_on_db_unavailable: True and DB is down [PR](https://github.com/BerriAI/litellm/pull/9569) -- Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) + ## General Improvements - Support for exposing MCP tools on litellm proxy [PR](https://github.com/BerriAI/litellm/pull/9426) - Support discovering Gemini, Anthropic, xAI models by calling their /v1/model endpoint [PR](https://github.com/BerriAI/litellm/pull/9530) - Fixed route check for non-proxy admins on JWT auth [PR](https://github.com/BerriAI/litellm/pull/9454) - Added baseline Prisma database migrations [PR](https://github.com/BerriAI/litellm/pull/9565) +- View all wildcard models on /model/info [PR](https://github.com/BerriAI/litellm/pull/9572) + ## Security - Bumped next from 14.2.21 to 14.2.25 in UI dashboard [PR](https://github.com/BerriAI/litellm/pull/9458) From 7673293c9f2ba119d67dfad4dd11627ee17ce045 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 20:01:00 -0700 Subject: [PATCH 232/244] docs update stable release --- docs/my-website/release_notes/v1.65.0-stable/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index cd85cf8322..d42b1c661e 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -57,9 +57,10 @@ Read more about custom prompt management [here](https://docs.litellm.ai/docs/pro ## LLM Translation - OpenAI Web Search Tool Call Support [PR](https://github.com/BerriAI/litellm/pull/9465) - Vertex AI topLogprobs support [PR](https://github.com/BerriAI/litellm/pull/9518) -- Fixed Vertex AI multimodal embedding translation [PR](https://github.com/BerriAI/litellm/pull/9471) +- Support for sending images and video to Vertex AI multimodal embedding [Doc](https://docs.litellm.ai/docs/providers/vertex#multi-modal-embeddings) - Support litellm.api_base for Vertex AI + Gemini across completion, embedding, image_generation [PR](https://github.com/BerriAI/litellm/pull/9516) -- Fixed Mistral chat transformation [PR](https://github.com/BerriAI/litellm/pull/9606) +- Bug fix for returning `response_cost` when using litellm python SDK with LiteLLM Proxy [PR](https://github.com/BerriAI/litellm/commit/6fd18651d129d606182ff4b980e95768fc43ca3d) +- Support for `max_completion_tokens` on Mistral API [PR](https://github.com/BerriAI/litellm/pull/9606) - Refactored Vertex AI passthrough routes - fixes unpredictable behaviour with auto-setting default_vertex_region on router model add [PR](https://github.com/BerriAI/litellm/pull/9467) ## Spend Tracking Improvements From 8c2f1c61427521e6d50c3ed1a196ff311fe81aee Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 20:03:05 -0700 Subject: [PATCH 233/244] docs 1.65.0-stable --- docs/my-website/release_notes/v1.65.0-stable/index.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index d42b1c661e..36e571be93 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -16,7 +16,14 @@ hide_table_of_contents: false --- import Image from '@theme/IdealImage'; -LiteLLM v1.65.0 introduces significant enhancements including Model Context Protocol (MCP) tools, new models, and various performance improvements. +v1.65.0-stable is live now. Here are the key highlights of this release: +- **MCP Support**: Support for adding and using MCP servers on the LiteLLM proxy. +- **Custom Prompt Management**: Connect LiteLLM to your prompt management system with custom hooks. + + + + + ## Model Context Protocol (MCP) From 2238a5585bec9d80d60bfec281c8f3d946646487 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 20:35:53 -0700 Subject: [PATCH 234/244] doc fix mcp --- docs/my-website/sidebars.js | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 2e5d0f2a0a..5aeee715d1 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -304,7 +304,6 @@ const sidebars = { "image_variations", ] }, - "mcp", { type: "category", label: "/audio", From db12adb3db5e0190b160c7fe4f2a37914e44fe60 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 20:42:14 -0700 Subject: [PATCH 235/244] docs mcp tools --- docs/my-website/docs/mcp.md | 62 ++++++++------------------------- litellm/proxy/proxy_config.yaml | 2 +- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 744c23644e..291de46ec9 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -282,8 +282,8 @@ This allows you to define tools that can be called by any MCP compatible client. LiteLLM exposes the following MCP endpoints: -- `/mcp/list_tools` - List all available tools -- `/mcp/call_tool` - Call a specific tool with the provided arguments +- `/mcp/tools/list` - List all available tools +- `/mcp/tools/call` - Call a specific tool with the provided arguments When MCP clients connect to LiteLLM they can follow this workflow: @@ -297,9 +297,9 @@ When MCP clients connect to LiteLLM they can follow this workflow: #### Usage -#### 1. Define your tools on mcp_tools +#### 1. Define your tools on under `mcp_servers` in your config.yaml file. -LiteLLM allows you to define your tools on the `mcp_tools` section in your config.yaml file. All tools listed here will be available to MCP clients (when they connect to LiteLLM and call `list_tools`). +LiteLLM allows you to define your tools on the `mcp_servers` section in your config.yaml file. All tools listed here will be available to MCP clients (when they connect to LiteLLM and call `list_tools`). ```yaml model_list: @@ -308,64 +308,30 @@ model_list: model: openai/gpt-4o api_key: sk-xxxxxxx - - -mcp_tools: - - name: "get_current_time" - description: "Get the current time" - input_schema: { - "type": "object", - "properties": { - "format": { - "type": "string", - "description": "The format of the time to return", - "enum": ["short"] - } - } +mcp_servers: + { + "zapier_mcp": { + "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse", + }, + "fetch" { + "url": "http://localhost:8000/sse", } - handler: "mcp_tools.get_current_time" + } + ``` -#### 2. Define a handler for your tool -Create a new file called `mcp_tools.py` and add this code. The key method here is `get_current_time` which gets executed when the `get_current_time` tool is called. - -```python -# mcp_tools.py - -from datetime import datetime - -def get_current_time(format: str = "short"): - """ - Simple handler for the 'get_current_time' tool. - - Args: - format (str): The format of the time to return ('short'). - - Returns: - str: The current time formatted as 'HH:MM'. - """ - # Get the current time - current_time = datetime.now() - - # Format the time as 'HH:MM' - return current_time.strftime('%H:%M') -``` - -#### 3. Start LiteLLM Gateway +#### 2. Start LiteLLM Gateway -Mount your `mcp_tools.py` on the LiteLLM Docker container. - ```shell docker run -d \ -p 4000:4000 \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ --name my-app \ -v $(pwd)/my_config.yaml:/app/config.yaml \ - -v $(pwd)/mcp_tools.py:/app/mcp_tools.py \ my-app:latest \ --config /app/config.yaml \ --port 4000 \ diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 3956eab23f..22d61ab073 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -5,7 +5,7 @@ model_list: mcp_servers: { - "Zapier MCP": { + "Zapier_MCP": { "url": "os.environ/ZAPIER_MCP_SERVER_URL", "mcp_info": { "logo_url": "https://espysys.com/wp-content/uploads/2024/08/zapier-logo.webp", From 366f3a901c78214357c779ac461bc046e045c00e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 21:46:18 -0700 Subject: [PATCH 236/244] docs mcp --- docs/my-website/docs/mcp.md | 51 ++----------------------------------- 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 291de46ec9..7b21063c84 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -291,8 +291,8 @@ When MCP clients connect to LiteLLM they can follow this workflow: 2. List all available tools on LiteLLM 3. Client makes LLM API request with tool call(s) 4. LLM API returns which tools to call and with what arguments -5. MCP client makes tool calls to LiteLLM -6. LiteLLM makes the tool calls to the appropriate handlers +5. MCP client makes MCP tool calls to LiteLLM +6. LiteLLM makes the tool calls to the appropriate MCP server 7. LiteLLM returns the tool call results to the MCP client #### Usage @@ -395,50 +395,3 @@ if __name__ == "__main__": asyncio.run(main()) ``` - - -### Specification for `mcp_tools` - -The `mcp_tools` section in your LiteLLM config defines tools that can be called by MCP-compatible clients. - -#### Tool Definition Format - -```yaml -mcp_tools: - - name: string # Required: Name of the tool - description: string # Required: Description of what the tool does - input_schema: object # Required: JSON Schema defining the tool's input parameters - handler: string # Required: Path to the function that implements the tool -``` - -#### Field Details - -- `name`: A unique identifier for the tool -- `description`: A clear description of what the tool does, used by LLMs to determine when to call it -- `input_schema`: JSON Schema object defining the expected input parameters -- `handler`: String path to the Python function that implements the tool (e.g., "module.submodule.function_name") - -#### Example Tool Definition - -```yaml -mcp_tools: - - name: "get_current_time" - description: "Get the current time in a specified format" - input_schema: { - "type": "object", - "properties": { - "format": { - "type": "string", - "description": "The format of the time to return", - "enum": ["short", "long", "iso"] - }, - "timezone": { - "type": "string", - "description": "The timezone to use (e.g., 'UTC', 'America/New_York')", - "default": "UTC" - } - }, - "required": ["format"] - } - handler: "mcp_tools.get_current_time" -``` From cc80370e0ce8b69b862f609a9201577c39840cb3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 21:59:58 -0700 Subject: [PATCH 237/244] docs mcp litellm --- docs/my-website/docs/mcp.md | 113 +++++++++++++++++++++----------- litellm/proxy/proxy_config.yaml | 11 ++-- 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 7b21063c84..1d66596900 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -272,11 +272,10 @@ async with stdio_client(server_params) as (read, write): -## Advanced Usage - +## Advanced ### Expose MCP tools on LiteLLM Proxy Server -This allows you to define tools that can be called by any MCP compatible client. Define your mcp_tools with LiteLLM and all your clients can list and call available tools. +This allows you to define tools that can be called by any MCP compatible client. Define your `mcp_servers` with LiteLLM and all your clients can list and call available tools. #### How it works @@ -301,7 +300,7 @@ When MCP clients connect to LiteLLM they can follow this workflow: LiteLLM allows you to define your tools on the `mcp_servers` section in your config.yaml file. All tools listed here will be available to MCP clients (when they connect to LiteLLM and call `list_tools`). -```yaml +```yaml title="config.yaml" showLineNumbers model_list: - model_name: gpt-4o litellm_params: @@ -311,13 +310,12 @@ model_list: mcp_servers: { "zapier_mcp": { - "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse", + "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse" }, - "fetch" { - "url": "http://localhost:8000/sse", + "fetch": { + "url": "http://localhost:8000/sse" } } - ``` @@ -326,7 +324,7 @@ mcp_servers: -```shell +```shell title="Docker Run" showLineNumbers docker run -d \ -p 4000:4000 \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ @@ -342,7 +340,7 @@ docker run -d \ -```shell +```shell title="litellm pip" showLineNumbers litellm --config config.yaml --detailed_debug ``` @@ -350,48 +348,87 @@ litellm --config config.yaml --detailed_debug -#### 4. Make an LLM API request +#### 3. Make an LLM API request +In this example we will do the following: +1. Use MCP client to list MCP tools on LiteLLM Proxy +2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools +3. Provide the MCP tools to `gpt-4o` +4. Handle tool call from `gpt-4o` +5. Convert OpenAI tool call to MCP tool call +6. Execute tool call on MCP server -```python +```python title="MCP Client List Tools" showLineNumbers import asyncio -from langchain_mcp_adapters.tools import load_mcp_tools -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionUserMessageParam from mcp import ClientSession from mcp.client.sse import sse_client +from litellm.experimental_mcp_client.tools import ( + transform_mcp_tool_to_openai_tool, + transform_openai_tool_call_request_to_mcp_tool_call_request, +) async def main(): - # Initialize the model with your API key - model = ChatOpenAI(model="gpt-4o") + # Initialize clients - # Connect to the MCP server - async with sse_client(url="http://localhost:4000/mcp/") as (read, write): + # point OpenAI client to LiteLLM Proxy + client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") + + # Point MCP client to LiteLLM Proxy + async with sse_client("http://localhost:4000/mcp/") as (read, write): async with ClientSession(read, write) as session: - # Initialize the session - print("Initializing session...") await session.initialize() - print("Session initialized") - # Load available tools from MCP - print("Loading tools...") - tools = await load_mcp_tools(session) - print(f"Loaded {len(tools)} tools") + # 1. List MCP tools on LiteLLM Proxy + mcp_tools = await session.list_tools() + print("List of MCP tools for MCP server:", mcp_tools.tools) - # Create a ReAct agent with the model and tools - agent = create_react_agent(model, tools) - - # Run the agent with a user query - user_query = "What's the weather in Tokyo?" - print(f"Asking: {user_query}") - agent_response = await agent.ainvoke({"messages": user_query}) - print("Agent response:") - print(agent_response) + # Create message + messages = [ + ChatCompletionUserMessageParam( + content="Send an email about LiteLLM supporting MCP", role="user" + ) + ] + + # 2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools + # Since OpenAI only supports tools in the OpenAI format, we need to convert the MCP tools to the OpenAI format. + openai_tools = [ + transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools + ] + + # 3. Provide the MCP tools to `gpt-4o` + response = await client.chat.completions.create( + model="gpt-4o", + messages=messages, + tools=openai_tools, + tool_choice="auto", + ) + + # 4. Handle tool call from `gpt-4o` + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + if tool_call: + + # 5. Convert OpenAI tool call to MCP tool call + # Since MCP servers expect tools in the MCP format, we need to convert the OpenAI tool call to the MCP format. + # This is done using litellm.experimental_mcp_client.tools.transform_openai_tool_call_request_to_mcp_tool_call_request + mcp_call = ( + transform_openai_tool_call_request_to_mcp_tool_call_request( + openai_tool=tool_call.model_dump() + ) + ) + + # 6. Execute tool call on MCP server + result = await session.call_tool( + name=mcp_call.name, arguments=mcp_call.arguments + ) + + print("Result:", result) -if __name__ == "__main__": - asyncio.run(main()) - +# Run it +asyncio.run(main()) ``` diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 22d61ab073..c0107d7ea6 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -2,13 +2,14 @@ model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o + api_key: sk-xxxxxxx mcp_servers: { - "Zapier_MCP": { - "url": "os.environ/ZAPIER_MCP_SERVER_URL", - "mcp_info": { - "logo_url": "https://espysys.com/wp-content/uploads/2024/08/zapier-logo.webp", - } + "zapier_mcp": { + "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse" + }, + "fetch": { + "url": "http://localhost:8000/sse" } } From df01337bd859a419077e0f4818d95f1afd2c578b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 22:28:03 -0700 Subject: [PATCH 238/244] docs litellm mcp --- docs/my-website/docs/mcp.md | 329 +++++++++++++++++----------------- docs/my-website/img/mcp_2.png | Bin 0 -> 135741 bytes 2 files changed, 161 insertions(+), 168 deletions(-) create mode 100644 docs/my-website/img/mcp_2.png diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 1d66596900..0947c494c7 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -4,21 +4,177 @@ import Image from '@theme/IdealImage'; # /mcp [BETA] - Model Context Protocol -Use Model Context Protocol with LiteLLM +## Expose MCP tools on LiteLLM Proxy Server +This allows you to define tools that can be called by any MCP compatible client. Define your `mcp_servers` with LiteLLM and all your clients can list and call available tools.

LiteLLM MCP Architecture: Use MCP tools with all LiteLLM supported models

+#### How it works -## Overview +LiteLLM exposes the following MCP endpoints: -LiteLLM acts as a MCP bridge to utilize MCP tools with all LiteLLM supported models. LiteLLM offers the following features for using MCP +- `/mcp/tools/list` - List all available tools +- `/mcp/tools/call` - Call a specific tool with the provided arguments + +When MCP clients connect to LiteLLM they can follow this workflow: + +1. Connect to the LiteLLM MCP server +2. List all available tools on LiteLLM +3. Client makes LLM API request with tool call(s) +4. LLM API returns which tools to call and with what arguments +5. MCP client makes MCP tool calls to LiteLLM +6. LiteLLM makes the tool calls to the appropriate MCP server +7. LiteLLM returns the tool call results to the MCP client + +#### Usage + +#### 1. Define your tools on under `mcp_servers` in your config.yaml file. + +LiteLLM allows you to define your tools on the `mcp_servers` section in your config.yaml file. All tools listed here will be available to MCP clients (when they connect to LiteLLM and call `list_tools`). + +```yaml title="config.yaml" showLineNumbers +model_list: + - model_name: gpt-4o + litellm_params: + model: openai/gpt-4o + api_key: sk-xxxxxxx + +mcp_servers: + { + "zapier_mcp": { + "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse" + }, + "fetch": { + "url": "http://localhost:8000/sse" + } + } +``` + + +#### 2. Start LiteLLM Gateway + + + + +```shell title="Docker Run" showLineNumbers +docker run -d \ + -p 4000:4000 \ + -e OPENAI_API_KEY=$OPENAI_API_KEY \ + --name my-app \ + -v $(pwd)/my_config.yaml:/app/config.yaml \ + my-app:latest \ + --config /app/config.yaml \ + --port 4000 \ + --detailed_debug \ +``` + + + + + +```shell title="litellm pip" showLineNumbers +litellm --config config.yaml --detailed_debug +``` + + + + + +#### 3. Make an LLM API request + +In this example we will do the following: + +1. Use MCP client to list MCP tools on LiteLLM Proxy +2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools +3. Provide the MCP tools to `gpt-4o` +4. Handle tool call from `gpt-4o` +5. Convert OpenAI tool call to MCP tool call +6. Execute tool call on MCP server + +```python title="MCP Client List Tools" showLineNumbers +import asyncio +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionUserMessageParam +from mcp import ClientSession +from mcp.client.sse import sse_client +from litellm.experimental_mcp_client.tools import ( + transform_mcp_tool_to_openai_tool, + transform_openai_tool_call_request_to_mcp_tool_call_request, +) + + +async def main(): + # Initialize clients + + # point OpenAI client to LiteLLM Proxy + client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") + + # Point MCP client to LiteLLM Proxy + async with sse_client("http://localhost:4000/mcp/") as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + + # 1. List MCP tools on LiteLLM Proxy + mcp_tools = await session.list_tools() + print("List of MCP tools for MCP server:", mcp_tools.tools) + + # Create message + messages = [ + ChatCompletionUserMessageParam( + content="Send an email about LiteLLM supporting MCP", role="user" + ) + ] + + # 2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools + # Since OpenAI only supports tools in the OpenAI format, we need to convert the MCP tools to the OpenAI format. + openai_tools = [ + transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools + ] + + # 3. Provide the MCP tools to `gpt-4o` + response = await client.chat.completions.create( + model="gpt-4o", + messages=messages, + tools=openai_tools, + tool_choice="auto", + ) + + # 4. Handle tool call from `gpt-4o` + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + if tool_call: + + # 5. Convert OpenAI tool call to MCP tool call + # Since MCP servers expect tools in the MCP format, we need to convert the OpenAI tool call to the MCP format. + # This is done using litellm.experimental_mcp_client.tools.transform_openai_tool_call_request_to_mcp_tool_call_request + mcp_call = ( + transform_openai_tool_call_request_to_mcp_tool_call_request( + openai_tool=tool_call.model_dump() + ) + ) + + # 6. Execute tool call on MCP server + result = await session.call_tool( + name=mcp_call.name, arguments=mcp_call.arguments + ) + + print("Result:", result) + + +# Run it +asyncio.run(main()) +``` + +## LiteLLM Python SDK MCP Bridge + +LiteLLM Python SDK acts as a MCP bridge to utilize MCP tools with all LiteLLM supported models. LiteLLM offers the following features for using MCP - **List** Available MCP Tools: OpenAI clients can view all available MCP tools - `litellm.experimental_mcp_client.load_mcp_tools` to list all available MCP tools @@ -26,8 +182,6 @@ LiteLLM acts as a MCP bridge to utilize MCP tools with all LiteLLM supported mod - `litellm.experimental_mcp_client.call_openai_tool` to call an OpenAI tool on an MCP server -## Usage - ### 1. List Available MCP Tools In this example we'll use `litellm.experimental_mcp_client.load_mcp_tools` to list all available MCP tools on any MCP server. This method can be used in two ways: @@ -270,165 +424,4 @@ async with stdio_client(server_params) as (read, write): ``` - - -## Advanced -### Expose MCP tools on LiteLLM Proxy Server - -This allows you to define tools that can be called by any MCP compatible client. Define your `mcp_servers` with LiteLLM and all your clients can list and call available tools. - -#### How it works - -LiteLLM exposes the following MCP endpoints: - -- `/mcp/tools/list` - List all available tools -- `/mcp/tools/call` - Call a specific tool with the provided arguments - -When MCP clients connect to LiteLLM they can follow this workflow: - -1. Connect to the LiteLLM MCP server -2. List all available tools on LiteLLM -3. Client makes LLM API request with tool call(s) -4. LLM API returns which tools to call and with what arguments -5. MCP client makes MCP tool calls to LiteLLM -6. LiteLLM makes the tool calls to the appropriate MCP server -7. LiteLLM returns the tool call results to the MCP client - -#### Usage - -#### 1. Define your tools on under `mcp_servers` in your config.yaml file. - -LiteLLM allows you to define your tools on the `mcp_servers` section in your config.yaml file. All tools listed here will be available to MCP clients (when they connect to LiteLLM and call `list_tools`). - -```yaml title="config.yaml" showLineNumbers -model_list: - - model_name: gpt-4o - litellm_params: - model: openai/gpt-4o - api_key: sk-xxxxxxx - -mcp_servers: - { - "zapier_mcp": { - "url": "https://actions.zapier.com/mcp/sk-akxxxxx/sse" - }, - "fetch": { - "url": "http://localhost:8000/sse" - } - } -``` - - -#### 2. Start LiteLLM Gateway - - - - -```shell title="Docker Run" showLineNumbers -docker run -d \ - -p 4000:4000 \ - -e OPENAI_API_KEY=$OPENAI_API_KEY \ - --name my-app \ - -v $(pwd)/my_config.yaml:/app/config.yaml \ - my-app:latest \ - --config /app/config.yaml \ - --port 4000 \ - --detailed_debug \ -``` - - - - - -```shell title="litellm pip" showLineNumbers -litellm --config config.yaml --detailed_debug -``` - - - - - -#### 3. Make an LLM API request - -In this example we will do the following: - -1. Use MCP client to list MCP tools on LiteLLM Proxy -2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools -3. Provide the MCP tools to `gpt-4o` -4. Handle tool call from `gpt-4o` -5. Convert OpenAI tool call to MCP tool call -6. Execute tool call on MCP server - -```python title="MCP Client List Tools" showLineNumbers -import asyncio -from openai import AsyncOpenAI -from openai.types.chat import ChatCompletionUserMessageParam -from mcp import ClientSession -from mcp.client.sse import sse_client -from litellm.experimental_mcp_client.tools import ( - transform_mcp_tool_to_openai_tool, - transform_openai_tool_call_request_to_mcp_tool_call_request, -) - - -async def main(): - # Initialize clients - - # point OpenAI client to LiteLLM Proxy - client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") - - # Point MCP client to LiteLLM Proxy - async with sse_client("http://localhost:4000/mcp/") as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - # 1. List MCP tools on LiteLLM Proxy - mcp_tools = await session.list_tools() - print("List of MCP tools for MCP server:", mcp_tools.tools) - - # Create message - messages = [ - ChatCompletionUserMessageParam( - content="Send an email about LiteLLM supporting MCP", role="user" - ) - ] - - # 2. Use `transform_mcp_tool_to_openai_tool` to convert MCP tools to OpenAI tools - # Since OpenAI only supports tools in the OpenAI format, we need to convert the MCP tools to the OpenAI format. - openai_tools = [ - transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools - ] - - # 3. Provide the MCP tools to `gpt-4o` - response = await client.chat.completions.create( - model="gpt-4o", - messages=messages, - tools=openai_tools, - tool_choice="auto", - ) - - # 4. Handle tool call from `gpt-4o` - if response.choices[0].message.tool_calls: - tool_call = response.choices[0].message.tool_calls[0] - if tool_call: - - # 5. Convert OpenAI tool call to MCP tool call - # Since MCP servers expect tools in the MCP format, we need to convert the OpenAI tool call to the MCP format. - # This is done using litellm.experimental_mcp_client.tools.transform_openai_tool_call_request_to_mcp_tool_call_request - mcp_call = ( - transform_openai_tool_call_request_to_mcp_tool_call_request( - openai_tool=tool_call.model_dump() - ) - ) - - # 6. Execute tool call on MCP server - result = await session.call_tool( - name=mcp_call.name, arguments=mcp_call.arguments - ) - - print("Result:", result) - - -# Run it -asyncio.run(main()) -``` + \ No newline at end of file diff --git a/docs/my-website/img/mcp_2.png b/docs/my-website/img/mcp_2.png new file mode 100644 index 0000000000000000000000000000000000000000..98e063efc5d77144123b6ac6d1339498951e120c GIT binary patch literal 135741 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 Date: Sat, 29 Mar 2025 23:08:20 -0700 Subject: [PATCH 239/244] docs release notes --- docs/my-website/img/release_notes/mcp_ui.png | Bin 95583 -> 242727 bytes .../my-website/img/release_notes/ui_usage.png | Bin 0 -> 67886 bytes .../release_notes/v1.65.0-stable/index.md | 67 +++++++++++++++--- 3 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 docs/my-website/img/release_notes/ui_usage.png diff --git a/docs/my-website/img/release_notes/mcp_ui.png b/docs/my-website/img/release_notes/mcp_ui.png index 6731fba71be9802379cae96b9c7758fad3525811..8f4cd4ea1985af23fd9cd00f3f1daf2266b43dc3 100644 GIT binary patch literal 242727 zcmeFZXH-+&_ce-&g8HD+5flU!Bp`^=q$*uXklu?RC3K|M*iZ-rRHPFSkd{!SccMs9 ziV;F@q4y3UkPvbY|MHXPyCUmAqoSgsd!(kM z3w)ELqM~_Ddj@#rvEWb)@a?RJnu!+`)p^FFuM~ni|tpaZ9I%$To zMq0(MZyH|w9*+E{j+0h`uA9q#;`>QP*(Vz`=iiQS(gx{B*Sm@|T$mYTJLxNPgRrAKmDywA<0F^rtz-zo{OoKmkjh5lV&o8UN%LE2g!=i< za`hDdGb*a87jFP7{mA>|Dse+TB{rSVAc@m z!y=P>UNweOOIx1oy_+1gkny&s(siWAMl?NyBhv~^8e9Lhw-PPArskF1x2ce7o4RZs zxZNdD_Rc&j#jTdV&O|z{|LD`|v*N?f%=m{SX)-c0;s;8Ici%U} z4CE8RQa_mI_wmJ0@&^o8$WHftS*v5^DpB|(xHFPg6b?sV6g0B39I!QB zxn%>}^O)?22;k$>!VozLGJEeR%0IuVPj@=(48A`LXLRi&-WLoeQhJ)ELtSFEU5vD0ByD?S!70A)A( z91+nS$(&kRk(2XXt0SRUDQHO#^Vg&TTZ@9%HkL7?4p3xetXL&J?d2B)XerCV7m#1#g+&naG5RD zkA2{m!LyRxtPZMeJp|~MCvlvHD+hhBsYcxB-4~S-iF2y>Rvz59R8DL+WiRN{b-i{H zTp@aCGUvygJ}chk;&p_4ily`pC7a@iO;{S~Ee!#K!Tkvli*Z7>^nr_h8)0ELPrA@Q z8?JOUTS~|q_ug6PK8|Z55ziTAFW(Olx*EFfu!sru5F{^5-&-8rdy1sHkTWX~pDU)Q z2pQUV%+ATKsrX6}Z+DIELT~AFz1mA2uqZw1q*mS{HC1qb52k?-yQ5)MFNK<5(g;on z%dmbS?Y;2W>J*sf!WFgS`(>h)6cJF3Nmr*q6Ta`{%1g$p{DS9ZWMq^(?RyYw1md&% z@B_>pZ?L6yn^GU+>F9DMztP^Ov!BfQHvGV_0^;o=h>-D-x+?D}hmZ}BLdZ)bANEv zAg{@X(SG_c2Q^^%zR0*n#-hX~z_j6+iD`q4iK&Q8>gklGA+LGt=6lA)z0K)GB{IJG zhniH-l0Lhvd^|5NO%H7LJ7hoXPA@V}agii1ljOEPYsV@eVe#v9>XD~=6L%IP*CkvA z-vK@#Ym3;E>vtZ_F`;%yZV9!4j=@4&M53=s$AkNd6^+rMA(h3(Q|`l&hcS&#^_Ds< z`CKP*C2+xBt)zowrH$h_n1$m6-*9qrvifz0U)fOnZr!4GBUBWgn$oWD$f(#XslUkd z{cU;=(+V4tM+8xfekTKZDB2(E!H{-gy7Cx)PN= zaWPkTUvQU^w5;?xuaUg!o*P2i?V>fWU?4S+cSo>S{YKy4VmXofmNU??wITwkK^XJH z-Q=!xYpHNzBFT76JpUJ>kNohQF}t<34V!`9yVkHf#+A(lkII>xoGf31#X(k=m?2$A zX2@*mq^NkHIC+8u><1kE*{&j@{IGp;=X|;eB6K#sVsXjGO3kRiG|f=RD(%eMGe9n~ zW-(=F%d?Pd(r5XEqzI#WKcf37@@B&0suK}Ik>?b<9-{8*O!CLChpY#gJkK5Y^2>(h z$(Mz68PRx}pFHnG&FKA|8x1{E|HU7^+T55>(x8_jZtO+uv~yAHh)^8mMxT@7A}mgz zCk|!!dGgyg@6yAupXuaV7G*X(A)8Cgi}4g|SK_Fcl^V)O5W}EeF>G!4p8ut0Y%JrD zT&_Gv?c;pNgu-urq!^tYj_$$*%e+C+tN!Yc(#BP9D%em2OaVviuNpRV1r3Y81nb;% zA`|fu{cDY+wVfUfVT)j{6WNw0!CieRnaS~oG=Cz%T7=QV<_#?)JQ|+kCnuR4N-12= zUFVWck1sZkkH1KlWAKU*GP}1~{-o=1?P9UAPYxz0BV$r9RrIPR%v!JZF#>Y!{9QV_ z+a3K8m#{lhSay^sLUQww6!fSbL``e=TIIgdLKzlX+@R$a$1)jZu;p%VbiFYULakK*i)xw;;yr?kVgs^ z)(0!Qj~wXFCa8{fG4(QER8B5UMI}1h?~qKuPI6t*vduk%O(?3lTWIpW=MCm_`Q|nr zU2KjxbN!1xNBv_vNbN~I%P{^X&620Mr*Wxggs9VcqMqvsHIimxvli!}hY3RZzQo?2 zvDYE|;Y%{BpdD|H_@A#H!FfJ0#f9;_)ANoRT=NT^_v^_}d@B^n`2vv#>eVpFY!M`i z5sO3;nO@`$QKz!M(&cV#aoIg}Y{3bPC-_Q{S`L^rHTnI`xrNO6%oFQP2TA5SYd`zysG7jo$Cr<-8OW7b1_=?7_-HN)ggEia zujj{)w~#)h0MZwHuC|O?HoA;#_eDd?gZ^erhbP{64l(`gldv3&~Vx%N7d34>|roCUF-xN;)b<|q*8Jzz+qv7G?`b=lXWH(NOjI*%z5){pXT#;4NW zY#@*MVbSC&=!e6l;?Q^f1vOHIR+JXvvO{URmVlx8`~Jc@UI2#GmIvbFApZW6s5vHs zRGvMi7dhSC-9R1s2|zbK*7%s^b=97svfL#cLc`WJ%j7qIKe<~Q^I4ua(1z6~um9YP z5M}A)F^qlw+jQHDyx{ zd0xzr%_e7|xb=aqeuLA66y}IfS4+YnX{YlkVmA{ETfYyMv8jvNnCE; zU-7ALjOrUeq+Y4p7&FeiM~{RQ@G!XnbU~DZzTX5LN{+%L6S{IU(Qat+?&Wkb!>=H0 zy`}z`(^}B6y%-m~VbEyJ$IkZ@v07?S2wYde&hpzWJ2jXPA=Dsn6_Sa|?fNLX{sf@z zu&Kqy>Os7BG?NpvBQ0n}B~;u%ilDhr)ducH)8^vFY} z$>noPm~3VN!|ch~4s7$_Q7E=SoSbCNi)$Gf6GeMP$$l$O?4K`U-(jCBp3e1tB$j(o zo?RPFP?ZP2Iz{hR`vnt8LKOW55>j$lvI+(Pp2wus`xoe2L4yHsF;glk9x5l7qX-f- z5Jn0sJMyDhHrZd~nz=XNKln^<&ruDN3AprQ9$?K6fWw*Sg9(R4O7P<2+O6bt?eE`T zL2M3Or2OWxMCAf`u1Z%T4^3{LzcPCw*F&U0l$?`sSC8{~vSZ$gH>8?>7vIhmYA)Y8 zBx;G{#Sv!oIiJCvWA|Flfo-!KmOAo@a%W2KV7-G{vA-uC7%oE0zvkO^8N8e#1B_f3 zT>y|~9U-C&NLv$5 zPPKoNvPEc9{gwOvy>`#A!7Io!C#g|=4z^-M_flL zj|dTL|oO{f#mGdp*$mR6e*{^`Tb81Yni`ZPo zZQ&xt*GW9R9|5}d8$d$Se%wutaNIHE^7Sa(eiWMCe-{l9tsJ`!qa!#ddgsnnDRynf z#laE-01?pgY0;z_LhUVx+b__FR=y?YsQI|0o*i)=eEwnE#=P@lR*alW-sld5IoIQ0 zFW>th_Pw|7j`{AscXp2C)J%&xHteJh#KuNO3#|D$M*iRyLRupEs>3JocT?yuE-grJ zVy7cJC-SBgBX^RD&dpm2;I24CV$czbh`OYw?+n9iId>aR@>_BJug?JA<9UDxTthb! zqIc>jE0jlETx1<2U4PGm!uT`JuYmG+CuFZ1>D^NEbCC+$lBphWj$c9L9wqI9U`VZ{ zWdr2dv#q9itkq!!+{m7kYAGzXGOtUYEah7e4;0?1sZg3TIWbi>EwF;A-ic37hVyAL zNsZ7(I0*7-r~Qg& zitkIjj*nqiknzZJW*&-@FeGjM=%;)&5I%+7uNR*P$FfZGZ!QovP`* zwmagUzIh0UAdt%O;O(x+6IW~f0DEHm)2$*;(Ol@R+E#J2Y4%W ziZ=n8kcK1EELw3&CW5b0AnfePnvB!ai=#DpiA>A@B;=!0iOVHA7w$rKU2SZ(6gC_) zGoE4f6)SQ@r`HX0(C!82#IS>d#?!-eahXf6yz_!_ky`p>2n%L1`Cip8rOfvRI@>3O#LS4c$lVWd$u zWpNSu?r>-QkZ+N2IDz{tPZ^)|?yJ>D4_?eYA2T;C1mbIg#|`3xSW3UA&zEgDgXK10ul zoj9%Z-~+7p!Cr~?T<50uQtzhs)(jsWtqtI|ZTzgclo!!kD!>VJ+e$9(&0CsWJynxN z2uKT@L%e(6mU4>qg9hEuXVJb(f$UeVbOLpgJ3r|~zQ)LvHa51r?CcvSh3TUrkbr02 z0_XrYsfZC!r;Ls`jQz^>mtt+vSC9C()nX-mYlP4B-@SQbzU3_Bel`y$yzH@M3)%sQ zp?AqZV5tMY7->}>+B#2v3(y~aSW@su41w!#9``60b1~=JH&Y~Ci^pX(+gN@r^EYofI6OM1yJYjf=-l*rcittj_YM7L zXv!YYbkD z=AQ6uV%DK>hT6LaejppCWl zr6-S!MoPA|aRwDk<5{OFq8g)@ch~CHEw?Oopt*51$E;vo!T=Cx)7!|KeLa}y&_v%5 zFDv3tJ`{FhV#B){Vsj0s=in#@?!5f`Z|5K86y)fsvkFMf8l>d(Si6D>If=Kl=u)*} zD_)el3~lXX3zMG_!<>sXpob0X+2}ms3gSGZT8?eGEiJLY{ob8-ztW_QaDKN7jVq7z zRRf5Sm=Va7mgn(Nq0|`e2i?%S@lq^Cj_7Cz&(~SGrvNVTu1y~@yu4K5E?sEcS3C{a zE4HH67tVd}*d~mTPG^%f!CKkwhp{+q)p2=*)2r_gYyVr>8Q(tuTE76 zdX&5Po0wJvE7#i0BFpDs0@zxC{?DlXf=B5lK?arP4Qop~UPff|+PQm1130TBxw5Hg z@2)((2kN9Ho#a;onL z5qJJ8;hN~TCcAF@bL>*E#J&KySw1!E6|Vb9YmMt@?d!R%dN7dID=c^J3sZfFx=BmU zw>_Ehfq`P>mK@U?vpE9y?Os8gkcU!9tzZhN+MyWJNn~M80oc{I9NJE$2lnD|aW-5f z#l~9%`tpbnWhxNx+hgl|9A9ljpOCxTZU@)Rpq0>X&P0O!sVU?pspo^F0x{a588|StGGZi zYkcX!9}ZWvDWX6>K%5Lm?@M6kXzwdLfw<&t}ZqHWTa#5Yo?WfAFR&~Cvrd4hhrnKWw3i{-{&n##ekxM9GWAX!1e;S1bOl7Mj$mAvb@G@OMs^X9^T40W{np2KHojl9+6vHg zk$X0yX8L1_xHej|{Q=`%w^~^ar5uXlY&un;oHG7*j`|6U4TN#$8q27LsYNA zM&9D$Yp@G`a2%k<_0Hc*Y6+3oTF-+zfTZZ8KcJ`)Y92kFa`A@vqnRs=P zcS27mqj2HA9LMGgW?kYN-Jd-9Nw1 zS4tVxnLAZep^;yB{`}Pgdf=}Fc|UT=hsxlwsN%+CEyK4p8^A>anr?0bRsN3)eAN~G zXk2ciZ~(NpdJf4XJ^ALtwBW6u)Lz^tRT26DCV&$K?sThS`wPvmT@tv|26v{6#=(I! z*`S{Bh9C{aP?4NY!0HS-Mm`8Z}mAoFAn?R_86T2=MHR48bx^B(43J)eZUn=B29E}Y!amP%?!!5b$V zs)5i@|L#TqJ7~GEs`i4)Oh%q@J4m!;FZ#~4vCEe)Kia`4oR{%{;c}9(pSjY@JHLgMP4u9!^|6aSPLmt=ed+Zsd+PNbxj`RBk~l=fMDneQL?*S zTLwgp5@ltmjPLdtPjs9Co%zzVit*8C;Qu^?Zcdad?J(%VmZTDb?C4o?*b?YHKn#;P z#>B~^xU2J97dntrFl-H&*Kb%V&MU1-d@1aSV>x~9BBxAZJko#etlpoX`I-$#ohsA} zm2P9nmF~FlO>ue{2vu}1fyag61T66%`MY;NX#Im}Y2MdL5x47bts3js^GOFQn_b|N zOFd1WJo0R{V!%E`+RF88w|sYe-W%Os`G6D_B&|0Syx7qX-K15+M$$3K5z%LE3z&F8 zQ;NLT;CO}0QQ}WAYZCcWP6kURD4Z4YQh(aAy<(R>3 zxzWGu2JZH+mtU+ENtl=9P)6r+Q&wwdp>wWsa!yy{9rYY7157j;{xqguuKf6Rp`gPUlr_eS$tc0z&Yg)QUFPC88!eI+iP(>K>MEcHr>j0-`ueKVMNmdz~ zF8T_-0k0uc%I>#;eYWd6q1suu(VHCTZi72@hyxs3Ve8|pIjC*(Jv(liH-9pTkROm) z86UpQbUG!HM3{Wb< zrncf2MDO%=HQENt9PUkDt>EvE*;O?o`rvl-WN0#Dve2~wyv?-%TvgJ=zHv=9@?++> zQly`kPMiz3K;7F8kU4WikWj0iInJ%jg~w(d=;;~c?npr)To2jF)b3%BE+lUKaB3dS4j78V@S|Z{y&cvMw#n~Kay8s`;_op ztt@eG)YV(*SXFas&#>@az0eMriG!@2`w z>(XU_&#{~x$Q*dI1>b_*X*hE;EnqGwO_Ad$qrRkdX>J%PUJ~)N+>~)-Rl9`>X2rbn z9grd3E_(Rh-%Y63Q`QKUX3~vO4jEH_trQf(JAdM9sO*TM`L=(&(8qUrXee4CG`frq zyt`c`(2QoT4H55bL5uJt*8WEM|M63EH=y{GBtEt>w97Y`O-!sNH8W~6xiK1mi6$1g z=?~u+lGZBQ6;gxHsKFgma;C-2XxeklJ`oSh%v1btBUP}~INA{Xo2?ez_m7~>Z6$ej&8c=SfMp0=#PUPzs~nVrFU zW?_loO_BI^g{ZMxL6+V~+{2Vng>(12X7BrQgr;`P+l@LS-c-*Kacty&V*e~(&_^*s zS>UCxF#6z`VRuHHOea&E#xoJ_8b<~wH5L27s)4?Qlf=zEmMQ20E`b;~J4|Aoj7k(QclJ8ECcmnq(K(=!&03TS%fn z2+jVOO8Jeir|$jYUdxrlv2ocMJhwqqTViAh=1XsBdyjdKquE;kj-fsU6}LgYeBChmhI!E$C=p!Mmr zKL~k_xi4t=G}K0Omefgzr9s(fek6;}ocLsDD-g}jh5MxGK3F~~{$!o?UoVjvanp*O9xnz#`RC+cb#^`Gc}Km;ERjo* zi-DeRP89Cxy02mtcUnaHep@I$pl)7&X#MeM4A6e#59yyXxHxjUQYzpu;WS@ID_-HC z;SYMw`;p)rp)8F2@KNg%JfdzDbhY`$#E_=9u(Z92U1s9W$j|2K#9LRFE0FI#M0z>1 zN`j;bj*6O=Yfq;F#O>h1NAiW%vT{ek z&g>(=uuCb2Jw@J6mmr7`{c* zinX}^AmW!t&d9dEgwS&MN(FUiGls++;QbbTVr@B$^u}&{ZkBxw1SLa2egu z_2T=W+*)Ct+@QLQVuwPqNOUcoOB=C_EOD$;hyWUeJT^9)Yh!hKRW>}Hn(x+fOFbTH z@}#+D=aqg{(exLRS_5c>o=H$Go!M+){2UCzSsK`~*yd-QYKaA~|+0U%(^gEqfdUomh_3N&DUoZ)!6GkTZ zEU>a;)b6kNmk+p`!rIoTDZr6iQ) z)nc8~y`>F1LZ<`iYF<}ML@Kz6q&b-F7jxKQR`bMrQH5qzETODV(&oGle)%=(RfiUb zjCXAp=2;Q(DV%X*rtsp0ShnT=1P)baG%x|h82j2Pbbq!yV0*3yZ^N+Rg1Q*TpYqN^ zJgJsLWb>ef)vA7Gn^w#ln`Vi;>C=9i?mrbF%F3fJn%-YzVYX*s9vj9`&(Fa{ zroGcV=zp-;W)?c&R3Fd3j14PVFuy)3YSiZ;Mc1Qz=X#SpqWpTRt9;YFNafM%I-5CM z1of&Djinwo)boJ`ES8AkuZ}u*?zjw>^W%{#SLF95UiE4P5xeIRKfXs6?qq2<0n>A) zZ$@fE?EiSs9lhHnGtElVzLVJlmO-vyUk{i+BjEfQfJ6ThGL zw6{I@pr6TNc|Y)t!G6&2QW_#=w_I;maM6gvCW@8**&iO6D9fB0pZC9Y+0Nhtl6R4S zI;kZ=N{adUV{CU;H}Tq4#;!=vE2Y^uS~WpxWyW37<-^FITZO!{M)clU71eLE7<5cd zSFn{GsR5>%Q^8V&1*{=VwgDN`^>fpQ{DBubjjW4TsxqxGd6~;YOGDMa(@n@ru^lz0 zod3DFUln*K%3muReCSG%z$E4c1CZnC2|i-($RnJW&V6TYPN0`kc+z*%B zxOrWP$KJ+d`JhrYq$I3z?z@7&FTNw#i15eG){8)6uPO8HcOd^@0l`D45eg^@x&}mp znmiZzt~l0qBzy-YI|jZajJojNNN%YLj4I#Jd%b9SIyDo$3vl^sNXeExM+10vIkai*m(7GE12LH;=?u&@XgQU8Y6F=#o9(C7L;X49PVBl(Z3Z4DRybAj#zRWyGf>|nKXtHuLS!m$}65kJw& zvFM9{o>N0d#yX(5+$h|1mg{7jreV#b2G}4hx2e1`!cwPRL<>g6XI&IOe z07o3@n3I0n%wR$3w$P|=rCu(0Y0KTtC9w%6%nLB|VkXQ~z>Dl2wSTVi*qRN2n>p$- zf3{_a1mESb8)3a#<)XXAs4eq;4k>E~B#{2C?L?1pjfLFyFKU2z$$zd&$!xuT`&Z+e zRG!XWr)PmzuAemMA~U)QoV!#{Ttdle#S5q0LIcdQY1;CFii%3DDSBZ%YPs9_v2Eov zU!_1DG;7JCTPzSreXIr*6&*Sz9uh@b!qcEu6&*FuN#wQ;orr`H;g`#=k^+#`PJr;Y z(yP+GpMltPz-;&uyt~&O@}`dSVQ-bM5do~R&m&)+Xwa9Bc&@Hv{n@2wADA;qhAD;H zRmPTHpLyJy=G)F&*halR6z*E(GNfxY~g$AsT=XxB`ISkpx-S7uvdQT{$41c2;Cu`mA#KaOJ{b2r%Lx5fd~Zn{mKep9Wj9e1(Ph z-Gj*Ea$GVGW_8cb05{tR9~6yjT8W;qH~T5jsu7y*%>Z*`aur5d#>#7F_84|o|3tm? zxxQ=hSuj=AnOt-!fd<;XHU>TgP7!x?+7GB22oU-uLMe2-nHFY;&h3uRoNoeli1yob ze`&uD5J08DH5^;ne#qMF|G7W)=9%eI4<${5h~yLTP6P3($+(s$cJi08LXultyxCFs1<40o09K*{D{ zAqW9XceBSB_)^5J(*1pzWIMLn?~UDBdgVw`K-T-#II^ZikxF7i;md5y8|@&$7Ow)q z&;5Ch4MWt^X(78qP3v1;D>r@vdX5RS+1;w}X2YW_Cc@$r%?lO?$rU5|j(oTBDG(O(`2{Fh377A>M9^D9fx<&up& zFlO7qdsp?{wPJ;GcU)t^#rDdW!|(?Re6hdMp@;hg&yiVL+0}F>wtFh0FKLeI7P(Jdx}B5N^1HmJY-DY-@&lJQVbF z9S&nts0%6*q{X_8lsc%sF0}-ZXK#dL46TXJGr1beCV36+2`1QDO6UTw+DKlCLy#k( z-qdlP?MVyxB&v*_pf_r?+KEStNI~8nyQAiTYNNTvJT1?_<9}PWr}0(dUeevmH%Z2I0#r^COUjyoBlIy@ff+5-m*?jN(%=(`3%j z>sXeTiK#fuS)wP}n^7uqa~zliJ;|=j^ZK#jY_7}5C08MK4*J1mQOV2GE*WD9k>AY2 zD(pGj&K)t9rCiCj8aC-N**1SmUlPY?gd_&sr1hlo8z!{s4~}}iOP!iG3WRt6z=WD+ zQ#85D_ij+tw}iW6uf%-@Cw(z~Xh?&!Ss%q$&nX;qCXRD62@FLsN5 zKnx_&;?roZe5){h08|8BDYFV;7^oNgM1LZ`6r|ss<~tefxO|km(J>qPuk)nn+fp#? zk2I9!(^t8n@tu{W`YyQjc@K2@YC|d@($;N}eS+MZiJlk82NJC=P{seC6slXi6U{F# z#pVT_y*X>cl!7ty zsgaKsONu2aLuVKiEXnx;zAbF<%4VO2(JpcS$Go7d9d@2@-`OWMrOup_S()t)B^J3a z-m{e{6AWMH9RZf!{Lr_ai7X+3Ge6#^t&JT7T$x4=T(T_ZL$dAOn@CKz6-K`TU|}fP zS2?cEpR=>l)cmmC;ufX#&*CR^Pl5&60;q2C+PB@90rM%&bHB+_jj?CUCyD9E48Nms zkwWMrs%}=ML|)RdO{q?kjwhkKQoG!ZqV^pXQx)}VmTxAq2a+XJTm~@5 z8!dORUl$e@^k1F^ojmdOBG)e@Dk>_<{2+LBxMJwWVsO&Mbu=ZEc$6_J_zvA%G&Dbs zmqW1iG={KYrcA4%hTY);tw>h|Upb*n5O_O4A{w6fTh3~}QNLI@34qC@DY5qTSj*x^ zbq)$Up2ZKt!;l706)U0sZG>=lZg81Ebn zZU7Ufo1&~X=o09koIpiouL_K9Yyn-1&p`W(MJSTrk#U}3eg_z={8&^5z^tQr4UNXg z)it1%ULHy&dIN&=)8dk~Na05RpMhOuwq@Cajqlmy%Q&%h%4Qo^dfBx;n~zr0KoHe= zAcEE#B!G|H^^50A$OnuN4a;^O7DAvBcI zLMU02XxxN*&m;U;IHjAk%&Oe9RHLu_B+8x?j*;0?JL(@*(jg)mh0lkH5kk!CsvF@0 z#Us;ki#I-r|9fTSyc6C@5qG+P_I7+c?EU-q1;}6Lh3oH3Z`0N^_kgGvCu3>9ioUVV z#JM%@&#j0xqJbtwDWPUgZVYI2vDmt3h7mhAd9-^#{ZMLI2Lt>$uin?FQM=ud{AdTw(xUy>0?Ya-gNC?8;uv?tvhDDAuQ zICG9jOstd=4dSfI$IbH0D!)&B*%{b8flhLMypa-@LIv?06KU{K(JH4yBZB zXD+B}T)`!h>&n}MVD>hM7m3K?8` zN_l)BjM8Jf?YXYS{rPn5BOR-<3(y&RfTm~C=_l;7FFw<5HwSLAbAOOj$pWT)KQ{ys za*o6cGvOva_(K#ev{~vXNB2jZQ>WL3`F@OOS34SaOQOetv#DcTPQ>ozz*0 zSy!Q9xs!t3601s*d_-ZQ?0SmidaLVoHl~3a>+^Z%n2Wm}R%`$iecI-Eeu=g%x;wW- zYX@TcgP(XqE)vgE&P6d71oy;B!U3Ri&>l`&ejBJiGU?x?sk@DOvdo z&q-GJx5zwwrV}xSPd?GwH4_4|llJ5)YXCBeeSclG6vHFRa3X#VrdmRwEo^qsDZJGr za5PF2h3AoEocw6MBCQ|^P>iM)3{N=22}J#p)_cHNg+`24t}EQ@rU~HSP?nymhbi0{ z3%6CelULGEvrtAR5gbk=8}ku^0WM-Pn`f^rxBt}Ft=pkS^6dh8A!bmVmDEWRF79Mn z{V@=+9ZBfMa5u(y!B?Morq+MZvrTXHags@J^s~)I?a2`(ifp|oEt28*>1sGmnY6t; z?9bW#<{)@`S1D1b$QEdgixg#EWXIere#}C`#Bj{VzOA-N?NV0GUZ|rzqp~M7%sf!A zX?n~+R0y4KkoS%SgN)xIU%YrxFa>^X&GS#s71IbzvLY8ojrXS5%x z_MDEo`BKC)RT5*Ed6B1+VV*56l4s`GQ5Fn&2T1;&YhCkKU1L_Si}u!ITD`*MT+I1) zuP)ClxA*}Vxc8=Oq?~J!`9~v*b_?Ksp&>Cy!49)+rbyn$$dH!Zt`N=;>}c!39-N0N z&ge<!VaT0zydlodH3r@S^GY; zD_JRVL=tO64hGyse6w(wbjnYFW6%Ec_>}q_%fs9k%Hzn;=gKqDeMKtbfePx5#%s$h zpX*GF>t+$F9E8gcWBkSg_|7LQDd#w(1@Mxjk=K+RLG{3qFS21)oIxukmE0d3RV_X9 z)KG^Dyr~Q$s(m?ghmpI=R01G&<*)oi;r;+%D9Lm!_r7$R6nlv3UG=PVB^=E%xcvl&(PUDKaH-E#|(hQoNT;;nxrtvk{{Zr)x(729*C z(=sVnFT7Xfta*x{eZU+NPh-w=gUR5PslnF>OcqQp+(Y%sJ)Ai56nWR3;@kh`osl7I zLi=E>n(1^Nto%GyDJ+Y2rCbVqDFcOH9&lLhG}JGGscWq&QIv7b_ba8=!7m?nCv?Yj zdtvUm_7D2%bmu{EV)pn>Te=Zuu(dd{lc*6^%}KqojAR zEqCKN%?-Kq%l|yObOTCd`JDZ%71@`8qma2<+x#uS9 zEOZH~1)t`3ZrGK2C6!HVG<3tWejT#Z{b;Wbl1g^gn*0!HP9 zkrlND$|1aJ*)@B~3Rj)x8A3|13b``lf;QC-Uax4|QmN9CZ!`-bPc=r3EH+0l#xx4K z%~w;5Fr$RBbg~OJy{g;xSl5h-EqbxJh#}#MR`77p~ zu-mIdVIjsUuTi<>v0r48O~oGZ(8p?Bgavn<)9RRSew`3AEi@Ds5wX$JQo2UI^BG;k z`|@+>wX(EB-#l7b@j7#t=?IKF%X)83=cndTdc&|M2la=my7R<;wgU$mxCW3xXg9*v zcc!ue#FIh7v4L5tJ01?yU`Bh_f8Sbg8Uf)w_W4xL6@WL*{dj+Uaj6e=9HvsWKFsQK z!XG~-iR$YDqU77Z-gHal-~XQJ`o9kwgwg+-^0m;YA>VF?*D01@n2}x9+*k1hm zFvO^TpV0O99$E@)eZX7cP)&Fg9BXRM6ZnbCpJhGhqD`CB=cGFWG zG2Y&`DK5t~JT{(HY;vEnNkWXr;;Ldfp{_;tS?_cD??Z9Plp>Fu8X~!sah)J9-)DTk zOO*Y;*|GKL+5h3WgMGvFf_NUaz$;u8bz`cNy7lmP`OqOxrYHJ-i0u)Cm_zXFG;tuC zOA7xgpEiS2RR^y6L_gS)>A&Y2qmuuJ@IEgo7dMB&)jeSZRw?O`L{SLw$v~DhGO%vb zm6aoV7hm-t-rbD?eW|zw!2EkS;yL&HJG!G!O4#tv`%}ewC~F+rm5#fMMp=g6!t&h zes&?ot5phRB)09l;?G3jI3Bu%$AmlnawL-HAxf+&Z0<>#M!mD zq@)6yQu`vENrjy%Xiy5XuXb?qSg+!V3>DOIOVj$L$QBbu#|d7I!w+I0XkKq005BZm z;Nr>%2ne{#v~J*A0?I;FU;pp(!E)Zju}i1-#VIK+c_(Oi6<~8<5uBWzz3hyHyhQd- ziG~HM)@B6(Lg%X^YRA}<=USADS6N}$t@iDot>e7^Zih&nYvrcIq?orn2441r5LaFW zi^(r01!Dhx@P$+{YgXYG(v|5%W?U#mW+ru6b{sZV;3t!YTnqU>~Xf^YaSF5J<&+%=#Zt?;eK6CI!rQB6P ziOUH#8OPwP;;3!!((a#PuUd88zDBhid=U5R5x-AZZ-p=zjD>~84Et33*RN?k|9$7r zx6ROwa$osJ?`LC`WtKp(_O4VvGi5-gLeL5 zLYhB3j30APjby^j*yNuBJlJ9!kwXkWiLLg+vj;r4`!q2 z%%+cuPp8E@Ca$ziz(4E_NM{blVHkh*`MrJDZ7#McBpnMPH<@7VW|yWcT|6u-Ht%d2 zXTS2U=z37+hI^3A0mUSa)H<0J>s+22me%1H^Z9d{fbuT%?>E44j+f8Hme4VP#l5|^ zv*#tD|5h0G|2>UW-QrbKZ`O3;+2a?Ywld$@ROr;`?J$3{H!KiC+ZP;bmmjR#<`L%~ z26V12!ARR_@aHJO={F;}zyo~s#{QVqh7mzZ0+eW0am`t^OxHU6va(c>dTIAPWdXQ| z&~O%P$-9)ddip@9{0`y69Q?^wk99141E#S2V0gulRSh5Kn=kG@?-b3%7s@`>q@L-I zVb@8bP&%0e9tIBk%^#2rEh~Fro%!*s6Gm4wy_}%P!$U>JRi?7}9-A!@{>@@fRYAFv zKW0?fB7@a+KSj*f=nsDPf|VgIC4tR#HiIAUeMxNK=Yh~{xO#~9(O+9m)ORl`a|l`Y zTPkvHz`V;QOHzj|N$fA7$mK}*e-GKb(Ik2DpJ_t`|1RZJ;^94CT~pSjFcYfY;(YL(EU7=gOF|{|73BdI!`<#eqQsNk zXX6qQ2J@jR#;knFIQa6NKi}n{y4{XKw`IN=EayKm7c>>T(V;L;gHNDzdG|V1)Rbf&PbqM&h%rU8wwH?@uJYy|v3^BHR*~}BJ#!?c=Efsx9{OC9 zOrTvTxc43`VEEc6Dh|KDCUJ;^)}NPEs=w9b_!XfuzkdVuKh7Q^tO&ze%mYV)O*Q_! zz>z0;;fbbkVNP~z>4gNV3>o;JAENm4Lu~JF8tS6ZHT-y5(fts7(fXmT@D1)?SnV0^ z`;yIRdDPhtRgCF3{qH8{p+^tS#gMtlIYY7nUOl>?t`iS&&J9Xj;AjTKm=Lb2llU43 zDAl)^M^G@v)Xc2TVFbi%dq7tMYRH}B!wY(Qi;8c@;$1wWvj#JZZ_V|j@ku7I8Bw zoIsOkCy;XKv^Y|OgNrlj}n-V%j73>-G zYup9G;wuY@J=tW%W4QEkjO*GPO~zJcFeeRF06rh>Q+1HOw^kNl!`%)DW_iNrJe9za z$eU2%%LxiIb6fG{BH$3ud={+RXwPd)7IwUuDCr|X4u7UZ%yaGQCk9%v7H1_z-@hlz zgPw|;4Z_X#X2V5KnUy<={s)5pP_!ai&h2tx3aWu$l(CM_-F% z-hwn&_AnI^CGOhaV-E{75Y@sSYY18@a?vyR;J2krB69P`A&5UTfLm3!wF<$|wJqZN zNQ#Pg)iZH+bSyfF;O7DtzeKTHXd0(JY=ows{rE^$oQ_Kux73`GFDph%g{^FnvB$S0 z2-*zYgVeA6p zNUuw$Nwshjsrq67F-!2?P%qu9{njMS!#1(hkmh!$eL(`DH(6rdLg(AW=_o)2j=a!( zrj*RZksLiLc^h+Y&(VkPvN@C(oWZ-y^rWwVTKP1ojp@{go}wDixD`JlfdumqW}QIx zq!UP&X8RfPaJD&&0o$B<$zIPSoH#-xk(kcglMT;=)+m0Z&kTxoko|nV)Ei95fQhaG zR@x?6`RcHjBcubp{-zooOyjn74#3Npn!j^;MDbR-&(InD4N z4;UW--Kj;&3aPGp2YqG*%}HwJLWH>i4>+?iXA_>O*wB~#g*wWsbRNE*a~m#2{ZT{STUQkC2T7I-MeUR_Mc~7s6K`4z(1^orGkykRC|A zluELA3ETXHF)v87!80W)5dnrUC)W*wq`KO=Skt(DDbULsM35+cXZG-2uc_oUGpk1= zcklzGGz_ufOp-~&vK=y9+(%#FUOR!JEKY8QSC6t?Zxxsz zI&!Fun|8!n{ouNLAe%KkSEJCT>Nv+(Jnjt=2rD+{&~c-;j&hajAnVOmSc620w?CMg zrTJIwi>X&k+uu%Lk->9nQ z?IBaNYJiht=mjM1;G?tr^8_Vf$!p9n6Ln*1BUzNyKguO0>##9~oMrj+QbZ@gd!x-D z<35cPd`LpL#r&DD{l;#8qZdO<%t)c0hsC922&1ZB_mw&&_@CC@+BcGCSM_NN(u zqOjREL%lW?m`|Ui-GBcy`k${|Xwu!8+KOKnov}fWEZ2HAq6mpV^6><03 zNgeB?fou^SmrdrExK?NmcDDIiadF0*Y8_jLhRERs&>m=JBF3Pg+>XFsA8I02tMOqt z8&sgD)or2c9uVbTx7WFd=Qzu?#6?fD>we$mnGx>a1L$(JoZVkEA@@Z&USvVi_w}Iz z(J?k=ylPxqo1UKDJGZ*un&|#NmiQOGY{#Zh{33@F934|p%&LE-dQ|3;kEal!}{Y z7U-eX=NyWUn7+gtFf)d5-q#6WpfZ)c0?jI7yQ@1Ta+B3ABy5vlA=p%)?pN;>x zG;TL73Tz5vh^$#1YXl#4kC!v<>+$pb&uLW72K9T1+an!+oVsxlItULV^V8rgGYv`R_SL!_scg2M2CM8Z3*p|P=b=bGoXCVLX#kTx$HO9{xlL;Lr}bWXbKB+W+)xE zsn0NvaSC=+@&%j2#kJL$=Q_j`G)>6@-b{?_+`POfetWvvL7YTnHL%ssXBard$ASJL6dPWaSa%yOk&7;Z0X{+`}v1^on zP<(cUiVdOhj174-6|^2+;x{j>+jYNahLlOd$)ft@T=~BOR)_L7nZBzAg*FR;7mlqq zbp~y10Mo|e?U*xT(`xW%R__kbO`N(uOMn<^uYVSsUlXa&q>%>3K`NTjQa0G!5 z74MC#)2Sg)d<#f~!{N#a6(f16rvEw0d|ZyJGMRuO*z5S>lshKY*P0KveZUcS6Q{oq zqO7tDf-W(y;1m(IUruK9!3AwXl4-kJ)3VXSY&OX?HZ=BdgJ*(NBsMn~SpfClOYIru z5tvVYq(P#wbjfb=Yb+aCXoYa+NusTW6g_t4z?yz5L@dIvgfi`+?tM5rQSWywoBw}y z5b8*N4KW*f;bK^GYQ%WJ2B_~z6HIGQptMI=BTFekSo=;c-hN1B9z(t?cps)7pmxg`x*!Q!RN0y1l&1?RnqNzNdq zohSg}&_`Kwu#k8F+pFC3!;i1DybopX@U}Tmh1AbsH4Ky*&W-xy78P^bjDzamlxGwoN2DqSqx!l8| zfGn2C5Tq9b^3QXBk_1DGW}VRylb25X{nEII28%#XL#)3=alcaMyluv{6V2nvB|C1+ zhX4u0cX%Ecs_`}kGcBi)<>Ev^tD`>&W(8Sf&Ng3-EP7|2A5=GMQ!&tXM01Ejqd+$s zIUocZ@!le^*hKsmf^pZZsI1awm!{teZ^!Y_eD9EV#D@797m)e%1)A?vjTO^;S4rFw z<)Y3^v{BYk9uBj#7nT27_6lSkNy=U?$6C`QkIs7>Ipa@G%oM3_450&qSVI5DH8&IN z6!;pZ#1Kpu9^MxEQ{yja0-XEmFw@Pvr~_j_e0!7|xv?z!AK-+3lxRmxe9_UrIToD% zDV6LJX|ceq_qif#n}P39%t>y*R}1u(ol7)7i*9_KvtY4T7(SV z1+eS1(2#ibxf4=I8C12Ts0P>rGRaE%*#rAJ#Lc8*4&{zV&OF^;J_K^dI=O<`ayIO` zC66;ofWaVFQ|9J#;4`I8UQY8*qC%E7*=tro}jtGq%E9RSSksCHCuz5@0&syNKXFoF9c)sGU zXMyD2bsO_zU(OwRq&gv-7hM4S_MVdd?vCM%l-UjS+2;yWmr*%EtZGvg8pjpKjca%; z32rVbwLuA{8H_C|RQKgBVJTC`w#iZ{luo~=SC$5LmtkQLfnVqNInezN&l40(&b0_; zC}bGa_4~)2W9HZZ?F)19-lcb}#i?G-t5JDigKfs%zA&jP2(n@h$|*f*QPc7uQ7A`J z10kBr?%kpO%Glr%J5rDpt0u@FLRPZiSRc!0cCNnikNwXOLjObg^zhwy>y$Se>G~)O zpxn{tc#+psXVsSU8Xw??6UzGKK4bOWJ+~0F_vNNE0*1%n9Wk$m};K%=ugL@0mo}%P-FOFqvoU zz{7>X*{8COD>4t!Y}Z=oJHvUV-)ZzRnW0S5Og2f1%FemHf8_hZYB zj*bQ=kIr=<=W)eva&27ITmmnF*@DBjU(SbqW`)^bHkkxntZJ*p$2}k~L=VA+pP#>+ zZV6C2qb&QNln@ofIx<*aw$E zwIs@GYwNTwn*qijx5=$%B_;y~*RvW2B4zVU#VXP8vBSBuyqz!dRUf;@crGP#=xAn7Qnm_=)b?W;U>DXm6^ZNpp!qb9@Eol z7L!9SH`G|vxU71~q<;~aW3nK*+m!>e=hz(^Q9&Zyca5(RZHG!=`B)T>6XjpCaw4ZB z(LfooLDjHc0u4yXwHwKiO)e@x~bSgqy53r;vgdcwMQ4y+E1IvC+Xk zI(&k0o33m$B-PaaApKD>$o1noQw~8?f4)7^c)XPI{BYT5F+sd8)}_YnL9|DGiAv3)$)3 zd!6$JC$*EbBtFjnvcHY?Ob{D?OC(-dpRG>QrH75DbmSg_qEWboCi9PMYN4j)7*H$! z#PDM$5Dz(qso})p4{M;dkA@RkgM-T*b$f&HpJrcXG3Al-eaP8Di3)Q$HZI>wAc6qm z>V*b%NfMwfh-F80#drC!ttJc2`1g>$aa-Gc!ZD4oGC1?RLK}-u=JvTX6&jCY-)z}j^+=qh-`icJ%dL=f%q!&x_XAX-Mt=f$=Gr7u zqO?>^Jmdb0-s@--mxf?-&K%K`LRQ@$eXpuAw1$+mE4_@Ad5?1J9Y~;QnRC9DDboG$ z>%+0i7gJC9%sNzc+Cl%nniWj)ZrQ2d9m$f(uq3gn{Aso^Ip0SmhsnBYDFHBUuktRX z<;&b-NwPcSN)@$39W>mt&+pT=>?Jn6vFSkOM40$1Bi!8Ae61xSvxW)jz?`rs$KD%t z%>lqh7!gO1ZX`pNw<9KjL16KgSaZcGsI;nA?UX(EyA1P1Y!5!1uYc_E_RQB6rZTSLw-e|p=uoRgg5(72srP!_6z)S56-;;y7k_vof7Ptb zPi^jm90q;8Du){rIa>lq+xT=S?I$)1= zhuoP0U4RwwOJItj8bfp-r#B3tnnNwp_=H#r@3ir2!n*yuJqDYrVWkHhi+-f*)2HEb zOv_WkB@{kPu3yZH$QRP*)~zg>=x6V*BYXx zde44*@C>H*v#c%_7q9Le;oLKoFTW|NEPzpj6Vap>@+8bunzH7v*v(3NU__KVQQV`v z@5!|?S0WRAde$bz$aRc9uk;-&)C0K29;mT+vfWu5a&23Gtqv2m z@G7RA>ATtC)mO)eP#3KumWm;NR&007Y)Xu4mc-Kjg72eW6+Z!^Lm)*DnlduT^le5K zgW}h+f;hdmQX;*7$%*d#RA;a80p3FgvQdeLc}X+0MOSQXjQ4kPas|eAN_=WTEwpXj zY8ytdT_FO(0fc8F4MDe{j=z4i&L`7JeFl}CnoXR=Q8NG_Rjcfg1QiIexZgCH36-p$ zvLEUD^wG7*UEXQe&lRN1X9<5^eYM+kNq4wK^^&8#r_Bx19CQsB4C83gHOn|h6>p7X zSZ|t`efF3*a2b;N_)vh)OD~OW;wh`uAPpn8>v|tlFgjxhxZWRfYdp|{pwnm&0d^vO zrt3|Zq{n@+b@`$5>-LBx;({}R=w=ZrTGsSbP3pDlHH&Jm6ov^RR}0|Jt|G@@ia1pC z@`%~b%L5>LP@VZ^$T&;*Fj1Nq3{2ACqYHGaULz9_&Sx5RkrhHsoiEy&qDxR8)f1TP z0ZCceBjjBL%#jr;P2-$4<83kAw$;L%waSS_>@OVPm+l2B7S?*0&R5UuzAm!h z4yMEG+1o$S!5mxXoIkZx8HVXJgVPFmjT`cbd!%T|@y>fMZLO^}z~T%n(+asqGx?O> z0nX$R1DshlG(1P$KM_Eu?@Hs=M4CaCW^96)pGQ=K?ok9o z_BXBtM{XVjxFHbd;7*+-<4yh9!ND>XS$cb`ck$jHqc^#51moP1_9_@M1UD0VtbIk} zL=(*8BLW@6=u}-U4CK5_6wo7qZ&m!1NvNslvBSH!&6i3|NSp6o3Z+`pt+cy3H|GHL z%U5^t*SN%DUzK2I$KnX=4%1us_)Uj$m4HfJ*ypk&k*gQ#Q#Ke5irMHLi{-{5K|BZX zj@@Q(g4uF&U|wbOd%n8+9t<6FwNa_4LUw)yKQT4$`eLtzNqdIHM?Z&gYhHiH8YZ~t zlCut$Mal`y>m_#q1Cyc@LV~VIr@nQ2GiFA?cg1Lnc_EwnGC{RbiD<@oZM?OhY559D zmgF6F>Hyt{5Ap~gcJ7=p>^-z~d78)Mmg2qAbv%zpshWw?*%j?ivY8s`ijH6rER8NcF^+Evt085D0^(D0lx(uX@fML|7utm(7=lV|?3LF}sFJ1-30tG>Yb1goe&UA?)d&_JY z;(8SZQZj<$e35eVG~dUuo!VH|Pb~49&cFZzYcayHofq-mcD=|#Rq_GUUeADj-1O66 zRAn6VSP+k#c`;h{3N>%fmGI|5uEr*h5}e&H$$q10nYm(IwYzkISK}m=d(GbN78ZF? zby{^()rv?EI>-kf=$9^{3rmi577dH|<~eh>xic-wyVpsk`$A zP%77(4aC-4yy(~QiWo1@UT0pB1V$W;UY#5YF+wvS0y!sVZCf{fHc(;!DhU0+$Phe7 zmr3?mRtb%t9sdqAPF{Gx=-%!+s!k%@-<6|rGDE79mP+|6b;~qRH5gDzZ0Tj)=vgez zT7aoU(1dD@E&T!k30Iwns!ABu`$Qr8Ptp8_2!3KD^u~T(lJ8SvD-08z$4qGS9n*>+ zy0THAM@C2J(vJAk*~4XFRPC}WjSFYjqqa_i`nzH>E)f1vYvQyz7K0O`zKQ`fnG>01 zVT-mOmq(si87*)IajF$ia-90P_-xBG0P8VBLqnSFsLS@3$Ep!dUeaOUv1xp{IUFsvJsSv}f9}EQklvf5@Qz^Cb{ju-$D^r4?)1dQpKJdmO)SK;MBL62cop zE6f5FJ`PYIyyo~V*79R=V#^xOGtP~-L-%jTm*Wr3qJYh`)6thvY^p=e(_tKm;X4sG zgTuhfMZ$QA^Z{O$)Q7ggEs&%F8aaYikQyLzhz`q#jdvdY9_M~0m~U&@whJ>zv|(L! zui*zEJ8@2Jv)_O4bVOBo9}uyG=fnJ?GwIIgM3F?8K>x~$lK@`mZEsaMIzJmv$gnZH z@yrq?05270NemefBZ>3`IB|>Ke^HIo)}#`oS>2fPh-!pKw9aqfXi*gw91S&SVu-JF9WrYB}kK_#3l_j&jr6 z@f$E?@^*xmDCO|>#sS{lS=m9Pr6T-`79Tx8)({@)T!Vx@I&OJZkX7^_Sk4fdGP4KL ze>%@sW4ErB-^oJ(z!}Qw*c6yC+TlX9xUm+Z_VA#54y$~M3krwXWJ#|bSCgj-y!&!S z4B8jpiTNkwmTuqmQ;ykFw3{=^JeB19DY@<-q3y+-F7YnWVTS;t)?z?l%}9s7E|lyq zPCBV=fas~*NzpkjpwrGnQPHoT%)ap4pdE>6uj-&)Z%vuJ_y<+xqi`IyUhh%f{qUw> zbS`a=K_`(yV2AMw|BEWqn8g_2dT%!SnTqfa-@||ZKQ}boydP#X98*-5-0-RW`hPOA zR?~1DKU=pqueSI0_EJyf3(jQ`uvH5j&KRQaQ8#LnIkz57dQLFFG*xTv!=pnOG|4`W z{XO25Vmm^ztfzDG;>^c5ITde8YlNm_6KSS$^kUF&an$AA?Wn1}(sdR)hoRNPy}g2* z+R3g<)f}xc9R43q9C>jcT7EU}@Ha7`uldA zH=H=O?3XdyMyXzIZI|GPy*OV#<0(<7|3C*l@4Msy6Bx6|&w-+rZzm1L(LO&vkF1eL zSIg|bBYT}MuZnxQv-Q+qUcm&J*JS95MhGQaxtZ<7{^Ehtq%8)+@AQc8`cT8~j+fF^IkV=bb2J+aK$m1xMZc&gRc=ggYaJq6}1g>TTQK zt&o*D$z97VU-3R*by(uJ_(^jOmxK4~k%I0QxeU!>1jhT#yB-hMBa&tq@xKb7Q`H`x z;iu96t$5%6gscRwpFK=70`m17?hd9^yUZASGml#*aVER!;d2GQFGmbKAoWf3O9`^a z6G^d?38~g@+!VxEIPp~v`E$bH&rLa`uP9;~gbe+|`cFZ{#p#MDAckROPht2|HlB;`7a!2mceobLH=_5If$EUYW!kIs4-*2#%*pkfL2g<;*OVPIqt$ym0q^p#!~YAZFF~ZtJm&0C6{mrce!K~}S1yx&C5cSLI-!1^ zB0@t3+i*|xRMO$MuUP**grXPn^fPwCssh&^mr@ZQAe_`f(?;NuB>H(QUASp7e=7I6jPoZ<+hSZQ1Nd}v0_eM;Zi7MS7 z8FZ-Z+M*>q`Xy19_35Luu`4TOpaW}$Li&5-V%IehQIw4d&|a@I9^PuibaizNEJsF0 z#=U*p1;*lxW5D3CQ+LKb{L?e-G7S64_0uL^l;JGms4q>iROUYAU7aqYDxVnMPyOSH zCTAVEf&nd3klVlQFALdSq2NCPdFc%O@wfrjuj?2F1|88RADw1B`dJCWqV0~Vc0 z;v`AFVR!$#KS(Z>yolLj+3#vzKb@ed;}_tseTb6Jmk&;sK|FJ8PD z(utqr^r(7xiwxqMR=o8=A^n_k{xS%MvYihV0rlyg1EfCa?o*<}VD_l0^U*%aBL|ir z;5EZiKL%;Rxfb=tK~d`InTj2^ObcO zO7S#q@1@XO#t+sftPMc4aS(J9^0f3+NSeUNI0OvI9n6k6afQTMBdi2A1xae34 zoc;G{L#4lB<;+c;{25Jw9z}mUjC;|?jLb=CAQ~E{{K8CS9zf0c7X`WTMJmhHrgvHc zBe|e2dMR%gq`rvz&cYB>;>XJ1S<+88H2H)fi1|!_{^z*e_3iE#HkX#4t@0)z%Nh`n z&#wR_oS_hXxCQVlESPfX^3hTW`oRgKBuJ!&B~~Mlp^-K-V?h|LGdMwUDM*WaB?o`0{9C+g`aT39@r64K9R} zi-&QMy!(|zeIi-=Y8KV9L7*>5UvU1|<6Pl^9GIqu*7D%Up}4!pE&6*COv>y7klcqc z0wQQ;g4?HxIH6StNFa#1sXq+FX}{%sf(ed#@=39L>-j6L&YWj>R`mU5&Z+zwIEse? zIspHsf_naTvOVC6rMih_;9J)nn>2N7rt~FOZL&LV&ytIrZ-Zd1u}%CV%$v zdJVCMvHs4i#_2m6f5jrHe?FV+lXs}~UfmS0(j&CRc2eccaQ1rHm#jP5A6aw+n$^8Z zKC-Y$7zqHS;TA8ZXC`{KulJCUrwu?mquv(DU3=QlrW<=UxpE&S1q!>B1czk-*ZLw& zbA^Wie%(fzIPX26?pUIh2+2G3ADi8&4tSJC)G*|ql&o3nhgJ&&a<6coD6eZ42O`ol z?uPhLL>)b%?(yX2C&fzo#CI%g;_rqRsFVd?xY>U@X?5+dhP@}jU|I70CWFK5<6M5? zU1JFqqabO#ONaeY-3fzTx&2Gt7R4s`(*C9GG-XzEb(>Qs$MS;VsD~bA{=aGbTS#NIv9TeO zy)0eN7(Z8r-@M)9+5v>jxYki+|Ieg}wKg8nmN2VEu4cXC%JC%}Kw9~x2|LffmDuc# z$kF6TTCV_;R;I~qUHyS-xdG^25!{7n-Lhz#x}*D|$LNF4Cxw{)x#0sU4=Ozm4!DNT zFDZ%g*~jiv{E6AI7~eb8uUkoz5WLBpZs6(D8}YZ@ce8*d4%@d$#s$C*U(x_LvTy-J zk}IFWMRl@U)e$98U?%7tpGp8ok6W-7dJNAw;jJr9)@zX*xO?EhW{lX+0Oezin{eHG zQ|M6NPiCRUs`ooG<)SXEi>Yw?k#aF?RO6MwmOQ;%LwM3-XRkbqc5WNUA9BXKlX84nus|Ff?*arRU$9bT7~qyl`yOLc`@< zM%J~;NugHyH?V!Dt8|1=jVq$Y7cS&!pt(?S)GgFK1y?nw2eA2+;i(6H4xmrsQCR6O z#}RE&|KOL@Xa5{CsdBzL9Hp)2qzAk~iDV7#e;q=EDjIRitje%MW&VncD5}(Yb~WY4 z!NTSt7NV$SG43RHi#(Pb?=4e45(-yQR3+P?A~SS&?p z6Vk9n(5+~EJ+>a=s(jl!1VCWmWq1rpakO;a@Qr@zzE64JSq=m_BN=Q;5NiIg2PkB! zwG$eF%>Z8O%JhpmL}hD<8lMP~f`2hU$|}ZIz5A6yR!c~2tzC0J--I3Z`^{XwAH_pR zhA?O3b7!PkDqc+hkKI}JtL4E48RW9$yt91pVSAv!*{lYS<)Pfm(lT&PHeV0Rtz~MV zHp`!MeK9`0?@V(6hkI-KuB1XGNLb-$W27De6cSocie)13lJMNR4ekKE=y@KHCxpHq zq5PWm&jz#>o%IweEvlf`johpsqk)Nl0e`VtQX}a_P1Qj|Vqtw>)lBa~S)3 z(4G4t6ih`3E$|p<)0f|0?=PZ=iBgcG6kq8Yn49N)CLZm&GiShkg^+cqj!9uR9d~VO zAD3#aUfjQBaM8rOb}yfET^;+R6g`zFY3`O?Zc`R!9z)JA&O>N{n?(7u>$T`%kqn&e zggA?5_Ox%eGCjq$&-7g%`oZCrn0k1P_Z85v{%hjG{hmsaPhf4|H>?fO)gQtTG5Ozm z2bJ}+;xJd3lsU}Oe+svBAD{^kUvDJH1e^t$K*wnqgxgp8Dr(ih0Vg6pTL9o29DK{U zwYNv_i5|LEhTqPRleZ!}y*P|dyr(&J{QqnMHFxhuuj&qQAJ+ck2@JY6Cyq`nyR8no zSF}+G?e;Fo>(*p5=r_Z4O2?!o)~bon2m%FKn=`RPWt7%FFcl1QZ;NJJCcHYu@XLJB zwnq8@9Vv5G^S?zgEdgZ_7h{-tEdILvX_20u7Wd$j^{DbVjDpdulz39*S@hR%)egzN zZ+~woA|o?%b=_xgJ+vS`A)%KjJAo|aXsYe>Tt|;yX5uSDgAxUhK*W2*ViGvPv&Q?? zHNJi{B<&wHAME@>{tr{-sy(N-Cz|b@_R$zRNQwc@4)a`c+X&d6rTqhE=PCDoQYH%;UI1!5 zQat+is=*#oEf)*^$s}mKZf|G1h_q)`{I6^drjma}5hblABT>B|&l==3IujT|9P5YT75mXp;m+`v$Q>Cup^!E7@ckQ z>JY(sT-pbXv+h$MXjyCxd&j5mmg|nrtf5dnYmV9dcj{KLCzw1RGHu~n&g;1>w+qfG z*}6yi()?ril73R6o64@bvt@crfH_)61%LuAxsNU(e$8}3)QeKT-OS0MzK&+ozn#>* zK-FV5e4v-!l0yf4;pl!~ zscVU@9@k8)j9(@;=slkH z)#FfL0%B<)6GQ?Nd#!`S-Cg3Wd*^v`kw@gnjPok@PuJ&ME*!j*#vgYQx$%-*l*(xq zC4UQup4{mNg5gzPZr`#}o97+kOZO$BKEAx-;`!)>YO8J}Kz4#D1yPhD6(Hc5XKHH7 zFDY4QiMptqn;lZz`)zd%&mr);h89x&u;_>MkvDzTIuZRBxGZ+swb^u0x>+;QAAKjB z0`Ejy&Z!*R<{(8Y+F4#lK~CPZ^JcgD!`iOdR?cwk;I4KqcFZE( zdRk!$zcs9~<0)XRZMoIwwxi-TFTIm7tm5UJ-QTWat=lePl>Sp~%h4k#6HH#M?6d(x z5hdz+tVCJod5JUshDZUOo@oA#fFbAE4 zrvjQuA9+0N2$UtZGIn(XQBS8s( zzS8qUagUF|2ncLIn!2qelPeR6l-SP^ir?NN%SkTIMS|{&po^o3=kFq@?Z~AQcS{Bk zo;xW4@`Je__%DVD1>N;gN&@vbvtY0!Hkwms>|0R&PyQ@yg0@#*pe3&MW+b?7BxVrY z`QYnDiLHT7Z}sHgjV$N&)deNK3y zgJRA8o^f6jGOF%R#6moGo4CDs{THbxOWL?F5A=V%{=FSjw>{zNh0++lNJ3cF&Q@Ik z4L-XJs4chF2xePzpu@5%pwnf4oEhQqR<0h!O8?^$yarl-ezSNk8|8(1jWe*V+i@lg zl$v!Tp_;?`*pt&Pf8{(w0j-U3%Fo$1Pwyp*jPSH^oP{RYR%hq;Iqj+P0pFs__6Xt< zy4}rzE2u(>B+Q#>d`Q#S`?*?B=c98UtFQHACNnuaR|6-7 zAz;~GUUII%t(r7Y5&ID9el$Nju;C4+EAjL4O8F@9p!E@Q7Z`q(%~^GATCorHf*Y8) z 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 literal 95583 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

Y#FJm%k-m4ok+wYSQ_FAE-9=SE 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

%$J-Y8msgy)7J+xoM0^8ja!dq| zo|w5psc)s3+B$Dt|Byq4T$n7!bb`Nx#SYd#jX`l`w!ZHJRI+}hH>t0+u1}(I%I4(d zmKNXY6~58zL*D6I8N%kd0pVir>Aa3Cz7JTXt-!XWgs zEIBrlBvbk>I1>ydKGplKU}SA_ZG$0whLk{Y9!j*a6^=2qZ#&H8zlgUM%|@Z|Rp`@u zzc_Sqx*xTo6Qj<^+IUy`>`Qcd_10O8|BgY~t*3YK27#bd7Dw{p5gld8a#{hYqE(#g z_QI9W1~d|Z!PHg2ojqEuL@7dBy~V>JiAhIQ6^i(pL!dN`x3h|#_vc1DKNxEP8 z@JRFxI)yv$bca{vTBp{^Q8r}AiBf;V3!{hIqjE`ZPA2?nO##a*h2gcH4W~@d!GO{x z5L{jC^ffj~yp?2;XP{2&)>+97D=>@FjGxWwS#>nM0Z!24G&Sfex30E+>Oo)*l$eBc zXE@12FWJ?f304t@j-0)3;Z4Kwy#J9ZV%le70}6>SXsq&^m-Roj_vX1>7@@TQ@v|9@ z>E=>Fj|mZV{NoZtk==is0z~%d(_*7V3%16|Vrc~*6EKZ->E_)0R?jBYe}b<2Zkkmol8#d5KPM=6+J5f;+})yKPuk& z@+TU8zQ&>C3I+w2b>J^Q$L_?m^?B1w+B#y~=F4lF;y^P|uJy0&2_E=(sm}dAI)$yK zp*5s}uTHxgQc1Ri1*^Re5t3-?EC9QPj zY%^|gb`JFk%*K-$0+*Uxe70krvY9B1r+h~6O!oBEf2;f#Y6B3r>)WnO8;JD-kW*W@ z&IeyEuW~p{m_30xanYR_WWf zO{hiz;6xz)QH|XE_Q^_hAc=i|hq>qzIkBru_c>Ip3jGex9*3x#9q11>O9R`;h z;A2TW(=BS_p|e|^$erzB>7u!YD!_CCP%93(ybgoU$H;Eo+2RS1xVZgLqc4%mG+{HmY=M3)` zPvkVtWR;kKdYL{9yG*RfG~`%yKcgEn%1GX~tvnTepJ2a2uR$lvC~J-IVO79`on3}K z;6ecd;@Dc83KzINAE5?RvM4|)Oa;H>opx|ytm3`r1K_&I2rE(8Qp!0($!ZM;S6mRs zE$BC1(8$JpYR@BAvB#9oFY^P!%SkXcssFkMmt8`OJ}ioZ)i$383wFWCPU8{lwhuK* z*M^%+O*>ho39gw@yKOdh!CJi;dVhK@#QJ+{38TY9P)zY@nJJvrUUhmW7di_H zcgOw-1gf(nu^O#X>#Drk+Ot1y*b%CYT+myp38;}y*%PUPV!r~4NF2C~ph(k{Iwmz- z@8w2C5@Z9QjcVPNaHv8Lz(!k5BvJ|H-StR&=IIOH-|G0R9C`4PpzxwA02iBS^u>S3 zi&7Oz^aJzNT9Uf?>Ehrxx<@1}>q_)d4aNt1*rNl>P(7{AelX)?3Kl)~6H=?ZIlmt^ zFc0p6V+&|}$ML`NH=f4RFC5C>*3NS z6Em!625n7uPN%Yti)j*Qm%><~6tY@s$&smD3zbC7j${qU;Lct8x(c$A`i z_IH5}rTwEBZ1IGCkc9N$Pt7I@T`q^Ty_zf@%WhQr+*j6itFBA*I8bt4K3Ejr8tE{ew=ijsPDFfuZCN1uzn@upZ11zuSrtm%Y(K>v}{`-8k4T@5PxrxTEhJ^)2tps zqa3d#8Np1N$<~8Y*=k7Rjyqv9>R>Q z_!s;j_1P!aktxpP%rk8Bxkwz*c4RmlIR+vY@Hs6N!2ZU=6jF?kNz)4Eb|6ePaOe=g zw~~S{0A-%gN#hopmLmJ#j$Wh~{d{Z7h*ErkBReoGU4)Nvc3CI$pxCZfP&Fvkw@1X` z$4qUFRfY|@C8<#{Bt3v~e))%MdZSv^WBPT{FTuGjwMLOpf8xhxr?BXxMR|acE)`bK zOvU__`$vLWHmk_J=Zy&(5|^Js750&)vELrpKaIJyW0{Tt5cPBSsQ zxm^cAMPuAQgboZuCAXpQ5l|?!&`Y4tl)k$sj504gAenqXL7oFqud53inW6%cBc&TUAm5; zackGk1zyhYRe}{EfYH3tMgf~Zb)|m@d?u55iUGfm7kJ86nb8T^{kBUwQAd6_{UOVfqZX6Cd z`Z*inXkjw;wWe(QQDA778T0;lP~&v`^Rb}k2oD`i6UrVybx+2&DaKREtvic`7VckV z@LZ6Opj{njM*E0qTi@nP*s*k-?_#S4Hn*`u27&BNsaKn=W0}0Egi7TH<{dBRE?kN7#k7a z5!$bQ%Y)r@jbhb`6)@N%f(fT#kvg139*;O}Sfwb+#;~{cn@y|TAWf!^_XU)Z2cffN zd-al>6=#$A5SdC}V!_EQfS2`AM$T(niMF#MhbFr2;xM@qoj5Te)UGe&#THD?ZX$RM$vD{oE&L!dp`C} zFDXJv-LoPlo%PlS3?Qo`fbB5Re#N4yP~|S*KlZstCRSdToJ5N`gS+$eJFVINM4<7? z*NOy}?)zVw`SmTOIiGi=AnLpH2N%UPuyxucm0>SC<$fbxa{ESx zp>~@G767*d0|wJ%ibS(`T`>id`8_fg`|h~kY05XmrN%3>_qVkyZbKe zK_cJp{OJgf_U`OU{ief|Ln;!N)$W~eem6K~J)RVQ5x=XDgut8u6k9PUUyQ(;M3!0U13(KV@ z`&zDD3&Yy>K+d4W#7Mt9XE58tod6bW_iH{$CpC?Mb*KN|S#5J`C{Z~wQc|+Qv;j?L zPA6JB!^u0A8Jp;R?~!@W&(CX6)z z7D$1q?K)s{z^q(PuTW<_C7!Z{)nGGSJoFO-U&@%bs>=gYGN1byuqPO5Z)_Q9JO=F4-|(*aaehT52nKESH$~QJRp@Fgc3G_C zk3>=str!WhFvTicj%5J$9A09vIsi8!pDuC}2Uk2_6FZ&~-@;=3lxhi4P9>gN(Fl(v zSk!3Tjr6w39!$IBxfVcqtV-jhZ3Fwm`(ia_?k43!5{#y^^9k)fvF7s&DFL11_uN1d z#?vYG$?x*M%kE3p;QJ>9qfyfXH$H6-T9KGQXc6t%bU3?qn`13srS=O$vm4b3*JozC zk2=m&ERZL9jBg#fUto40KVODTMr2V!s?ks9H;~I$8gXJ`sx&K=Vx})MuWNXeK7Fbx z^B1DAzwOBJMg-1_M4xM*1zX<4*TRBOn;)qrD~wdtFUlw z+OyN@1KN*sNSX4;pc65@TN{6a9Z~#EeP$)kX-sOtmYbmNNL3mS$``%5BRWFKjKkiH zusz?QGq%zS3!7!nbi4SI5-E92)2q8u7gikx2FwW&?+y%2)$;?b9)0mzc$qc*dQ;}% zPgxvuQr6m4h0`wEy#O^yw#ylran8th7m8KclCTT2S;i{#6{8~tFW)+F)P!J&b4or# zJ75F4KD+CaZ4yFQ%vWoOl`TMw^GgM~C{27A=HqUc6Yt@%GAld=HAj5T;2=7T`O#rL z#K5mq?yChz4czPlrC6*m?1WXeFUzBbx!>$f#i@jw3wJhMrkXx&>rgmzJE)RnD+F>o z?E3My?`3+}H|5RWwnG;=??AoQ82q$P-16<{Y2YEAx6QtT;O3pK7OfCG_9IZ*PoPW)S&!d>c0WHU0i-etaBoPj z{!HXbL`?f z9d5^E?Os-77jpo?Oz4)$k39(0v|R^)OQu+Q$FPFb9cfCpP>`t3RrXS$*9{PMYzm+x z9OC39DvdRZJ;#6s-=CtOdd!}&PdSHmSrpmGdlvH(_Rgm1?Ag}zNm3Hmr8)lsoT5oF z>!?gc%%#P4U=X??0s}N=?nz|2MD(RO!p(5xmLfsU1UCOF`l|IXVjEk5W#d$Vw}#Z1 zPv(^nwHSIfafp-;{V<#HI!7+*GZI$Gc>f{IWNVECV-_DqRHF0@D-Hp<@@fR9`w9z; zU50nT#5KaM-&}KxznP-3?~`OXTyRl=TywCf9jQRaohb?HO_$Z7(*ln`R~n?p?Y;cR zns&Scxs3-X)PH4nf@2Nu0JEwIMft>`u$F;^MTOWN)rVePr*vPxPQxhdvZ%x=lCJ+0 z73%nj<;}?+&4id?tq?kW&PBk%RLKkFw203wbx0_m%xPg%ycK01B1=!f;(?_hat&l1 z)@}Cg;V!ae(w}G`HoLP+LT1~q*ty?M|Ab@=s$(1xly!J zCu}-2@XK&qr)Z{<&6Cv-Ks@f&HzqeIH-B#4cSDVbd8=)M43b=_$z>s_kNO}!`9^26 zRi#w>y~lF1!BfnMlNtXOH(P#$z3uSGXC;_%Gno5d+GHV>1z+pSjICRc|9xW-c}v8T zr1GHMnZ=6+-)6F#il}z5FUi1kVl?=28~8$tRuoSuGi>ee#>8&%4a^YBGY^3ntA zjS+#TYi{5Z4Fv)rQa*y*Bc&>IffTfpr_pl%qooE`AW@FS6fav{KLz~P13FT>A6g?~ z79cikt-ho0|z<7F>&d<^xRSbaU9)PMkNiW&PYP-0G{`Dn3(}s zYC12L;hQd0Tah;dB{lomwZhPQ~V4%g>&wU z%Qd3en`zXY{CP%ihx>x|Xr^80HK)k2H$0%D=^dz#=)HLwQ8b9>&ImeQK$e-mUX=_~ zy-a!iqeqczuRRLr&o(N)InGNxxa0|+T?V}f0aCTT*lgdT zz!7xpX@Ew>V4C@he9!C5=}=g?oX{9=M;~d#A3S*DH|F`p={frCD)MMcp!8?by~u`V zExW(mYiyW1DHkAlZVQMMl`yGlH>bbAuCIDLDEW;xQ)y&`T!|&`m;kO>Hnm0=^ug&2@t+?YEkeFf}*ONmoy{!mAPAla}f=fbGc29oWVa zC@FpHq8?j;gT2;nFLW*{9gjN-c9=2jRH;V(9apWVsG!t8G|yL9;;kI@t6gne@Vd`T zcyQ~qwLWw0_89Mz$%TIMO27uBf-c9|Z8A=TEE3H@cW}_jjm>ej=5=6fE{nlT9khAR z+rYOych2v=Eui8a*+&@XS0Mi7oEtEyVgdy7{^Q>C>&{$rU1S}WCpcxBrmj3Nqpqwg zk6)iizZ8wkZO%4`CU@`b5noTb{bmn~-Q2+=?Rz_SHM)Q{9u?&do@cpe+N^ld_(z+7 z_-X!q7ZzD=_uDzU4L=44!NcaZF<0>WVSk^1cFBshgVq8fJAjUd&=5Rm`pa;9%%3DO;=g6f=zKyPxW% z@1=IqhrZkcAkr*Ar`FOtBIDIxv2GdMgFLSM3>| zwqy8OP9>=W9YVC&?BCEx?yXppqq4Hdz5KiXnV%XBxt101kEI*+FI}C^ktX>|1a$R95OvAuk1_e$`yc`xX?- zbWgu;hthM7KfI4;M#l_*g`@`uV)r9R;#%sP&?tL|Zk%IE?A){XvZO;Bj zZNNfQj$!GA1^8b`d3$df8Ufmp9n^A$Ms$BT;+3}w8LyHC{~y}kGOCKd0oz3yL~;X4 zr*wBWNT+m%h;(;@bazXO(%s!D-L>hKZg>a(@jP#w^L{zU#Rr!QnLT?CGrzd*>xMLj zpn&~=E!c01JO)jcdRgGNQ4fXUkzDm?QCR-5`AsK0RC{zNCPE!D;djty3M63pcm9E0;AaNe{?DhgPTcu6}9 z-hO+@c2HscNmRt=43HQ3iY@h!t-PV=>EhyD4=m^XwY3EW{nhS68A~02bpsHSuOi$( zq0$E%?6~htf!dpJc+|QtKTj(Am$lMgVE^BE|A&9`3IAV4GxYw%7l7lTb;g{t@bKUQ z#|FMc|B1=u_pj|7gxv&?krlqrRIPl;9Eed}{hI*+z1wX>JiC@~aX~OP`Qi6hG6dt_ zlmUszdzj*O2!K-;{1YGP5e}=T`RfQ`u(;J%34ab|`fs@Q|Cckf?d(uQw%qnUAMA%v z9f5W#gRE2@-dKKZGn_uF!`3d+u5=4tdm)`sJ+bbpP|lFwmavkIMJ8 zx%?(z(8`R(MhTlIx9?JvJ`}2b`WmYC=Xja_bv)HENOnt@9NNC=K=jgP!I<#H`AQgd zE{s1vHl6U_P)^TlEDL#G7R%p;p=wx5a(1ImK#+r;aUL|tu;vkpF}88Z;h{yjBlzzX z;1ZLOdOoz8KDx7TycO6*S~Iau$!(Ah8y{-u=hg(YhadF-rjfw$je>0|728%mca*ib z4gvknycAQ+YGxJ|`$tc%FuhDqRaMoetH3_=(Fm|mszp7TUxb$6=mf>x;YKoDn78)19yF`UhOghxgc z1>z;xHCTMtN%uxab#$g8{~d*Z-AYN(up84a8Cu9RfA=Q~k<`KKuO&uBjmK?=gi$yw z?jcN4dN4~mc6)Uk)qz4Po~ztM^Q%y8hZmo&wY7Dw!JKfLLD#n+Vn5q#!>v9&rS3Bq zG9mxhNc>Vkjme;`q!BG=@ByJ$0uvXI6nV2X0ZvOz-A8;1zP!&zhOGG~*9B}g{rR}P z1^(Z153^xd`by0j<)2|zJiHmHKT-x=uecB>Dw5k)tX;8NtwdH}n7+*(kQc3TKRL8+ zU6m#(s~t(NgMLN+pg3NAGV`Q1U~7w=QhPiXar-(kZS9dLR#{1*Q3dovwKQ0FnTIIH z*o7X=JbnJSfQ|UvKE>sJF4E6qy0td<~G-1??XUfQ07N% zWRwk8ZHxq(q@YJU>Wlpv+*6=iux|rp)m7uQx`Xy3zZ-L6v3i&k<`DLW_R!F9!Q4AX zg;opJcZV7`^pA(&Z2!;POz6O2;)n?JRx5^NkX8vDs2JVexn66dg0=1tWq1T+;qPcUz&mN?x6qT!ZqJ|T;X;&JkKaV6 zoM#G#)uuRWmaJ3MOtpE?&|WlQa+Vx*mc2>9HUO>2CVZ15eG(d zb%de%2Ya-`%;$IYBe7$jIK{b_`1WH2Na%qE0FPz&;wxOEdmM z%*bKiCUDrg$7)6Rt?4=5tsFz5i>5fFPp19pYkd9D%RNk&5}AOTn$2g%dMOlz|DNEa`3VVFk75{t4uY1x zZ*sH>`swB@0=&+72|Go@Cdy_6t0kCf-5Oh1u_hX)Ia zYOC=eCJbO%Gx(8N=g%1Wkl zl#&9hLhFrhv?|7+eRO#0*nrx=aLy`p3i%A-qVJT|UsmRVKX`GyB7!y9-K85L`3j>PkmWMz-$lA7lvd z{v@;}Je5plgLt-RU(>dlxop^zTh|)A+bFIF&^~z{a_r+&?BtsgV4e2i&y3&gPM(mD z&@VLaPnY`E;IbPHhTqyP7jEXQJ>4AL!z1#x6~l$Hn9o@Dn(Cpj+}j9iC$gUt;_K&! z+sJb4VW7ML+4kvlS137q-`mGlQ}hPgNs4QKGR45b31_EE12+^NV>^FIMT61vyvI0P zasLIc+ChR<@%;Ac;H}5g$QS_HLU`OfEuKbetqN|_X}&LtLqj9qaJlP-A}1$Ly9x+(w`_mo`K!)!N850fnpCy*aYg^)cp4#}l$6n9lhYmv z9R3#b3v>TFm|wi)hf?HT z(JVitnt55SqFhBY+aTRTu$Pkd7n4m6qsEhlnrzZy80*6PT&3Sk;OlF+oQ3yOUv3Z>4dr5C<%ascB$#)eugrR07)7E77%BIr~ zO~$l4@}GBmf(eKgosYWC%UYubF$dR&<-VrW-{$iA|rbbKAO#R)FExQ5_qzp#G=;<5eN9%B8d|$#mg~U<*h~(mYfg{ zQ*!ezK9`e{Pi~A(N6S(3_DQKnF4*&nZ`TidObJ+Lbsnb6{EI0C4H;o3LP>C0xG387l;_*2_@-^y& z{74u|JK&Q|SB=1(;mWlch={HkJuln zlbnxu?nfC~AKz(g^L#a%7{QrM8uKe{QS_GS%qHzq#ET~2U}RL#6?^l)jbSM%8Oe=b zaR{I|Fv(9F)pCh_V`GVF(FgtROe*I%^f{ywk##tGWiuIPbEpgB9(6CfsfZluY~^D) zV9Y5@N)~F%d&z#d3`^oZM6%}ZTkhPp7)(Ibj~p4`IfgUmofg<>h@dIrb%)<1i|Q7L zrhJDhG#Dc5hX~Ww=yOFMZ$8m0$bU1qz|P_LR!Jiq$#ly8vpgQ%x{k4 zvOVyb%Rxa2%?L)rKbv`+cz=GYUC z>C=OQ$L%tFK#EVb`|K4n1HNI+7nKgMiMb4;8f26JuR=i*BTCkpUoK1{(rcluP|Wf< zXeVG_Po!-nL#MQG`heXa3SmDPs~^JUEC}b`MXFQ4I(!#j{RsM=u`gohT- zJ_4Dv3PD)k-vP7Qz{ zi&L*)Kk*Urp{3u5Dsf;w0Z0*dbSd^rSXq;`CE; zve6kT*w@{Tr?=7hSYNam2?Tc$1LX5xriMxu1GT0dw#GCH1V%v=;ZzQ(1+}<3 z8;{~6+~b#Z89+j5C8b4AJEK-|(D}&wJGFOZy&^^%U3eeW40G?36wP(^=0^9PtWp;T zAt;KWQtK*^^q8?ypat&A0fabYz-GRJKAhPmx}U*ob25VN&`^jhE+Mi0k}$|fogQbQ zShJXKMym|pJzGY}Eq6UYPV(vD=5!!nYT4e}A&3FtDvd+;5PGtM(qth9Qswa%>(GZF zcIp}$I-l?aKH|NpyE+LHaz^Q&S2c>K!HtOG$M<8xD9jaU!UBIW{MLuVVUjDop(oEr zMEcG3_+NB9p2PBGeGB^An}gXzGY8{}c!&)6b#e8$c$^N6IvRR*#ab=ys0GGE!7T?D z7tPJ|A}%a&A{VOvz~m0e5WJp@P_-x%uuR}|G9MM8Rf`*w16g`9s~muVjMJ{d-3aCG zV8%ddQMy`_FCjDc-{5k9HrwsS2Y@ub%(yh7c%1Cu!B4VFt`{6#7!<^{2DSmUQFTF3w6Kk={ z&D+;Xg91-u!n5hw_&=*7gy9mv_Qdlpsj(`Oujkws-R+EVUjbH)Tfol&3xi?zvyKLb>FKP1`$(qQQ~mzsnUE4+ML9d!+9W z%p1x}<9U!^@2~c0!3iHXTO@PcKCe{!f@oH&oZH~l8+!0~fVptKI@3QaH{Hwz!kah3 zx0NR0YalvD%PV|NTHlOn3jT5EeLf)KIC=?ibBodO$z*0+z*>Iy-!OND`q@|6Ma2Rc zv!W!CB#m0FSF}%q-#&_K@UOLK;c~& z+~vhSA1s?d9iL?P{&=zQt;9Hgug469T=%wSm-_@WSA$$sUD2-*Dj;9q!{Z(N9#cSBne=Nhg zy~32QGB!$5$iz5Rkc94CZU5)FFR5FDDjeeyl_8nx=hPU;EF%GFK<;r?l+a61iPwXB z2~pyZcrNPQDK%1HC*s%Ecia>3!a={XE$y}49Z_~f>((NQ96{CUaGyq-$>%EE+u&6O z2B-jaY;oSRE3A_PT>_2rg5^i94#s8mPdW%8RHN+mmO(`l4iM#Qc6&-_Z@CYhJ(zFc z5Ts1vMI)0xKjqpuJ={np`G^UH>~X^Q7V+g-9uy?=o1y`;RuVt-3>r8i z4`Dxx$qblh^hum^$pYKClU3QPjR4zPx^!U)kO#-a4TN^oaKmd{P~m= zhDI^*>9kNVziRq5trOFN1!y@yfi#(8AREeWu<^fx>Z)ei0=Tv!13Dc2IshkvNiLrdwos0 zvV#_V$M);Z4AJZUBJy3i+N80lEHhbB6~4>emYd5cgaYf;AQ)-Y(DQgmjksP%ljzX) z^t<+2Z=1Hfe*`~D5Be7IR*>OAZE%oXYw*bamWNix*1FJnWbL|5Z!FArhwk+)VLvqCRKkeH*LYJ4!R>>$ za}-+c7b7{jZTOUvAZ|e&7=VNt%S|;BDN2gsQ+AO^!|iFvh_E{jrVX_wGkrKb zFkRaYiEQ7)04pZZc0ZHFLL>hDRbO!VRP9F$(--*o7`6cRANZI$#{$18IXU;OfNKG~ zIfw4#(8t_u06va=ngnzRRCA+UGH|$IgFLgj-~=U3;c)d#lToe6rQ0c^>tLuInImxZ z+Yb9g5=l4l_mx22U>0II1^cYpVa>s9B{0CVC#vfiggwFMeAVZ3aQ~srz;Y)oyUlR# z%&{SH{X)UMnA7eX8)AbaU)F25;?{U?9d&0_pIhW_Mv*YbEtV;UZEbC3d4by`g@k9d z;cR@fP1{H8YOD$4@AqKye>xrA(an|H^1M$wfx9x5=XIDB&}=68&&n=JDsJa+#1o4A zD@7Cib0%RGb+SXA*-o@7s9jf%Lvzzt=Mp$uIkdk=W%ReB%0~!ZO<5q{CwJHsr+vvu zQkdHrfy%Um3)1Li--eo_NHbv{_2pxHTPqm(IO>zLhM4#d())^B>Sq1P-6}@-uv@tq zOzCEiO&_-)^AFS8baj{nA_t(}=XOYp{8w+)i3E5_vd$WP4U;SmlPo1HO8r6KITWzuU<=aBIkink9 zaJ1YMk`HQVVw#bxF56~X9{zVZHiyNWRj**2S2^iJbAzA+8BNN+ zOo!Irer+X@(->vCVt_-&i#1=t7a#>yWG+`G9Ei1lvTSQkbrestzk}QPoKyb|+MY2K zo!q8Bc>Rz^Z0+kgOSbvp4d73gM-~JI`ISTPaBBKDs&g#J@W~on-Jf^mqNx z!Fp7l?xy~%@-&ax`e%DZ5!8d*{Q9b{tJMze(5NqYmU7Pf0r3hG(DFf(-tU}rwR*uE zYB}K&n{$=-@~*E`f?AERJD-+!%I`VXsy?wDhHZBcSk6>Av3clvqF;@-brKk>wz+W~ zzTQs$K13nW&K>nW^+TBNQJsjml+8kkvX;Z=MPWp~Dp*YOyx(Gl82F>EM^P5qGICd6>kbQ{xGuJv;d|32a+Yf%s1dO9bdQL7=x2oH!2?i?=2BXaPZn) zI8pC++h%{BZG0ysCZ@lUm@!v#4*?7>D03;eQD#!ktJr1wkm+LL z;{(ug-|H{wu^9KenaciD2j&zuv|2R;Y(GAB;B)B34Jr1-(+w#WP+~(x+No7&vpSt{ z`Rr9;xnxibz!%}=|Ket$c_)-mR2-IAGFX(d1(M=)+s(x4s=IHnvt5oEkq%@4x&QW~#e)(26BO*b z2=`4=KaP?GBxcagWu8FcRt@3YFz?&Ry&giq3bzTcId5_wF_^8k0DXN|g`LvwfJv=N zNs*h#cR$heK~mZt18`k?Y}V&tPS{T8Xh1yxEAwBU9^e4w1GVq|gV>N5Qtt2g%uM(1 zjx$xEp-6qRt5MP5c91h*g{E@ME!|the1psRx^vPu;R|#|_0*&pGl%x%>z}h$ovDs# zr=34fa?Nt@(0A!LgcgZOA!|7dwG^UR;75l0L4O0MNH)q!%F*x5xJDuB+-ju0e{4Z~ ziQd1$56Z7wd%;bIpSI*S-Z{)dP-PAq{eFyU8=&;-@bwjM4~=6_g#XQPzT~hl0MpO? zmiCjIWQj`rYu_#E=F>)?Eo_-VR6k#R8qnWRfn}X1W-OadhfXkT9@+TDpBHp1ml7a|aP5G?2Y|+@42RzFMiaTxhku zL+gq69e&-4LjwHCkXmVvv%n*)9-*G`Z6?)6K&vD4<)MW6anwD_!#~Y>*EC>1RJ?tt z`^R(|qtyg!^Y1BtcqWo8o}QA^S1m`^g=CT0yYKql68sR~Rcp`z8A-sYI1!=Y)mG-BwJ6)9+Bh1U$CS&v zVeo?2@)&0=j7hO;dAUW8=8}U5;ru5_QdI&D;Rir#Z1d1f#cHMA2cHb{lgWkaKU=Vy zfxwQp>etOIOu1-k%K7)|o0E?pNST=&gBfrS32N*t=y%$e-@D{!gj$6cvFr@EbLcK= z?Q^_g%2BoXZDxw4*3rK6UsUDf^wd98rK?7AMn@_OU(Y+@$$`^5Y5BE6G^6stV08yw zCd^<`lV#TfMu53k(OA4 zY~8x~GC=ISL)F-JH}gDPfh0Pdo$GSpySVwv=S8t|x;yzCgAzgtZI|YdO~?4SSll4J zS+zm=ok8F^*qn9Z9(Khz;7nP)-k3a-!;t^rM3KmpVu;(V?&2irCmpC?qbpQSfoAy^ z$v7|XT+1~C7;J~_X}UTHvPHZBJniU7bQxum+Z_;-2pZDGocb*CZ|VJg*?~=ELBY(`-F3xQ>Rfq04CZ}XM{(H0X+evYnxN8%vo<<*V=arVaa{cAJ897-CoeONJr~3 zB>zT-`Ei0Ry_pRqL(iik5*w{^;hgInvCUT4awy;;TdVt8vE#9~mqHS%DER9{XUlM{7zp;%)gI0H zo6yXxeM3`GpGzMlj6`1eZlE*uU30(@S?k)j*o?zyR2YxjL0f6R(b+6$U%WYm&ANg! zj51tfhi=ltbXb$9XVug55L}<54!x(BPA`HeCW|vI;qO*xKHsovZ?#-2y`-5gFsYq} z_bz7f;*w85AiU<-60qI@tT~v(gxd(%WU`4sOSmTF44*$w`exLm%8c5xXOx%`r+MU% zIDyua)Mh4;A8H~NU%E;DaZEE31>z-E0K}X>blm5~gV4NjX3CCIlP-JeEjvx-;C|tM z@|5R_QbPphh;IQ0vh8$>VOz(R(@8{!V%5{xcs_YVLmb+21HpA`FjdO=h?l)vnyRdQ zO#;jUHmz<5fH}QgZz(-^4Cp4aZ8~0dZpSObh~*!h5)BR4iP-IHoqbHdEBtdu>hT+r zZMS=l-8gwrLh46&o6fNid0uM?FeD&ymG;3jKUh+^ih)Fijvn;#_ws4}g<193EltiB z+$gh-ttj%eroKC8JDPgBljm*%_mGkByIemsY~kS(q=QT!3!9HmmrhS5AsUrU24zF6D+>l`Zik2F!K## zMcj!GS9P(uhN`TM53atSZNx~A&UV<|b7eFRd={7Dc^l-`X=EjRn!b>1kg;1Ej)9)$a^x)ZD#koE{z_ z;->j^*0orF*TbJAt_>}7bknn} zInuVYdtS76n9-i(y-ENl)Lpvr$cKh0tGuiM#FId0WhyjtyPlCXI_&v59?WEG)_egX z-LxxiRn-KC@uU#fEf zc{mEN-U+i6n}ByCJtf6b9CzTQUMM!a08>T-vE(GWEkKE=%WihiCLioOOd8ehR5tTy zU?e+{FTJDLE5*~S@ay&xuzqBm^4E1(x^`BJzraxq7N1uzzgyoGze)4034U z>UoX-61EVBw$&K%$ilztZh;pLw;~Z1OjtGQkBFH!98j9sOjQWD5*TC8= z8Kf#ulWDt^pq5#YPX4Xp4FUqfS!P0IO2&p2C!cl$>1;5KLju!BCQi<|kKa~s^1rH| zpYx558MQmQM?Q2Kbz(;a-n;RJttdN4%gJG_mx+d;m$Gj=UjRO4dLvrfA~U$Pu!e26 zeg_*ZUyz)GcJhLHWcp?K=12cVaY`2O#|>mtWZS<$z1F|2j|L4X{D9}hVSh?eG|BV+ zDunc>ObT-{izzY=+ZVF)Uzj~O8qUj(*nlR?S*Say8L>i{l_ZhV!~j7AROa>k=!}HV z4LX=DOS;yq{_HD3=m}bGa&{h(=8S*@F6w)|V(kWW9;ZW8p&uea$S!;NNeWe;WM|zd zWRi)*4ydLS)thd&(`+~U(Nsz_F{60Tf)9BXugbh1U$cA~Ml}efim+~bWCwH|GzaTL zQrCyWf6diSt&yy8x$IeR<2x#KnM=#yA!vP8en_r?nKtc8F zmW^QqLxDi$5#T@luGhv7)VR_G0)#<&uvi#YNU&I~^(uWCU>*_qVsGm@1EB>J1qbUn zSSxbomd}QwI zbLDm+X-H{NgZ&Ozsl8Svl~uBvhC2_)BasL0Y7B+RQ53*T1lHJYh$qd7*+zmfb(9^1 zD~{4QtP{tPZUM_&+li(?QXyq;qh7k#?=>MV0?_RZ|7?rpB73n;lWOSFk{}YIo}U0!Jn%p&5!t;z{j(WxA0G=4xIL6|uSOhi11e3yUO4O) z$-`ZbFJdCmO)%>*;m(^kZ!p)XiHHQD-yg@pT-RyUS&?We7H>SfR0eaFQtm1S7;2J7 zy}{R!;o9Vth$3*r=O~~>=U(EBg4xljwRrcZ1ayS%A8<7kD^>VAaCe6;-{)hBI0A5V zj6%!|8Pz+jHm}D#IgH6=AUT|^om&Ug17n|YD1+%?@FJp*DQ4mfzYr+DO6iqg3TA6k zSI9UrYRvo?_QPZ-vC=|QGj*yeXDIxH;(j6-x}M4BzUldMjvHScl+SzWkZjum`p%1Q*4?7s-1AXQClJW( zsA=nEd7OphVJkTtOyq}6?YuZ8hEMo4v;W$f1JvnIf8FijXZl@ejpa^5g%QPYLa9L3 zyTG!WdokN3FrsJ@O^uF5ElLqeg+G0EZO)!$c_hsQP<(UJrp+$ zeh|HGtZKzD7pw5$))AFqI2_LnqBTWe_l9!_uUM8DGUB6p@?xj#1fmLh%u4I=RLTlJ zRH$ZI5!J0*E)&%%elXS(z7GBLa;IxfZv4>k8z?+YP(;>1&AQM1_t!x9uVD!UzDNR% z8WsTgQyYfRgB*4nb8N&a0_EPnM*IlaV-i4|a)pdjb=IqGHE&(j)ssP1&p@&GZy?_* z&E(*WMXOHaSg76?cC;DnZ9yo;>3kY$qen%pv+H*u!)DyL1*BW@65oo^cB+UqmL`w( zJ1Gh(1E~ReK2?+g$Gtxn)L*)={9s1oe$?4r&j117_k+&Nm3CXM4qc4S*%{LtNDJTs z&uwAAif04dK=pwKH3{`&#;*=m-~p{BJWtIV%0v#Y# zM+6>qbY88k!SWx3?Y@A`$p2jW@rF@<{QF`Mi0rgh#|UC2Yis)NGJYvHSAG-m%{^5N z&bARH-UDu1eSzCIryC7kjn-`jU0-jG>Q-%lp~HIB7(YfG#(_>nB4$`6P_aA1o^B1UpDC z;c(fJ!M?#E>kA^m$xzi!ylhm5$eN^bc+A*K3e8DJxQ{2AB>WnbB&b?I{Sq|@&Efp} zS&WW>;s0_!9;7J~1%`#^Al2gQU1P(TGXcOWm;a@;)foPG)-Jp~ieR@iuu-BIfg2yDy1bMKbrYyrQBab0^7loFBFXT(!13@IiF~ zO}Tl$n|RVk9Rx#lfc*IYTZFg0v3O%~aSRMqv|?W4MkG(U@c=qk>yj$);pUa|=ooNA zX6Ih zPSVenJsN-kV@zr)by1Lmpbq7*vlx9)^3Lka)j7KNl7z(O321qA%MR?}Aki?}Ratvr zSuKX;RVU8nu#YHFa7jvv3}+k1haig>8>9ZYP!<0;h4a;I z8W7ZHKKLc-#}Re}{aWu%s&fzpM)*0wf7df?!pRb=0Jy)Ox*w+UQt*~0H@3{!#y_`D z&{kFeygIFuIcEEovOA4_2a%)l|@hk&O83RBZ~;{0zaRgr=iM%N}OKpFoX5zRh&>B4wYR0mJzX806a ze2c$bq}cHcf(i^0PS8M!(8aAwJ#v7B9kUHp2X`tF|2aLxP&z)oM_~`?muamTcw?g{ z0}~4i7I;YI@3zCC&Ck#G4-Se-N#PhGljq7fyd@#^w;9)O>6XCd>B(p2=gOHDYb4*M ztPHC1xpVX_n@#EK+Xt!sIc1@^-@ro5%U{7g`x@=h7C7*8B8YDAWVBcRxkVdZv>CY*qSP0uiXY};mIY`J=qe+Je20sRm%L3s zZJBc&Tibo1_PV)6Ilsdnpeiai=j$9o{_~Lea}I&a6cSB$Xj;pMr)|1w2Cfi*6n%E3Uljh z<5!FkAc191EYnUpIj~>uwS0+{9nNF>55edW5?U-&;tL=VM$j!fcg%7;KYo$2qkdw9 zTqWEnAm-wN8QcnD0AA3aFDkpaBX(N#W_0DkF$rU1mr^s($3uQ6TiNbj^0icco$+d0 zK5ZmQbvpHGNb6RCx4?FMSCchwl+ypi^S7_AqI3xP)?bf4!NXJfHUm8d$LP=iiQN;r zWn=#QbF*H-xKf*3%lWYL0fwgB`B{jpFz=f~5v59rHd;%gv!LjJ4OeC@9tUh)li83Q zGz<(84~=xeZHiMs;MZ?n890EcBaUUJj4~+|Xk?<);YK%D&%PztWhG;MJDx91%-Bwt zgaJt1dvQ;^d|wKj9L+zDUE6`0fFe-Zw)@*6kR4Y}R_fma6A(x|I5A8jrzgxvGG~q1 zahq3#Qt!&4H@EAZhvM8o|8OU;gb9=-tczik(;l)n%~#LYZ-ASkBJubP0*baMHJD$l zb^wygS8YD|5Pl2H9+{F{HKcJP(Z@_&*`arE>;GuMq{{ z5P#X&&gdW13^!xh*%$~q5svG<)HztHbfmzd)9{UsCd$hq8{JriYn)~_;a4ggb;1o(BxLjpW5 zC&;}$GdoYUpV|nQ`!mtzQ;&D$UZ(ip9S`cjg2M(Wh30*=Bv*rCJ2P(@^v($7Cc}me$yiBF{2CepHupmkVQ7 z{X+jNCo7#~WYe!b1elJ4L*XTIzdGh+kuL23Ak8~8Zq=plLCAy{JDbE+Ex^B|nX?K_ z_>1`YD;6m=Xm_GS%|Af}88=3ul{`Fn3_B3U(g)I(E zdhRj$Jze4751)tAPL=3DRfyZ@8Qz7eR1uEn14G84-m{eLC%lYD27$ge+bkhW_i<+3 zH$Ls?V7n5HmQe2QGr_38pKD6I71}DS#Om*2eluhhQIkjp@KlkG=kh-V9*QoKX>Co>UEn76w`etgN&f-8`1B2 z0qd>xkQ$w=j3BGg#nUxH;tJ)mRa8KlY}OW{WNqn!LspOzWC)Cas7P_OIINV6=9jdT zT*nVKTv>fQn$r9`NAnzy`Oov-=C?<{!f@9%KCr=1Q*6{*9QgaRCRZW}86D4!bO_HB zQ3F)LZaGgZWDXP?+K*JZ%;VUKE3OUo{~pLE&SX7$$6}BQ9mUt^)b4-10!~$JP;zCp zRKd1xzJ~cUrsW5au-*30*FNTMKhU~>%=5o!J*n9XT5pK)?dte1v>vJ~1Mv2491ypK z4l(lcD>kC&s>vLfFaJOw@cu2=P)k=4G^->mjQ+&oLaAE3;Pc|#q^HG=D;Q=HR^$r^ z3UaM?N}t_5OE@?^LLsd%T@?cOd{RatRDg{8nzbk3=jX4WPATa5d(TXcUN;q3hlzdu z5X>c>9ZT@AI-41&d|}U{$>q5Kl-2w`0;=;~N{+pNKft9Wx~M993NL^`8mx6*P{4h? z{ONA$r@M0ke-PC#{sL=Cd`yG8iLWkK1NZj%f>qBZgpYek0zl#Li1>vRBREwSaRLqo z1~Fn(=#&ya!I;oXKE69c&Jx{^}n)t4ytcO16f>)IU+{vx)?r3VQR!QlD zA;WlM=c}Z^^o&PNXqS&VT(0e6qgERGUS*@xXUE2 z)cAFm(w&#=GB~>@CMFn1f_EH(T>UM%JvITn`id?bd6ZEc9^nSX@xFXIQ0z}q&mzgYN&H^KAUAm5o` z(JMd2_SbryE9Fp?g-9^s-VjR{WO#tmcV)wiCz1cEBb;JXm??bZW8O88`Tl zF3bl7wsYmtiqtKSh=&vXiqBmC*%AvejQsPP+db#Ql_o;9wQ>LfO zg`n^1t+X%wEmM&Ttwsz5qp1v#=ozmagi#9?nbz>GXOV14nlBe7KS;~>kzssin%*_S zaA>_8R7O`|rVJ0IrP^~=b@2dt*I!CjgTulkvIy|UD?FtSSDaCedgc3)RwD$}Ka@{} zks!sIPk*P5ENb^bcX?R%WzGmJJokI0Jy4DcSh8LLm`Nuh#^L3`^8A*YW{VZZ)mx!( zGvAl(_>jR|(~JHbdI zGWfD+ZtWckl=-kwYew9g7jUMzrI}ys^&Q7tYqDD#cmEW8>1RfBLX=j}M`%9l%>Axb zm)KAs|9V138Hqh-DT-9w==dSO0h*RO*Xtb{^4>O1%l40Bm)%w^d#>u#x3!)PDchla(`48W#8w-vTbe)2)$EVMiTPh@2OG#Ln+hEU(s?#TEGmde3d_9?;n*Bak||v=QxQR zZXh*i+i>J$X3;;e!>GJN7QQNg`E8~TSr8-uB}^hZJm9rSHwV`oFdSpue!V~ub2;&W z)59ZXxsn0THYnzCkFUHiS3&*Ne6g!Eqx%zjQOQ|*3rAQqIFtJ=K7JseNq+@s(j{Ir z>3pysHN+imN1>H~&LJBFb=twqPkwz0q+~4*OQc{cqJ+VOo*-bPwoS*AzpdPNRYLY* z;p7At@T$J(E}THDU><7=Fo(pYNP0C$dV=b zYiC2>5gKZR;NF+LLnH&02T$u=>^tRiGCCh|`E<5lj*rZ-ni4@5JSn-@HR4HsxMMa~ z#^=aE?O~Qi)vPmk4K$I2?N3vOi43-|6W#~7aT#ICYrzEP~3Y0pZ&t!-%p@_HXX5^PF3DlN|Uk-j=t%dPo%UK1KqH?d*VT0y( zii>5mr++G!;%ETH@q(hF$$576?FiECu}_gyGoKoOcX}JW{HFX{vBZyRyML=IuCbA%Bo?qPnI ztw4u=Q{P&LU_-NwlM&Xz(1S0%WZ_#99>wASu8Pl0_K4%o&TCLawCO^-+bjlSK^V{2I%AHoveiS4BmV z)J1H_y>MB`OKI41sCJnWuQpv%!3?l=mclE8YUd@&y$xHwGmEP`yM)`@E@>-Eiu?Rf zu1ZB4_qSWvUc$cS?@(^02JL)T%Q9S$!Tti^ASyo;_zPtVd7^(LQ1`Fe7PQ%8udr0H zpkZO{qYsssKuX0&V~f=v9!`2PDC7Tp=W93r+P{DV_l2{}%#TqFwTdOV8 zZxP^s7RrF-5B~~L3)sp=HowOfnD$oL_KioMZ$vjsx3GBbGo&=P$*wq7eucv-ME}Lb z!%d9DdBBIP;jsCWG05Rt$U+Krwv zX}H=a7!7HFy`;H$;6vjU)WP4~xKA+1-XPXykr%YJ=97Om^6Cd}P zg?N0=#zsD=Ds3&5^ogk)nCC)@0Nh%Z$Eklkrtx1~yb+_^Y#_P5qC!qpHj(jm9tRVq z*IR+Id+}$mh<3Jn@Jh@XUU2Wuyh| zN;fu}y9A)`RNp3CH;;-h8P+K-Gx4mb$0cW+rDY{-jMv`nHG(9B!`EnY)QH#I)S(Va zuHmF*3zZbnzz`*hbTa#k8KDs@hSvnPR1GvAifP1R3XDZLLlG&4PxDc+32;dICNQ{l zT_3pgVeVPf7Hn(nQ%(5Jp;dlluEgeOf3_TK~Y-p<&E62e}Jj zmwrCrt*ibDDdWw5*}A#**i8``H{Or7ubq4y`fNOs#ws|1^D*yu_)d0_@i2+WiX;l7 z0J@TX_+3y?jp;DE$6=vZ_GE-zV|nsZ>?i2*o=$%t|B{cgRnpnAHB6ISXGFcu^hx+KwP9gHuPzO@rtu2JSf8d<D<-ogvIDhhN_75KNx%KsHob2eOF349ZDLc zOS(HHq*S^~>0#)WW@zbF(|hk*>4#eZTj2)_2ZX>#W6pj5>SVv!A-|>vl1x zd3zx4 zY(iz)8N;63VWNuPZ8e*#E0>v9BNXVi03#BV0K{XEAmgz5vht2 zPz(9aRem(V(%SbJ4n-R6YH>Yz8g6-}07~d$@t1Rm(KfyPyxV7vn@r$)PjnZ=eul@_ z{y6~vexPLkf602Y(pyh@k3JUv$EH!kYjC^yu*Dqyu%FOEvH7vZP4nHGqipX0q5fj8 zOZz$eA(#h`kwuzz@}RHF|4{YA@Vq1owOF&S4hP@l^XohCv-NLGdPg+&{X^9Y04egE zFBe$2gy3H8M6bHf^RI{A!bT$K>ZH^i$0oD<*=V?~Toxq5kg3fXCcVbold*=cU{FqB(_cZqLENVkGy(_vU-JwYbxtfodcD)8zk*xu?AkQoRnOfMR(Web8>PULYOgtHWr-;l>K^(@nRS+G9f|W3bJ<&15#PvtT0u+9af z! zNLFP;`g`)n+Y@7_euJs%A#FU2dG!=RZ0!9Uc6c>I`!^2 zmUb~!I-6uQE;pX_q7OW=!Ps&f^)fvFH4W4b3j=MXgZ&vm8ztxLLs=FyYr?VZ+R@e{M*iI4)n|U^K zw~_t|ZRkxI6n5);dMn6;Ca+!`?I6W3nk4))_Fcr>XXMWhuH-v!aRB$l`f2yM8XwgQ z{_-zC>jAeBwv2^Ig$G{8?)kxFRSY%~u=8Ub5{5CCZ7D#dm%C;@DV=v+G1tiT?XaPL zFj7k9$e?QV`$C;G5Q|gkJ%U??6o=H#{I-2)Qt{(g9{EpvCiN5b5ZMdK-d-DpRu&gD zlLQ48|Cg#al4$@2-*3%S-XLAycz;EdoAGv;K+Sw4=GvyYwvLxPkU)izm-p@LU1U0M zz_YtaMtwLz26khzo70y_KwhxIMTS~6S<(Y~Gk79d*Llb)^fg^Ntb>8dXuP8RPJKL= z3cOn~`wpExl>C*TeRn4g%q_${_KcO{A_yL#!HN)#MZl>Z){@6Y%9yGcSjH@Ki7bN@ zu8~ntoN6Nbx<}!=ScNaJUh@?D1%#>t%QLkNl+AtvwD_`?A1TP>hl3BF?o>a^mZN#v zPIsxshKf~V+uiA};;*9J;)*;7z6J_!IkLXI#2^%FRKLdH{USNzikd({-NP3J`(5Je zSD5F=bxetZk7A*1-9c*v|Jl zyM|!ifc3i~cY~G&7WL8Uu)WG#&t?tW zm|dln(^>~Ct<;K@`l~j2b+cKT62Fh9k=4wHx@i5pV|@=p%*ggo9*_Lgb*S(wF>lT$ zPk~)1H%S6EC}Gf%HXU=z-45+H)#2l6i*Y2fmWyvwgqGvY6elf3Z8>;qI`@zV&ReuS z-xA#-eqh^~CE+b=dgy#_W>;UhY`lZ?cQ!mXa}?_D`kFSF9D8n?KEz~R!jjz#!jeEO ze2j_`-?w!kXSBDJx|($6MgaFdQk9bfr~tPm0PY?%q7WLF>2#+7+B^la`ex!kC1g3;lMx&25VKM%;I z*nwyl$tPdZE=%HTcONa3uIsl5%f-*N^tb;TdXJ;nqOzBMS#{9%_C_xIV$deWVCX;K zU1}<;s9S{g@@H}o;IYuq4~z(}nvODB+=G`_RPrlZ?gNPTebK+fdy#Myx8j{khV(L#>_W3w+kTXe!#A$QT^6klj));AC8+yM_R>ltnXM#WPQf+ATgQ{oJ^4y7C@%^vaEK=X(^#aFT{U1%s4_40YOlgPV|sgfo1|$yShTED zXP&;%Kx*431C)ydyoC z9glL?Vno-6@3T%9_pOhxFJ04$fYOx)4VSc0^CmCm^8pB7x?nswy${l|(#;rZ8x#?n zta3tc?;34E${Wi-5)yRUG%Wb$%5l+YB^!=E#q?rfqRDwvg{dMLE`rvLtZBtXQ#emn#Zp{0=+V?=<|w!sY4)3 zM!0sJ;q?ie>ePcy`5wR8%3hiyemQ8{D9ilc({I)0f*CXeW{4SNYqi=TZ4Tha*-48R zh^0bccz#PgAKnEg<`&vRRhu)BE<4pQDY>|~l*|(ZH1kH0&aP!baQNrG{<_ZmFNWRx z2L;#1oGT}!JCrztVZq=w*Bby=d0~W%9OF?W=51^mhkVk)Q&)j<9APrV&qkldW(`Yu z@8tYTz)8Kt^Zs>#NO~KOVB*uI=~KixjbKmS@Z&kzn&hWZE9u#sv*<{_hZJ@RK&=D2 z?~YSn|K;~UWOP%?-4+WlH%6td=zr{ys=VW1Lrx}{5jTxrKg4v`z*Kr%CmF_Bf1I6i z6o2b5|E`41wm=_kjU;nB4t+jWeHCK-di0_F#813y{G}#cT1{=TvoSa+m2Kfx^^aIr zO66Z@{m-LtBj6tE6gcOb9K3BTSOmPm8-E?3sjOQ6ahbr&fW`L2e`?Ert`qdH`StH_ z|4!vORpA(!nHBZAS69hkU|{G_{y(^(4>x&vd4PQU|5?QJ$1eHLPXdm+o7)|st(*Jl zsr3JL6#jkDKyEkwzb{wyx7&Ic8W{=v`wNtF(_w;Z5b?(DZjgNV|J}9y=X%>vMfCN% zY2Q%rah+Fx`}@JmRrDp)7!hM20@TA+c$O->=KuN!2UZbu)y48eFfm!b7At)GgII#3 zz((>)O0xoR09|z6-Pac?z{tdemOupBzo}e2Zdjq>PC*CWUINF_D4mw6sVOBjH98_D zb+*U+H#$vKGTFb^36R%+-V5=aOU?8huG#JlJsdJutX^0ZJ%eJ)|FM8v= zS;7oeUCLl8+im@OjsJK?RV58y$gD%frvZ*Q3~1=x1Ta!j;7_vwz++O-BnJlvxT1VX z|KzRdBqSw4?yNz%{1G72+kr@gJ>T+^P4OrM=sukTxI1d=(0@O95cTg74PezBEVe#? zQ>Kyg1Qe}x>pCw70C2V8;Y`Wrg=+dgfM6L=qXxhfdZ5{;n)fK5mqvEey(57B2@)Xk z5r@Bc35f9rI0w<4_)s(jN7^2*h}^D@LP&+&$Z2SHvhvK_1m2(6#3+e8g8p!UZr3N_ zTU#do{@+9YCXM8xJ8?>HvWN<7mKzC#J$Usz&ZUM@`Cb9=GAXn zpZRK792U@L1uSQnIKNE518_9F*X=gG)j~Bg5syu*lhEB>_6Cx6y=>DgKJ&APW0;feM&s!|}lCAb`%}1A*(h z8Giwt;_CqSDpjbOdp4`@y$)q8jk-?Klg0-M2VktuSf#>`^o?LOf~*|dF3iH=ukCIve8HdoD@)Ml#)4-fgo!)K%)2H z#^QtGNj=WQ(1^IvzVkbFrh7ky06N4nDttUpqd&jHo)6H|z8NQKDuJAj z@zGqx=h;%g2bM(DH;-dhrnZesOw82hr(C?j&xd(k zr;;sguR!-nrC_U8Lw{&>Rb=+u(YaRhzH_wK!#h+y^n}e)uV@$npee|TZ|7c;YL_nB zf8Cae{SbR0^{*)9mtP(h-~`q&qC-&@OceIy2f%SAgl2kzmR|#mfKtCMSo}|a{Za#D z+xaQy@C%S)_$GYQOJutSK=LtPfKK|JdhjL+vmQ3|GpNjBrZ}Xm^^PGyLxKr>02E0` zpTim&o$7QE6!x_zQnl z-0Y;-n&?@2({(mK{2JU_X%PUz1+<0Xf}aKxSe=hqcUJ*87Rjx6QL17O$SlQOLY{!q z7rlslE|F`))mG2Sk^D3_xvNmvMWEU%SJz4{UD$JLx=5W>$Njjb9>}JQm+6ZDiH4cD zUq6rEC$UTAf%O|&fF#JqA8(faDz%Gd zpCwR5W8ci{4crnYLaZ&nag2x z`&8Cxy%7hVk_}s_ykOW_9Y}b+lg6&ae5EO+@)&y8Z$W-n-biZhu?mTC-%cc{s7lj8 z=vX)9DM*seiVdOUtui^4^AZ?QE}jXfsx}LGZ8d0Jw;XuA;;wXHst<6YGOQ(3uTEU;YO+S_CF<+?0&Nn%iA;~VuG}hBaknq{V zXG@270vxrJU^Z%4s-P_R{KLIpR<&uDGc}13uK*=;pX-xak{(bf<&93arIA3m7 zzQQ5uyk{kK_rd^$(3PL>wmn_4&Ym0aZ(EkN(6r6|_$V()KEsw>E|Y+J7z7qV#3cRG zw9~`a+eSYJe0*|xTR{6RW&b%x&6ljUjB}J$PLTde-wIcldcg)>_%1Iy6Vx(fxEt0F`pVr=fS<($GAx>eapG#;GJk~R$U|p zkGSStX3rtF$bco}07Cf}D43WD=^$IW2<3vz z$^Ea0q*&w7!pMhrhW@@-*8zhNZvZ-1$KFZA_#y@l-oRC3mTs&f9Js0bPTQVYq;8OAR$hQoD13=tSL~y|ouh8{C@u;e&K?U|(HA zcivVE;?d4fZw`|!(wd5Etk?4zV zdPT=dm!ZPB&JJ>3${f1_Mv_d2{Rvg?5SC)(S2V$iouC%rLiK99Jhw*wG=H?JELD_C zf}juu%^a#QddLz@0F_NT>?;H|R1_A_I32v|SoWN>Gq&2r9L9)};ak>vuf|mkM5tpw z9g~Wv$Hg#@)SjVME<#UMp;|ta2%1hMqGS_!A@r-C3V5bkXdZ6!GoMGcT_kGs+_s!| zVTuGx90Ki9@f%N{zBAb*&?hMjo1idEVwP-vjC#yx3_H6hP|bx}Hi4Sxd{vaE?$m+X z@})W1-7r7Iy4jWZG$Oof;BlWfJ~80t1Cg1Py}=mfPZuu}233xIIY<7AzjLPs-p?pN zre=V;SBQBO$6$}H(qJ6|MO2{3vFrN%RvbS*fJ+2Os?nlo+bw!dH9brTRqlXS^Dap( z#4EU=bF-3}N+*@jA;dP5L-< zzf!tu<9%T$)hKcvY-N-P0w>fAVfmS{tXXwI?mX%+S3?dv?%tHd*+xx<;R6)isI8I; zGLH6eqk{APLn~h9wh|>XH7?AV%<&mHd|Wx<`oLHHgj_1-Wz6)Yb+{(s*MD*F&_J)@ zKcaebQS3sXi;L1uIgG;mRV5uA5c#0$x5v@E(6*s)Uhl$LDvlMhOs3w^EM>ip{ z&}bZjBBB6e4a8V@H#LURU{mg8gAl%ogCbd%4VG-LH%)5Nxhjy-pde1 zQjmZJdqHh`Y1w{*3wgjq2#OAx;^RL6x`R7wW$c_6-n_{PqG&&fJUm38VCas>MwYg_ z24}c^z@jOY@2XdH{~M3nm9Opd{8YQ(HY)m0K?R|*RUyDC1(Oti@BScdYZSTNZrQn#2OGQbDQt;78G$mKcOJd=?$Lp7q;}{z!%I)kQ6m^wK6Q$>A~gk(BHcWcti* z1YAUI!M$Z7{;eOs)|((c-K)ROE2*R6Z`X0g73u5VY_bdARGYJQ`~GJDMCh|Q8OCGP z4M2`D-&|;29>0UqDBEYRmj4+X)e~`KqWuDzlS*ZlCfAs-w}7FCy%Q)xf%4vdb!4Ly_rfZP+RhicS++YO(WfJ8l$K+ zK90qu^*#LzeG@RU=8h1^D4QHJ<`MrQurraDqJN)LrbR*jHJ?2@Dml&Hdo|jps7OMZ zJPfU3y~a{L!zfDR{mWsWk_RL!!3r{&n21fIbuV2GbUrRF^d!yshCF<(gq&@f`lw_3 z`jSb{7V|~_`(~->cWxBdgO5$gr}?=Y!(3Fem1&ObC5{bkg8947WU^*?7sQI!1?Gj) zdQyA>i%us!>6`gR_oXesPVnDb2dci;yN=`!ta7@5urljq^imHVl-1#Btf7ljBBFm5 zSRQzk{AuKk|1E;TTDRCe?pcW0O4K%!ZGg0HquH#RoCh0%ix{j4zC6lM3! zJ3$uOr{-^Tka5rHOs9vfQ~|}{xHdXHdFzs)k&te5f4orh3Ln2mlGJwai`07LN!fgr zITZr~3NWzSx1UIF=J~@r6uH#P&~E?=mX=6>5t%eHc`gxL+6ynon|? zQ-f(+eC2aevh9gjrrCr}L2X5+Z_3DR<{9?FpHi3^6`3%EF7&gGY_U)J?BTdEEt~O` z_|Z}eJ^*r4QBtA-VFjr^j1rX=3u^PG<)!R|*Nw2m@#D5EMzwvAXIM%ls-FFui6N-_Ni|0${V1VBjNvZ}9tv#r?pYFZcumKC02w zgk?`vMBM<9;zNtWc^icWo4nca?a6#4GWy_=MwR098s}3z z!jLn-K@hK{@Tme~69bE0cR3-?8gc=>EZFn7TYY|_4Om-_?3+&m?OyNSw|d@7>TH6& zAI|%LJ}?zxUc2^>sL*?rGCiSr?jGL8>O_k^epS$#V4G-^Yu#w;LlJ#bfl9zAK+TqU%Dh3|ho@Czd~} zL>^NMYbOn^{7Q|DjTM0!S*4`gY9aK}n}Zk;lO+BW+8=CZwc5}Z3>tjv*bBR~)J6t$ zX|(j`-t`g-TlgnYQ`(``XJWFzH(j6f{qaw=Yqx<>`D<(g_9k-ehr7xsY}|+Z_a{!j z3*v?{#0_TCK`5?5)4h#U*@Yi|yEADwVC;X{4`#sanCyROA)Pd5ZEr>xJYRZVW`)Il zrzqbAn6rDhq}B)dL_^@fJMKQKC9h~ld$Tc@zRr}~`1q|-n0xi9H3PgS=F|Q8?40*n zR09jF;vyCJ?M^|p@l2|ei_6$wARemD+cb7`Ec}DKv%0B2F@3w)depVdple?EM|1bk z!$!YmN5iM}R?OYjJ_nS-w8aL3-PHT1bW5U@vSh&>q0vTlsr~hyp1+4kv6QfF4;oBM zDc0%wvV?rx9)lIeAp$2<6W}z|a|AP?BO7@%mQu1ANuv&j2>bpoGr=JWh&g*5 zo!cjiwFlb3*Bxc$ss@m3P^LBmGs}&7IS!t&KdYNy8i)I_29skdgh~pDz_kd0llDIX z0kAGGGg)*4BAMraZ6S2cgoLOx^rZap{@PtA&59_pCeEp!c5aExX4ZExzbb6eKucG; zH)$V#vo}13dooul0Y{1n#S^qw{|KxyK1n0x5SWLPzN5oXU2;3?K$N=K4Ba5Nbgj_U zsr!f+X6L!t7m0rk^(E7v;hq!DQ?b_!eK={WQ6lrY$!s3o?7#Zu0^y%k%ay|gubxBH zV>kS5OBk`nPDuQ9(%}Kk;cd6MnvHE0Cokh)3&-&h9HWnSZSV`FPE- zqg`oL^BZECm2JRrKd`-2XDl%I)G%u*Xz4B_y)L9p?p}JS8~m7sblLIT+M`PKIu1w! zs9&G>0ttXFv*d=EV53di;ZDk`6{WU2^OWNZXo4!<_(aV z`zSfovHBGDdYa+q{jJa^(9|C3Am&Nm>|%xR?D@5ok|9EJRfiL?um>^i`!sgU^Z}#E zc#@s8OY}1$ST#iWi>i+5FO_6dvK_&e8S0 z@L_j|M)7jF6lpC&5PYUqoLm8rtQi#Bb0xKN2Ne3ft#E83VnIU^?Er&C{)V@Ic-yVf zdZf)-dPy+>5g{lHE5+y}Bx}gePa6G@T##w1=J{k%fB*t7;B+jdqvMO6)uodEiL0uF zj!$mIzdoniq?qjORD=fFBDl}G@B?m3)WU=)%{c`De^-3NLE%> z6d16-}*ZwP&O_dL|^KG!O6=H0_uVGlKafr3Z#1?tu*Irn&Sku3?jA zUFaa7CK-)7&VpD1TdKb_l^>{Ph7qLL^0tPPIAkK|mp}XalIxm15Hr_X&vax3_a7_Y zY_esC7CE1;Q1WXYZ?G};KUE1?t>&kDF&w80IF};n>qPAP0|Q~xkh)IV5j`|OKi`R_ zfJaQu4To6M%kIXc-L@~Zy;c2u>HID+K?^1hHp|cr&cxI_Lq34fj%|&Le`B^nR!&ZO zFfY&B<%B|AUvs|sb&A2k=4g=7Q8%G<_v>&*E$--yA(X4ip_(R}M?9)AvUsEtUu37` zMAPDo5w(JOLti5lGmtn?l|5bz_5?DIjgpK(i_O3-)Cq_I74`T1fa!#M@_Z$(rDAys zQ%{f|trAW%b-uxLayU(JB$uaK6f&J9^ev+HQ+J@x3vc8%2}Binwq+v?t=DwD z#TqSozTzl?^O_arR_Tj#pM5XjcIL(kKjc}_&#&U_G!42w9PLmjq}H#}p>cAEkCh4` zN?N@|>_mdCQK^AW-W~{Bu*{BTIFF|J$&Sj(-2NK>yp&N|eQI=qpTuV#ozyCpstt%- zX_I-Z_mV%gxS4(^`~Y+6@tZ4eSjUHPu}=JcW|*3H=gEE%NS1J~GDrJV$ntM0FE^ZY zqADl$DMM~&1cZ^+<)k@mbRt&%Bqxv-ey%WO6&0PJ2OWM#D{KF-80o!vs&uKE)X~m6 zVhumb`GFeB(wff;{T}H-yTn}%kQHlo$}uosV2#D6IMWh{FB!E|rVq|jeWF4#H=?)V z_i*U;pwVM*iF8=?pw~>9Hfonl_$%!EFun*S5hqN3C%Y?Ft5tJ-3=YaI24WHLK&pO{ zJHGmf%1yW{OU;exoI=Q1-(2Bb)u~TI!aKI>WH0;H%e@KG>s6ul zIP~_2ZoY3046U5^Z7#PKe3-YlGSbo+W;Fx#&nw!JHy1O)&|r~p0%op4m)7$+wW$2y z`=yxchEnE3K~lauR)u8tzN10x{j&YBvd{2*{7Uy%M;?JLHKvO!*p79lOSFY`plmm0 zJ?h;rE-s%>UT!m0d2Mo|5niQeI`J57ZiL+&8Q=N71@binN`iu8OJxC%o2RpXk2dLH z&jtfK5p-EVj&$!e9OG_CrGj43z7PIv6vCj8nf_}s`+QT5Bef(Ch5s8S+ane0DnR^ zu#J1FbG%gxXiMJBp8opKs6f~Z_^(R0iyzldmuM!Bmlg+pOXfXmUZ}mv@!4Xb+z!}= z-weHMy0Nz)RNXN)s%r#=WXUwJ=b8d= zk?|(Z+3l=|;q*Z`ubKagtM3j_Fo}|oSEOdI=0!;7fcx|P2ys=TD&!z&-8<1!*bKhT z-~*HT%UME|HEF8X8ppZ!mwz~}2PsA{5UrsiH>0RD;Z1&Rc=V-8FCVhKafIK-u{UA_ zem?oW*a&N7sk!o6xnOU$DAZ#_P#wuKPE$Hr@OGEaDjq`B z)|?hB-fI5g?xnW~#XVm41<$TP(C+ZAJ`tPQK11QDv|JoiG$noqW=sMu{KcA}w?^OL z@pYTz>4`e!TnsnHxgc1^KD45(M$ypFU-J( zc=bwCC7<7&v91T9NQ)lKm%zPTacY6QO`0-!Lz_oVe%C3$uzb$7I$v10f5rhjq+KQ) z4nRvOmX0wfcc4whnhbjZ9MLfEzi^1iLhh5wb-5xU$x+ukDIfx#eTgu6Q5u$f1NLjzGmdn%b3Y$=DNq-R7+S4KjJ4{fS`w zDusedN}&PB?Ay1pO~=l*`vaK?BnBpsRvVC)17CzJmzs5FMuo$+@rng7 z41hxhCBZr8`$Wm)q4bd59gZ>^QyYMCHbB7Fv$(SR^)wh8|zy(M8I zS{u)40!QDa@FeC;fgUmucgkotQV)ojKGtb}uD6B^NZ_BX^sNT-4!(v6HP+(Ftk6;U z&U+s5MNCiH5u$|Hwq7Fu{dZ$Px~8dsSUzt)~)$sL=E%k zRjTFl#YOD<9>&}`!`zv;Mz~rA4ui$#S4TTvZ}zr&Z{8LX4&1%T^v5oHpF>T~Pfncf z=$UIGp|IW=HS(#YnF&He9~ZPGeA&Df;;a&K!F z;d}olG?<7T)aY#OJ+s;QSfrCFC_V&7I-{{Mb^&rLeuCOi@N3XDSDZR3!HvE(nhq`V z34dENMi)Pl;UfBU*#pie41NdqV1e_T6+e*KW7GRi@xneIb zcqcBUW#oWdnSI3O>5b(9@t?3JCVC^T3ZQ~=xji0zN7Jr=leuQ~Xk&QA*ijvQSpkHN zGCzj)j$FW;c-E_>5xCr6;yk3kcOOcE-@02W-md_kvvH(j2qB)$^K{vL zEE@sj579X)U?GQ^xc*y=oW;Or*RY8=z`|^%_-6Xx`+56P{)WmaF z+w!N7K!Nu*m?z|Uid;S=`>euMl(B-wdZaS5z{e1CB{)4*#arpXeKikO> zh3~PszE>Y)xqr3*5%qHBG(n_vx!Y)tZzYdNi09(^Y8BJd^KiTqGhXjwEiBWMRRB_* zorBmL4&zQQedA~nOVi;y<<~rBbRSLsPIU_5mp&KSn$}{ZD4nSnYf$K(jxC=8>_;u4 z?hVKbpi4Hi#KoKyb=5%IGcT^Q1Y8~NeCRw;t=|jFS5-B})M?X9I}PR!EiuN%xhJiV z{Kl#IjY0m~8dCP60zU#Z2RSn>8oP$M??dU7C>y=!$%7ED9l6=`PCNG-2Smio@hQp- z!eJ9JlzpiTM`iMsUkd`&&jzvZwp1EXd`|3v+_v*nr&8!SnSLewS{=&*6HBvr6KUm0 zTCEn$kNXvcs*_0f(&XG$%4EsH!}j0hu(+Njz&4yrJ*NJ6=XQ1v&>mkA1$SA!!yZ@? ze?WTp5d6ubE#l4|OKY&`hD8VIge{PixZbM^X0P=zD3=t=w?Fj3dG2c$rA<5a*J`f5 zWOj9X`~5V-M=3g&v!`)N8=q3$g~>4Uoyzuj@{!6H-Xh4#VBPV;)3kLs>O_Qs3;4dp z9U$8A`DD56RL>W>(8%Y0c6ETOEheWE|!TIVKk*)U|jEcSN^x(mK z)6f_1kp1TjMmSQXWFyg*TV;qjSx3?+Ah1}7dZ|46-D12|p71oIzU=F+L9e4tCiM(VeJdMm)pj=c1Xa$wY6qr zKUm-bQ$)UMu>vRdvR(I+Uzxlyo@9^a@bmP=RbKsX2qmw(8p%8GJE<)xYSIpPwsl0@YBiUX|H)0P1QY9ZqnGO^v zhV47t)1iLDNE$&|5f`2@G#CJ(p0pX(VqTE*wyWhOV)HH5xo9^wzuQK2ac<#*V0CK1 zCpqp?)TPfaNe|9XPcbUD@jO(2+{sO_Z66nfRC)KgBd{HYfmk>R8x zS^{zI`|aG*$%+tli!+Z1ge02ZP$pskLcEK54L2dG5;9iwNwFb9`^L-n3`tD+ucR2X z{n8NEbT~#okU}*Rbv5J_ZH^Crs*-Gjgt~afA4abZ!oIj?q7H4o@oh8Q_@3K_w;>7GH&6hz_3TQ+G4rdB`zFML@g6}|u z2!2z7ASu%B4pLuIHF#dSvGkW1^)c~Gy7W@v6b4ckt8_HWDN;EdaHo}I_@iHP%t*q5 zciNwGbUEeUHPG2$PA$UZjdANx$(H=A;aaOl;Tc@^q3pvNgmq>5@@3yanEvBL&qFdd zc0N3@G+V{_RHiOgTz5}3C$==Ks}~Xm_^1QUZBiIRPN}b3MIBED>%++$?x!u!&q|R~ zA}?TGK;(?Ubd>B`2d2{RtDGg@!P`fj#v z9%?n2>}QbUs+ht)?xiOWInX_9wlCXy_efKm@QXLNJU6I**#67-1TJMpd(x{{_@lEF zVkoNFdNa$;jVIEU-DNXOQ!U)re0aRhzcer>3+H&TGuIfjE;nB$Kkb>|-MJ08D~q6C zc91iy>8!cIO^I6I&WTTytrb+lGa)t1Pn9}m@Wv5$oFeT-1RML4g~f6qVZSVN!9pkJ z6c_DY=wWNoShAjE5Zwtn#0Ww=K)t5{!&x9es66rr=IM8|&aB-&Wl)2CvOE2nWkO4tqV9lY%2=*j3MhE$l$h;;W z%dT&>`L2n)8$l`?8LOW31-^&TbJE(+4nkk2oZ?Kc4_{#MB$42wWyyROt;j;(VYziu zQRZrJsXo4d{a}uD!>V7ChH+NAw-*Tcnzan`q8*db6HPlNPb8?HDl5^5phYJu|leer$OAp!lYQ(vWtq$&>g8e_ABGB zz1%@lUDvv~A7`UaC6l+-aPc8Rt!a-M_=4*&id_MEUk^-$$Ady!D?>=%7?HIKr?@-? zPB}Gx8Zre#>mOw_P@ppDjCNs^bxukZeg7UcNK*LZZdejYt<{zAogj1BfKgngW$7 zH*{C83~5>&tvo|G)!Vd(t`B$d0eyZ!5z75L$CM*kfuxchU({$9MptVHlqjG^-@6)i zADkYD%J8>vzsuN9AQbKffQ;+26==Wki3nm1!C_3+mS_)HVok)Tv7WVRq+Zr*U5Sc0 z5hGsPFqBQxaIfQV-Ik9WPG4tMMx{m5FE(kV*UvV$9Llxu9N}w}E-ERUAR8HSBAVWNI zSWC4?#M#U@dNM&oM68#IcXPxNBQQd+Kl#d96=fpR@n~PE+9X#cP86tk-W|2A4!AmH zXIt=L%w_nb$tN7o)DDp@11dM}!_>tP7Jkda7}LRM;ynzK9sXIl;UOOz8P3|gHS!PZ_Z8kO=UiF6{&&o}mvx2oGQ5zV$7pB2d0@x>3M z$QwyK$!<$yW`n?jX{%@eY@kx=Wny>5L@MIeMKnYj8^7AS#n;CV%H)`%p{2e4F}uFj z%>gSO@s5$}`l28ju26r~02(3u4vcIr726|JWwmXAty#=(;h<vYc28LR*r9+@QcQTpDey&QLl$7S(+uLmNGRrDcg}yMo&SlsL1z8EyVivMvRvwY z4N7Po+%mblG*YA&CYn)_2L1m)5?~ilj7uYa}6TDiWW)Gvb z-O(F#vSI|md@YqeB2_&X z6ueU9C3sH>uNeM%CVPQcA=7=PBbbU3D^d3pIWo$PqbHYOJ#p^`Dy^)QgReu1M38_C zIg;xFJZa1i_1AXBOXI?_BQRR?T_rOO#_sg3EW8Nzd%b}lDpmxqcGF#T4(MR&wUvvP zH3`JalTuR|_8s=HK94BALA;Vh^-TfRyBJzFXh_;!h}Ur$yyiWfV6HpcE7z3Dvf+GIXqaWWA3~>zr1}3 zIpID+x@vk@?@++1cJxVgYD!Q#x52h;p zDE+{Z0jy4CIwDxq8KusK3r^_uVt;L~;QS9b6opl-*6*66Y73 z*Dwdt>#d^Jxf$&~ls3W+h4MhGvzMijHp>a`pb^(8r}|0y;O&w!`t4m}AVvg+z!Fi1 zkbv4-$2_oc;Jbw8vQNc%XOBU9Uy-w=5*zT8i<|T6(^*0`akv)4L{Zj}xONpRa$&@U z{R{l7-d)X>;(ZD8F)mS`I_#|B(q$RG?w%gFhE20BU=1?Q&m?W@8!QT(`p)jy5k~%D z%kRaa%etf#a+v>(TKRbq(vdWs1)<8JadkLS_oOe`(etr(cKz4Zy8_$HDgH?Xq zv_4^N$d0wc(7(M1kqrNfMY_jT_*5jjfmfukaHwADb>2}`apBuJ3?uYjf68Uq%%&!# zNTa&Zp@DCruSP;UM_g_vOtHQ{Snwi5P74-=1p7w#5i+&Qu8sQ6KyAHoW^oP}Fikx29`@n&?<*w{{UwKOf?9jdsPHgk? zeDapeAmlEOL=qI8m@KAo?1A#+Hsq!Jx_h^KE#CbQIEq?o`?dbgUQ+`l+Grk27WSd! z8_Y7lvq5H0!t4%4VU>xjVvA^%zQ=dcq$j3h^vJh}`4ggF?SnIeD6@0jX=a2anc-mX z(w6zvxb(cS;MOBJB#EI}eQ9oA({2Zgn6Pv*7W8TgD!-+eBZsTPg`kw7o{R(S(C?r> zinv~?<1P8@y*RjOC2_wBeN~DZYM3WM>NB>NdH}s9_vB(_O|V~E8W_yA%mq{FzuOH{ z;*}98nP(~^97mXC4LM~s2Nz4+&rF4-i!^m!O=9D%>%y?16-JqP!N3gX6!t{u-XCog z-mdH%yKlMOp9>#Ou{7^EJA7vT;wGcezV3 zsn#>I5aFOnOxAMW7P;4@RWvma&hGx?j1m&Lc>xpMR2vh@LRXG(%VWs|OLK7806VIH zWdBjfPwXGU47fK{QILKeekhV+>}!=kx0uRLLFUiyNmtA%Yt>X-Et{vi^js?zBgY+0 z-=LDpRlgqj-6TU=-8=A=c1dbaq2|~9A&Q?7ED5657bCkmvh*cB+Gea__&>-J4Cnm$OUHZ7I8jvywO-66kv@m2&Yg@4>n{Rk{j^^8*OtyiabQt_bw!?jTY^EEV3Uf_F)D+yl(=TK%ZlK7kFkVI~o2T+B&c<;IlM-T~x!D`>U z^Q0}lR%+9r7q^hXLu)C`R_4_S&z8a1FrTN;8uUezJY8vNguXydz=z(Zd@$$ZYL-?x z^X`s%`c03zeSy!C_cNwfc7XQ%QbRXrWflfyzBwlC`?-75AXs!&@Aoq1p{5SMqV4&` zaa~)hem4V>8j*NyXo} zfaVGDeEu_Lo%%X+Ck8ClXFfZpxy=}JhQ!x5cDsHL0p@?ID5oPALz6AqoqTa2&p{O;|kuKvK#BQ_uPkMA@vauAO++MTUB-Cy0Yoqn!+ ztA7O--)WM1SJDZn9HJg=(lJpF`Gg+PD!dkn0Sm5);4LZV17Cy zvAUr%%hdwmrHvS7Och>+l2*H^&JRTq`HY4SpL5Fc$Y@w1mQxNlj^%@br+)#lw$hpVB<)>KwCBjDeIRMNJ4{2)fQ8-yIukQF2(g zg~8@)bSJC1%6@Vo34h#bAh)F$#vRjpi|jG8f??90*#Y}4Zk`PUkYV7y+M)eC$nRuU zda(^^-PIuTbcco2!_8X7#m`5L@bja?=F)tV^ed?dzBN900Dlh$zGe`6*2bELY=Sw7 zI6fHG{kbx6#jDT;QpG z6MQmG`#G`3tJBI44fXXbdodb*E;F9g#vGHlY6!YS-=h`MCbyyfvWabz0n&cu5(^0rKqQ z#`C0W-8uUzQY`lc7)^_B8x9xsn@Rx^JO*Cz*VxIuz4{nOPZKN|rk(7Npa2pirQOHr=FQQj)V*Mic7) z?&_`da@Z-_@Z}s{fLPEj@$wFAGD8S$wvxT9(MY+l*Xl{=$pUs zunoSK-Rsip=1)f*ee=sqwZ;e4A>lYz8J)6alWWCUGw6)O#LLjS*NO`P>0pfS61;YZ zN-HJhSzE*)RE6fgVDvz@7NrN?n)kT}ox&VIO z)w-ffGjtb;jIYJgsIhO~`ZOxuOz0soqNSCI#&4Ta!dQGIl^eOqJj+{Ie+L+UT^Rb{ z&D=pUb^oia0#_OWou5#!*LNt5^B9kdWNa{Di=Fzx$#a2n?)Gv z2D=e&vN?RP_yKWmsE`CUTOHqYp*wIyvG8Yz#nKpCGcFbDF77R zDf&Qe)4XudaMn@;ZGiAm3;V7y5wUtOBG@1-wM4I zws;kXExTYPU zoGXfZjGyQ>s-8)N7yDZ0ipg5&BB&;q5_BCsCiiA9TMsh3XbN*~gdXWek0J@H>IO;u z7#Bsi=I?RGGTuoI1?DxKI-f7NriD`z-w)XZHhjECIb1h0=4aQN!7#Xn0$XnqKVfaA zx_uKXU7=oAC8kns97$7*STk(dj?2xlJC3qNy+iEmT&>t`&Nb~ z^LQwi=YIk}a{Uq1&St z@7OzQxXFCv#|vxwLBtV*F{7JP9>3P#yOdW)a3bn`$0kC0ZQQ*qEdW@2)<5P8UD7m5 zA3JAE|L%z8m?)3D!4q;pbb93)YaU@g>ZMUh@Vf-tz^*2LBc9;eDeZO;R>cmH`RVx& znv|=@YA9vTsNVL^{i8D3^G67_={EMC=lg|(gtSyldCl4jbFaJAZdaZM-S>JANK0UV zX=#!tuW~Z%bNfxyOtuMswf&|(+H8QFMq_hqIXUu`e&V{`uI7EXx!SI@6Ztau$iBCr z!c{`Q4f5y88S`x}`(aG2(mvA@k3J_-+#Vibgf7h5U)C7MtZ%3heO1>7dqf86#>ig= z9Tww_nz(fDnh!`#%W9!E@EU=_-&kQ~s{WpW!6&)y6B1_bj$eop*va9=*HkZ~%!)eh z!X3KH>OWMwtS#@x?U{^K7ae=||Kc7gX&szStF5K6<8?6A3PQg3r#YtB)~b}voE7I+ zDcZ_9T>(%yj-31wuOp`AsU~%=6+F7}Xj8PSzFuoU>y@GhZBtSQ655?C$H6hA-<~ik z=Qi-Jr`U=8(05jZoH>Co1gxM%g;(S6r)As_biEn0Ez39O#;ijKjSZRSAf=X;#B{h1HdoZSU~4VN=ga;3n|j>EZLJ0A}F8{~yz znAN4j=CnfDMQs*Gqj?}R7$hk(|GGiKg)q^r7ls?O-6K^hbVF{5%wV$v{ORh==@&?% z)t9*z@f?$kj7{#h7W*O$NMvn1D(Dxt4A9+@0YYx%x#~*zfuH)M zgt>V+p=XYJzC?!SrKLSt4s?)487Lc^zo5txX=-cB@zT<%Li9>Z1S6mJl9Q8X!sn}4 zuQZjwl-d!JewEx^E`u|lo?Q?Q#y2oYDGc4)D*N@br})wtQA!7nFBwOzw+_5by%ohw z;}3U9w<9YHv0US^xl|0_w@)_B+(r`TlXTxH$zP+$^WGbp#pu!E+uJ!%>i$+8%JW%n z@@_xHxK0{O>)}PMk`M^4dCp|adJgJdKx%JxFPs%u{eS39A6fjip2%@`mq1mA?W#Y+ zp5;mp2n=+>hcp7SyJw+LaQ3to?d`7cJ7eWaX%UPd0cZy~BHRS7PjASh6d`3*FZ`*5 zhG|h(O-+r1o7+f3BXmo!t3SIFA|$8fta&+I-G~?;muK(+Hb*3OBF%XY6kK!w+X6u# zF{g&&;$PU)=i*Od*?t`1oTk)pnJd(?++KefWocWzto#~?yz&H2xjmVm{oP| z#(LfQvZO+3{&pnz__zmW6UoG;xB3Hk5V*_ku6rwaIZna$`pb2dwl+2rR76;cDfYq= zG(BQolsiBqC1b~`$|vO>?;~&?@NWz&02;5RdAq}t?0HNe`;q+%m4_*Jb~Xh1?wft! z2XJ52t?Vzq%TUuc=O7CD|9j(@A{bG(x@wltcYjPfM4c-kWIBHZ!zxy3SQOs2>muj(K~x217=UoU-X?{)zn-RIfy48HdL3e@~hSk;)A-JM*I@#>Yz% zA_=;BDqHM|ZIKieC3YOp0Zbcl$Uk8#aDMpP$iv+~i_iZ5CJLwWWR4v8@Bfqj6n{=z zIPPCF&)*%OxS{{JjNEblopt^kJ#EDm(g7{-$Ydkx{T*L`cnVjSm)|)XY>xRYEIsX! zFivj-B&y$=`r4tZ>i~b^&;FTz3!jM*>o%5Xmsv@&SfK0HX@@58C{xZdb<`L{Bp%y zMpcG{q%@T3)aWY7CGr-<7c!bIm(~f_JT$Zz+qQFRrN`YV*)_1_A1~9se#@KqkrB*I zO?#Er;P;ESb}!^7KRx>NsPpzcUrO@!sq!eqGJ5*tzPP8{tHH?|@!07xm`eQQ_WV>r z#0gPkDKINL+l&I}75Jh~Z@&6Zi`?jn&VRKm|I=3Zabr}}9S4npfq~nMjF7bVxXCnb z=iDx)Rr?SB+h&q)E@al{+5?wKZcS4Ylh~8?_E#J#9UUFAh^CDH@CkVkaY4cTk6=#; z=Ze=cV#5nJFg0?BO|}1RQ$r2&jEwG|dCKa2JCNU_qorjH`0~FU?SGq2l5ZYF{n}t> zXJG_kWtwuO9zj(fWMnuCA``vG4VbG{36L_lhjtr}#PB`gvj8JSAE^OR1wh{Be(`%n(E6 zs98^ZoIdZ3`_EZu*i?8!-S<~no66P(4@V01t5cUghla**Xk^E>p;xc`Ykga%pD`Mm znwh;)AM_G&;eD;HenYe=I#N&MlD3Wxc2^Jk<406fp4Qq`gFrID6z}sI3jN6|It({f zdIFWOfzREBq*d&WL~S(*di&X ztwp62hx|`i94plYf_uj3*6n+=9Hw*WS>6Q=v&JC%u2(3RFlO$(o^3Jm*vx%P!nDMv&q}zWL3QgKv?ne2nk;}`s)p3rd9d;9Ct7#t|L>T+n+F~_?6_cR)$(R21$Uo}qmkZ{H z1>%l~W=Ec_LW*5?zUr_+3l!*i~GZ|5ci6P>S$5s4)l>FIn~_HnN)4azg4&<2W1KVOLT z$t$_Ie0%X?nd>E`Zy?n3c<))$Fy5}wHhHLNP@=Z~%L9pt$>IJqxzOdnoS{|mWTQ}S zm|wcuircSJV+9%&%OoFCPBE9-K={T)W!H`Je}+=&WbZqF?+%mZO1gkRvHqeRC?AmXT5TqU7VpEj3wj&&tv>GN?Ge@5t-JEPt1q zblj#{wlLJH*-6{{JVhV|UcdJSH@UJa8cGHi%^qd;K7Cl+aGbaPtMqXj9ZbQm;aZ`* z^+-ZvX^CUMbJmQO*A0W4{~FP!n>2=5nVGQ(gIjDuLfW!Ec$~m{y+*INf~jx13|gNe z%NejS@wBsvM@pi*ZLvP%f_1=K5pqF^6UKlL{2ti_Ap?JzW>x z$mX^ctTcz!(ads2#!H^yEWec~I=pkg+hm%2lyL!7Q6`rR-KMJ-tus_-bXR&B@KlDv?kc zlZ$f#-&%3uPFI1;c&%OIIzE!U)|s_)(Ke{;opt*9FIQ-3#EkV(7aof`yYH#;1+&#IK&ARd6wK4GXAyFywOvRUaWtsPXZE`+Cl=0oH%vMqi zA;)nuY2>GlLvC1wnYM7k8gP7p&0d_ShB^Vu!5zJBlcbtEG5d>l4it7~>49nAz ze4LAGZfWT$OdZd^W#uf!v^7aDA#GD$7aJQJHXf=+oC}rW<~Q#Zm~&ilm^fBCa_3RV zC`xykq^JA$cqy+-fN9XDYM;)EbcFWaEp7_^3qikfYp)7>t%x5l%!4wSqEvOiriVw+ z=ql>D#Aljq^xY_NA}U67M`;Fu`b38d`)gu&&0Ah>M1Sml66A=br+qE^=26#@PE4SZ z!s9x1r%A}h1DA?x;(hs}h5E{Ie5NL?pJ>ahMxG9NmXwg$9ctbQJ!yXNZvhrHhIaf} zNoO<{YoDZ3zWDKcR~h&ra zEdUX%eUtQS-#@8tK66Vw-lMeZBPbN32|2 z1OZ(!fl;k&iV8ly8(#vle6Z&p>_e0evc(@Syd;PD@_B#tE6P2vV?bK|&X^f3?~}A{ zehND{f}=dOBv01)DFg{P4F4g3L)stIokeLtZ~ZI0zfZigl24sSX{y{#5Js6iu>KPj zAJ19qv=QM>*1(B(mRHhoi8XnC2}^?zd8U;e;)KRse;JVTD&SXAufYd0sp)#_>P(TL z6_zaq3()=N;>!#IVJky9q0?i61@}Z9bLU@NX0pvPiOESDNtN*{^Wy^f*if-~B65oL ztBWYg_gq1xj(Xm6N}d;IhkTgT;rDBeE7=JlE|UH38pl)iR@+&`EJUr-_{?N99Le!d zj%;Gzxr7C82;HU8kd-{Q$DY=5%RMqyQube_CBGQ}*NxNt@Uxrm(&$q~tl!RNZfMe0 zR-VCBM#B0;-0wQNhx;%KmS_mvxe`sJdtLLo7%4%c10-uvcUK0pB!^y+#nM3O#|j|= zTm=Fj;{lLYGd||{{xHF=LB##LvmL|Rqt#>!RyMX{4@s%1gtAMCgXoa|8W*{;s%lTf z8A%{{m;Pso!0>-XOE2@Bhn2N;Qq$3~;>XXQO6;lsQ4I#vMSoF%%Wt5PtESSrckkY! zBp;tTrG~wInHqfSKd1x$KH-1K!T%?@Mzzg zgM(u+8^GGEf6?%z|9k^p!+%`9DZcd$k=xnc&U+Xh9u$Ui=15|$oAp3?z~1tJ97*+_%i!Fq1@8QKjY4Z@ zyg7hsp>DT#_ejsmD(_CFber0AgJ+gOjs3rvkv1If(mbcKtTYgq`j$QbWf%G=&8Q`? z3{Aj2P9Bps9L{}B`mNII%+0bt)#tuyBpB9Ptw#SrB}KBnpR_=_t-muGLIL^wJ~x;B zFG5f&xqJD)0{+7<|1= zCrj$q+G99es1GOc599sUW51J^yccJO7bQ}p_@==VcIgc*U1-AoP@zDgV=E&fRSm@~PfC6)z?NSYK-Ko5h zhf+3mTFYm2WAW}aV5VGqU+1 z%#Baf<`=I6vjsz!`Lz#)y+3BxZ-tPhx;KwCF@)Bc? z6#-Ghx?;~PJWFgUIQDFVqcH?SWDpP^B4La5ESPzDxJjV0WnOz$bYceJG)dH0yw}k} zOpEMY?-Z)66e@3G%e33NIIceM1{1?u{P+eo<)n9GNqvk^jFMWxSKA#}ix!V=s#*^- z0uy;}d=o*A=vPYxkkjIir?Fc~D;+Cb)st_aYG;+P?Lo=if;PYUN8CnYpBZ<%c_xwL zj9{JnbT+Z!DQkyWCDn@TL$*Uzw)*gsP7U;7^-^gv9;1(gVRZH6N{mXA`ZkS|q^eVD zCdq~BL>Zdm&k(fhRplp}pCCo;Lqc6Wz_6SFIG5m=jk)}HG(1;yWCm&ML033OKh3j+2Bar<|JoJ1F`PPZJd|Hv)(TE5}Q|H%=jlfX>W1|No1;3 z(c_~C*YRm5=5g$M^a@VUV}Ru;kK?&RpTuFV|D&`!$OTPhmg5t}YF)G>z1!r%f`g^E z!Xl>eZ~yGctEH}4Oa5eF#gstz+uGvwXE7+jU&h%ia-1i+pnxqU1h7x8 z$K4a7I_TxS8W5)^mvYc@oA%rk# zMJ=#?9CG!uWX-`>13_i6l!-->noza7*h6LY+ZLhi#q7}eKuk};>%%J%rkLY~P%{@( z>rM!Pp5fKi-C9|{&Qr8tH0q(?->sSTBY|9VQei@oRu=S2OUtpNb|+*)&Ok2cW(B@oWs?OfbSMyAO@h=y`iproR>m_;{SW zw+LaUsNji0^Ly5R1KlXZ%N~Y;M7S8GT{|}pMr`x(Bd9JOPl^80O%VjN@czchK*=|) zeC_c*Yr?@(_wy;g2Qd?TigE>~Z(1PUGUcQBO%_H--tU`Y1heEOW8Re4gdAM`<$zF; zv!XJ=D-(8(_x|P<>Zsx2;exZ1Lvz+APh#;6o+o8{H5r}`O53jPTTRz?3%QrB^e%}O zHJ-aQ?mh>ndRd>G5E~r|x(PA#&qZtqUi1*+nPEZ3Z?Hp0BOcR=j>2xyCNfDL(g)E3e~98x%1-2^G@7G6`qv4 zZF&jZFSK9XlW55M8}~DWiE)@XBHb3ycvv9p-PmU@N+#|No3E%B4^>8CmNflnk{4X= zuiD#BT!?Hm`qo|DB|iSUr@r*biHV$U4i6Iam6SdmiA3EoL!9GGle~^s)bq6q@2?GF z!Bbqx6|I9YD6f@j(-t7#vGxYd-QqGOMaWJ$NMSiNUN)Q^=yT{-74%s)o@|P3w^ikC zE9mc9KAp z4efwq(e6q)4zgFuJ;lfA0&$)Wc@VWRO+u9qPS}bnTe>ar!S1;ppX;brt&^?m>erkf z6!mA^kVd@|3wWC}!UN^LA-FP_rJQz#9I;(kySsHRk%sT0C_Uu{_v>i-=xuPEF$ul- z@$r7BT%lI=7=W=@(z4l4Pso9_xDRPgYJ9-FomspjE5klg=}eqjZxb2G0{?cqJ(i(x z(?R`fbs{MXh{iFIlhI^F5t)kCnG&>c!OR5EA16As1cGM!AjNgFhl1YVRv%5ERQ%27 zkWM%`O?t9+@D?wTk81Sts`g~RqT_ldzIxE#|D7ud7An}s(&cbMrU6ea`s(vjfU<&6 zNhjP^KStMkv;00-ju;}UKP{W=a_m%$38jexyba9EFELX z9gJ-G^T%aq^=T|tIoT#`f#@$lZ5uwToGOzH zR*C(6C}?|ck*%(*qEZhHQ^5q^M+xXqn7y||IiB*OeD6hx53g)T$2^PixT@PUXxuXP zN6+Vq)pkx-*Ty#!hDVlMI0+kz^@FoN=`&i;!d82CgGGAcvp)MbZMi$G{JDpB*mZh2 zY;L}g}S*2A3+LPMTyW?o*N3@;FNQV z*x$rV&1$(4#J22v{Y+?Tw%F0|VBSfbRhiL|lU z65c*}|FEjaP+yD;61&w&CLD0&K7zb8Lhp?^IW!pJ)BpfSrPh3Q~dFyP^ks?^)SB{_S1H3!Bo%GZRAE9{QNOdh>g`kX=4)MB z4y1cJ-$$?MD8Fl?it^6hVs=)7IRcKrL|wjetuT*Q#E6Z zJ9Ds#`K1YhzyyUjqUUh>SbQuYJe=d!t(ip~$A()X(?K^Ch>cxbZ+G;x?<&#{{jFDuI)(qu`SX)CaGd&xsFL``x!DrEvO# zLqk0R0|V!m(flDutN0&3UobZymkvKmK*eM7oi?T(({^6FAmWPW+LqiEr98Z^@H3N; zq_L=9(OqTzSHA+EIjR$_?WV{7g4(q z-u6Lx=qD&sF@tRIXp;X)i%3oA7G>~_0HugC2e+4jFJ8WQ^=)@oA;sfy_0+PpcwNPM zYKs_ee_Nzi+(Sg~nYXeMs5r23uV~W2+>U?xrj%;*L}i}xXR|L0U!2>N`jok&l%k); zTH1cm4%C48YlQOP(6!L6K!F1Zw>~07XV+t@LAY<_HVv@OmN!0+27)`v8btX0IuYR#I^@)yL(Zq3Op9v}SKCk>zcW`jmb!$p;oQQHhV@U)T*K_cQZ7Fjk!U}u_47PnS04u z5;A}JHOcod-5*$OLv`zkOPAbfJJ-KV>F386FKtMY425HG-X057Ktw0!PwyQE4wvgmcA=HItJf1lGm~5HZ_Wpw>C;p zMWKi4)l_|gKD`c)rz51-tGZ5}xRm zzA0QMRKt>8cVt}6_ss2of4?NMl1Z~LTvcfL`?vX%BF#htM;V2$I;C|Urz_QL!7HAG zg>k&{n(NHMYrfyf`tu{gQc-}WgG9QlFe(13R7m#nc&}Mx@Veo|tE^zULf%QFPvT%p zB73p^Wm~e=5h!#L_k<2ECowzZyqBH5`bx_ny|D-#C;E&&<4I?p=YYl2KIk z$u!%~%RbO|JB%WF$8&?LMF;$CO*?;dv_O}2dnt0r_8PSs&d$wsOANo#NGCl}#uf_f zC#{N4)v+S8p(zjvT(Wg@Nu-`oV zjgxkb$O&K@yiF`Q@r$|#dQl*zfAi;dWz~< zBJWA>jfP%qu|s%FFo96?6;~@H*Ad*tQV)C>3F8btmj$)>}_+i(>@{5)?uQ0Cgt@`mKx^ZKf*;FyU z)h6Y^`;0e?co^(bnm$6t}p#ZaoI^pF;s8~wztfq#^u z85j56G=-+ZMM+hpqAs}mIt{huVYL_ZdnV%8Y5fn9IZMLVcq!Zao-A}skYc-s`lcKbwZPFCHAzJEOlNSfcTmWsFo7O0&YT}#}s z_jOP~V+DZBn|fiJZc93s!~*rJTME1p!INaXSI+aZt6c7y==3`)u{8vjSQ7PXKVA@t z_478sFi>iC<_{-%ucq*R!Iw-2s3*I$+S6vD19h65Jcn4)Z1GjDl(#06uaJozb%$;h zeDkMm4c|n8lmJL{Za`c!mT>Iv%kx5qnjY=nyBSba_q>Yfxl6x4MfKh$>lemkmM-lA zC(;lNlf{A~^)wG%8Y^4;1L75g=%#w?@K+6uqKy%`PHYf=;k&4+{?1Nufz9x$k4nzI zNq3eSOoHx$9fP9GR2k#2dJJq9F}(GrCfNzUU$mq=O9_qy>^LG#YM-8fS(p!&5_kOPH>81h%KJ(g*nE;uDM(kW7RQD71NpM>aENH{x$F}bagEM zaeZ;U!5iZ@-8%x;heq|g5)I{QRc^Taxd;Aj2@VPhQZGg`9Su=>Q2Cm=cplcc8Rr(NH>T zSEB1dr^K~IPT`L>-_6%746PVOds<`m1s^v)fiqQ$@ zIK_Nt6u0SvVhkzYhIWomdX*%j%LS8tn}lDMFU^+a_beZEI5=_et+Hi_R-Y>h_r+2F zwA`d-Jm#jvaa}nV?AZw{W@%l`y!Jk@hzz1ezs>Ejz3Du~5aQR*@SFfur0rzEbsR!z!-6_Hy!-X={JBsGe z&}&W#gUQKz7Fk7i_CAYygL}Dp-aiMeK3U|#_qbKavjDjA>3l79pkDstjfsjkt&|RB z$A8H?v^E--WcfY|eC$J(I~r*Sy6v0g6F;hz17&h376xTFilBdfq7-lnsUqvLt}Z&Y z8OW%-^TIF!-?aK!6jVHU-LSYV5xLR(z`0PblCLeXYRZ1sb<4Rsz{*GV(VHJr=kw-uU0ksjcRe(Dty@OuAtSOq>IL<+;p(HdNv`LN~e4E^lV zcZi)A3ka6Wlcn4iMT^sN z9awFM4k~(MDECk*lB(7*DpD_*eaJfnBABNYK4LxEH5a-UW3EtVild+44$4Z}GH#Mm zW*wv$VR$|hTh-}mJ=``}Z*6Y5R(BXzzN9PyzA~UK>3404i>ED47pF!>Ar6e(#QszExbe9Jo_w4`(sPn?gNiEZ@op*&fo3qtT8`d%VjMseP(|lQQh8e zj*6ZdGhh?6(iBAS_hEdWGyOp5yN_!-E+t~(;~EUc2eZWt6SR9wce8C3>ZCyFZpcxs zWX7ZHN&BIZ!Gn2hcIy<7ZWMzeN(b>G*eN$6P0iV;b)wqVdzxTYv9`54E6z(_%<$G@ z9nhu3nJW%is(*5fj5ST7%6s%;wQZUD#78RThop;UDE6-aQTEQ_X%;V_6GT7N@<^^& zy{nG$I?9@l=GGsZ`5dVD_+@a|M1pJesvBJ=x9HK!PW=#ex1o)^!hVl|4SJ=yCs!Z$A20#4?w`wCd6w-H z$%A7e1cF6C>P2m1m@z~QQwZ6_=J`Qs@^VhtJQ0IsWIS2FfqzWP*9B@9G3x_oA@Pox%HZ zemJ15#w8f;@-RGsEx*L~ote!%iO5TaWa=Y~dn+XU?la+rDoc zlve#2q*G*7j^f4Z=npTCXn1_6igj)D2>~n$vWo4eYxBe>T#Y}y zDDQACU?v|+q>WIkz$W)H^)Cy9rcMVAcRL})e>}L=Mw#(P+0dkTf>zg* zyVOB8I5Qt!+>89c4ZEyZzcNRtb-tF!{Gog%wCeY>nds!bu9=zl5-W$8g~oN6UlL|p zUfgQ^7O0)%pZpeE*K;)s^#tb!WX??5QyXUMEgVio+M2ZB2kkeq>*PLLX+b(NZtn9N zi&#>z!Z4xv3;cCE$X=sRiDrGBOx}{lx3wADj%(+c zbjaZ0e(Ne(t^i4P9Ml&XUymqN(~T3zqwNOMVHYL}ew)?09c+M8Ct z(k`$Bx3$8ZA2hJ`orSK2(zXX;kCyvs+H#WJl>T~jc6BaPzbz%ZKY94@p_t8JWDnr+ z9rw>W_%T!!rL3qZV&e)>wUzg}URfGuA-pT`ud|e*yQ3EYJ%w6zEUoZ!9dpi^FuTxs zF?)s8F8veTTVBoShD&{+IFGaYo#NJ&O&VT2Vmx@iDlEW{ahfzdo4=DdpGAvJ7O{$9jgA7MV@h zjnN{bv8|aF)zo}H&9!G|TP-)c=usXIFOyy8?Q+>}rB`Lg49L^REhM?j2H6iPf;);3 zwcU3y$y|E9k|#F&NxkrVZLV^UGl`QyWu7>acfE-sY*&Uz^e?D~YMt$B0J;)^iTq2n zQp&!n{J!J-C?70~_M_=@-8bn>kp>Qe|0=y3ll4p3%l#_^Tjg}=uEQG`(txMvHuugA z#oAHETr#Jpubf}|Ye zLN(4jR-Way1#McNaW1h@Qwbywmrh2P{H2t=&DR=+w%2@t|8VNGyk3^xU6nXEU5Kx; z?LuR++A^G}I6-krSWb@8($Yoqv*1G14u zIwo1dz4HUJmqjxlgqyq9AEAup40qdKjib}GYdFdV#B4FP>uZ2(tL}8Ci|^g+i*j$2 z%gj|8KnNXyI|>5JBArGRM*@k3?ACxvZQkF`-G`-WvoUyB>dc9FqxBvhX;8xlG>#i~ z?Pm+Pprj+&T~*+mhGvf1FOKnbP8ZjQzo?BxCeSf5Sh##(Y;y~Xh70j_f+SuaD1h3O#+Mg9zL$&Qn3_0v{}zqjYwm~Xk^-}Ub88_I2`E445)g`sMF ztf|;-WwI_sROOY;8MwlV+n_+X(o=zj>f;SjRLW>)qluHzxS<0sH=VB`4BaMD*T^b~1J!XkMkMJg^SuxZ(xJ5h0k5?-L^C8iR`DuV*PX z7BMlAGKJP#zndb4-1g=*t4ooD*ZN;qRGHU~F(>n56qc#ds?~NSgy{9EZCdoQJ4Y`e z=5!zuj+(rlzPY&j`||@kA72v!>bR!Qk{M5@?_)(VyunrdcYScv_Et=JZQ))oCA?uS z=SAdMO~+K9oMzVg)BME${Prue8Rv7TTQ=N07MmW^5w6NlA=8LKx_xymdeizTyAm4+ zRjOY3B0R;ufv=8HjAV;5){n`#6pef`?{`zOy@B_`l5`(~x~1X%d?YO1vH~&}K##Q0 zLw;pMe`|8o(gX~Q-|l%U%L$NSfGb1NMpS_94fItipUgqTJqy)cNfqE_R1Kb)0FtN8 zGOE&n61JYYEP{#LasKL7dmRKnt)wn1{OK{0zPn6PH?^19i`wwverQ+BMz8XA-i|P} z!=Q3SxjLf?O8x@YUh!Eu)pYq}F!DL^EUhgnWIyxdaevhS!=zJ!gWTo2pf?@^bL?tx zL70!^WAwCNpxikrHc|3%m6istU*Xc;xntv%A%f;%q1WbqGnLTruUp3wsIke z$EWJ+Ml$WkB6w}q@d@JAa%ZMb$Y<%3p=lzUa9c!l%ptRi^4RI^WoJX9PD_v6%*-dd zV!6sO+%l+SWG&gMJ%$>Sq~He@D-LlmEq2R3Q9zYDy4kueDCUf9bxm3G($UZuBl+Qj zCp^Cg>GTAxhIvoa_1#>#+s5oa*A5R0P*B~B%)QVnb0rEBoUYw?B9iuoqR}rEzhS*3 zCBW+ySaY1PN!7Q(92^|n-BR>2cX!RcUVmr7F?hwJ>UC26?WE$lu7o7E4hj)t<2R~Y zZZ^vf{(!F~H8o(e`n8122u)O?Z)BZg>;FN550H`3y@(li7VOsp}2-PwKoW6(AeJW_+eHkjAvr8gFN;du50 zPjI$9bnA+h4P(C7i)EuF(4I_ULOKIy*@d?-C(DkELN>f#Jv?cdo-6lSD_c4V#FiQaa=YzndbB#YXhps=5D4lpSthA6{X-dqEd0 z6g(XE^wI3rhTY{Cp65sO)8Qw_Qzl;Lt9WM9wNaN9XLCEncZ*WRYZ$(b99a3%?)6?9 zPd7XcQ>zm9OrdB4QySMxX5^RR(i9vmw0w^DMAH|}P5HEBIu$b5zpL);-M-BOKvULf zkAtc()FnWqrHhz;wafn;a6g2(mC@Shq!kKP$jhaorO_<{pev`9Vf`c!HRXr+Ro1)b zz7)caC<~V~@b4N4krCV685tz(6-Ii-u()VBdsNMK45N)<5`7gUt$6w>PIZqz)5qmqCf*4!NxPyeEu}uU+Dbb!k_EU?(Gq#rb+A!U zob=8TFz#iKs>!7*LJR^DhqHLT^?^$}KWmNWrDtXCE&GdKOM3rQh)oaaHRq`)8v6)t z;O6r}{$&R~L+Tki0YYm__lhP3y>ZV)Y8+In}AH1fmbEBi8-e3`;MVS;0e2LOF^W}b~8@MlJ zh6A^2GWF?eGTqJjRRK8$fCP6k!_I^X!Os|U=FLA}#Rs%+(4MU?{MgN)LsU2Lcsnv3 zy^F{P5BZSAIMs4Wh=3eh1yOtT%UQoM2}=qthAnNB!*>#m(TP&y7)O9~^jPZN-8hu$(g?j7_Tj-d z;OdE}3=Z)&grc}&?KcLFcxpPUqH#Zfk&5aGz8csz}Xq=`ymZ4~nvEU4LCsB;i) z&q0O6L`4bIY~vuAV+#n=@v@OJqZYQmnv#0POTi%}W!CjcTi+`03j_xQUKs5JuI4Q) z0=8ei6U((RT&vDk#y3Ck;kct6uF$~`6KC%OF|pS=5uIdfv=_GzX>wvsEvf05m{{YW zce4aaj=g4ipd<&S>o9S{WXLV|+^q2r%-&oWtFfrN=E(&2gP_mIFkmNY=mx!SjM7+x zq;}lw3!6S9-(6sRHLz{%=;b|!xGLvIFQM4QL0%4a0PHhkhYa0r_T>hkGMkU{1Xd+x zYs1tN<(vV_U4EM~0rmmycdis76;NpG1+3Z}T^#^Q+ObCAN5x&og%9|N*_EbV?JIfy z6O{SSQMR!0(o2HvgYIX9uPMTP)3nFqm4sFd^9mPFqrT5+mDF}>;?%jxJ-%hMNKegl zrFH22=dn*l^0l9F#b`e_dcE+8mQ_R$SelT?+}u|pN11f?;WXN&qL=!Pzvw6OuEF^m zh9~wDy7?*t2HlxgnOi!QE^6(tK~k!bvA^z`PxSD)_f8Kk`@6A8TMg6&?bTJ}Q{)T| z4zg~4m*GILL51uH=$|i7%(u(d-{l_=#aNhnEx>YtAp&bt-&_+497ctm&q+mUFb;}P zCG6g?oR20D0!(@)Ks!k^Isqc63r9`?M1#4oIupHe+rB!3|Gr~$`K2?vNWNZoP2Bol zap3{O+Z>PW{hNy-Ny+(|q$KY%9?mOpZi(L;H2k4zVFU}?apPOp{VXxQBg|6XDC+nC zg!BbPuZuS|m?MhF$Z<)OqEQ1Ad-WEc+uPgOCw^0VrGXw!hVI0}ygAHKK7otCjYC}q zr4x&tY-g}zyk*bJ9@>@Q<>e@xj{b?L64;!k1n6%lb@Im1D z9Z}5ljKUZGAG(Zh9>&bAjf9trRm2?kdRFE|o0QX-Ray-US8S>u?$_&jB}pnjje=~P zqB)Imf>!V!y-QB*UMUP_nA+S(*Y*4^YeY_wXk~uaufB$G`ed(m@v;5WklRTu0oQ@U zu_M=x2;jXdYEg4zCm$>#$`%2ZXxe_DS)aOmj1gyJGMI!jiT1mBfhKK=GneWgfNnWT zOmW?p4G$qKSbL-z7Js_?5gl`titc{2=R3}=YKi?%DGY2YOKTP%C=RUr+;*?a*{X_a zC*U`m$W_mOz8Ngt+nG?UWO?y!2WjJqV(s5u`tjqknFoYPnxJ83rtL(5@{bu#3i~A zNl?EHebc1XuadOBrkcviPgTpLY895b#qiv2sD-;H-%}~4F`zFt-PQ$v8oG9o`;YW0udR9Bg-$TIil%;B;TsDO8$53Zw>ninX8F{FL@tHi zSZ2=i`^GHpqNet(f;Q2qBVR3zv~UKP;U|uB&7j8?${(Z)nf9J&=NO;{0O*lXO^RFuvw=C|)z1&w5N%C~n!6Uw8fBdg|6uEiPC1Od<{bc{u;&)B1?a zK)A?R$UA&QK5suM#L+^z!1vpP4RkKi6-B|@IO;6W2B&gDG$G&hL)-4YBOzgFq&95g zVInH&>(O4Zb(LR(q`Hs~NAvOseC&tZ40KzAkQVfZqb_MT_Y!vcW=+*AtxU&pT*zLL zwc&N?4hKZCG1~T5h5YJpUbv@L(>?x+u%}v273Tu?S=H*#o0)3sd@Jm{(=#(Om0Brj zUrcv?indhYU*;jT#NPO;lLs59etSmT(d|;CaBX*_2B4 z@U(1BW8n~0jydIQhNS-K&yQSmwpKc;&*F<8i!T`(UW&GrQvW{z?hqA)+`_}j#1d2gw8M3QWn`YhfQg0f836b#uk1znZ)sK!;j{7w}I0C%{_s6 z5%Y@x<%?^9oq?bP)1}wnaHdvzdG}El&&SJHk!+{WqbAG-o~NEZ(9b}=_4 z|FF~wVzmIe%g5}E-HHNsMy%M zySuKal8=m(|)HBZB6sUiI-%P4*92|rq~s$G2BRyz23>F z{kf}oM@5`Nx4p3K)Ehn5Xm(v$(G>S=NAWdH-7?33#IY4ek)F6ls|6+Xe&F*F*sCYs zW}WVJ35?e`R400U|w0ZDc0Z-<6b=9ej&%z1B;e)6nl) zHLGcQ`C9olQ^ZO*>QRyyChTc6MD*dbLCyY8r2XN>n6ee{xdey?_0i`uIz90tnX&=) zgXy})K)@p>yF}-k4bEaP@dR{_#doQrx7;4(!TE-1CIE9Kjn_NcXQ_XN`f9wU1RV$5 z&m^dG8Os7^k569IQmqm{&C=Ohe~z{s%8t|4w5!_=bX@=SIuBm_ugkK2(>a-$_l3^R z&rTL5tc#;-9G2o;FOHU&Mb7`sEv%RKVaJf}J^n^GaVBRQz!$Is&@7#_a04T<)-fy( z1LyL=DD_&rby4`wFN|Gwjc07O6BW;fmPIS65}<$SOVgWqp^r^P@QQNxLD>~zzkeud zn9fLDDAkmink=rn?i@~kPFhlqr+#(0$UmvJ+hgL>D&SP>Jd}clGxa)5#4EQA99!-< z9vzL{vp=}E(c?Ihr}eJ6^={B~`0p!n_sS%^a_+-(u&1IJtV%MU29^uOb(L%2tAwa% zXI=5Z1~g&;GS*%sabnUn$^`wcEBh#EZocQW@xpE>GFoi&UIc*@@@zYGKfxC<~%VZZTZseG3Ry6aqVl>rd* zr1sawU;qs-2`F#q49)`Z6eq@YX+J@w%^0DF_BaP)UN!!8m*KID#E={ z6`DbU2%(z+I@Y7MQIxca#&s(9Ty1_S4;~(@C2e@I^9~m!10IQIm+RJCUgK_nm<3q| z$42dbaSA4dDYWqS`ONO>Tj}Ba6X;xZ%?OGe9qP@lJloJ%x_h)M{+KLNV#66*#gMj{ zo58BPvcuxg??RQhLG^6MMBUM!s`^=aBc0g+C^NIb^+9xWG^7)UNl=l_@`3x&tSaIo z^_|XYJGzzP<9>f0qh!Hw1 z20lwj0T59Vc%O#-`0-+Be=C^3mse&ChgnlOl-@wPEJVZO(I2YxQ$svQ>fN0!M+@S_ zfkR*q@X86?A3`M#Vkv3_y4`krgnJx_^@qv2LYFO0vhnizNL>ab2dG@2LLFmuaq-lL z_3@jV35{NaiSf|l)p6CQT6u4OtsSiTv!3gJaJpO>lCPl=5@=D}|EdL|m8I0WzSJ2b zb_W-I&XdcxE3W|PwjWT4k^>_+V=ymQ_uike{5v5RMrAc`(gNX~Xl1Q>J=4D-A@FE1 z-P^O$+h>HQ5}?*^Q~obE-I8YY;QxcQw~mUkZP&(86y-rM5dlFAKw4V5R2medOCK0u zXlaI25Ca6IyQFLAPEl!)p0hBVv&K7=$_PoH)FsJHU8;Ps3>bFWP_W z5tW``SsqhJ3U$6ZO`>3v;$3R~Y-?`;8@-lOv=vgDb;_)i#a*#-AMGS=S)Xz4LFH(d3ioQKCCMod3z7r_4Cv7X7AJXkPH9YFCaEaUqk6c7`s#h zJ&l-5V-ZeV9?O+3p`OR4vdAM?J;o!`CH}1>>8~pf2Im|q+N1efhlYI9M~_j5IKQqL?pPh4cs$QQ&x-;#EC1^1l`121c8;2x*JNUDG{|dMI*sdd-(-XL0Cz=EuYlIon z3FCu$qSJ9FLP@iQrE+5Wti7>t&E-J!@E+wH8gCI%==9r`|ECWovc%dFOG`^_3b&U8<{G#uTKSsJZ1SY8@# zWR>b)+ZzVCIK3ift4b3%7`nG&@Y0^U?*xp$OdjvcVyV(4hOz}VraEh{%CvdrEZgVd z$xMpE`#P$uW;xe>vsg!;n6s`~8(Q3{HeFMp+*vx$^>eHeNTbaOVIX=CrCh^N+csKO zx9?l`Oy0py4|k??51J71)>`?wF1u=`9_$seYTrA~qY_XDY;t|@^^c>JvU~XX3JJZV z|7KKmWvjy0?B(mv(uCKRY$IQA=ka9@dbLR_zFLu}h8We-r4e zM|<6-^lA1}DIkusXhqHya6iuqVbR#mQ`ab7;QKgQxgBAw{;6XF9r@hWW}I)Z)VelX zxA>8g)u`NWx$xkDpcfs2d@XM&#y{@wjo%E->gCk9`}E1hUqLa`*Mr}!O-C%cw6m=@ z>|$dK^FwCo32-W^Q#Jc%nz$Q?{HV{IXOPXnp&S~TCk@hM(gJ7{G&e`yYM_32c@#J< zVKQ_|J1*V~!ahcKC8gjBrrvR(aZk~GN$+^fMkGb|?Xw4c$EtKv2jj7|<=D130V#EM zgS(eK4<52t{+81!cFVt9rA`42osE!{?~-qV9`#@Q$r4W}9RBrZq^XlLGq-w~D5F0i zG+V1s5=!Jtl)RYiws;{$kNCqki-U#z#MnLAeKHD}F9GcZmaT&C-kuBYpwM^JBpuQ= zZx|vk@U*U494`c~qcH}QeE7%9RJ{BeTMI)=f4_Sh0P@O9MvWz;wkpE`>rD;kBk{jW zbNM%BEq8J)Rk-+6qDoDza|y~^cWBWA$>oc7VbQZALFbpl(%z7Dti2DDw&GxX>i0JE z63SerG*|B%-!zMwijLmv$lUiss3(D*mZ4kCCI+ov9{RO+me5C$;|;=V-!`4iP{~L- z;Pos4oUt1HaJzl?U1~{WZD(j_?RD#tPed<<1TCjm?Aki#<|x+>xC2`zWiKmGd>xOO z2|m9Vs%1CbT(YzVPusH^)EtE^O}CVFqU{vp!~fJjQ-BU541Ba?J(}Cj1t0F8Z>c?I z4?jFZ*3{0gZzvhAs*JuZcFd)h_R(ql8v0cvG=a_Z8oIZSSz~4tf}^U>EnPJsd|X{E zV&-o#TBR;}=@$+{yOLQ>L+Y#u21`VGvnuW`-T48urM~A z=CAP#tx{6H%|i|g$JEj+6owkIzMIt>SGkRQ9r(vwW(5NEGpPRFD%&(e4<0-yuU5W+ z?ky;{pC1|RnPs`xO<+Fi`vI3%zqa1+wYi{|IG(+F_ZGT$T0vl8u~31(dH;+EyPA1V zFig^v1Y=%g3X9s86;_>{8(hGkaoNQdLseRF?(@VM!+D{bRlonjZ*Eva%iz^IM*tdM z%q8o_h~Mh>`AM-b`;8Pi>#RS=<%!3;%Dx@Ht0x!_XGQoSW_+Z-=n}3^k~+rgQLQA} z2Bo9Ri|!Ez88$tWz~BsL6799s1Rg6)P>}%sho&hhH7y5%RJKNJZal?rx1LA31L-z? zqoxb!-aQ4hF*aBotR|rpHqNpZMobK_euGC z)s&BNbXzF<`RZ#7+{PVYD>eA1w2rR|++8nEmQ(15G6gd>0Zn|e^qbDp$wa(7kSNp7 zjxt_UNipMXmXQyOwyKtXB)a3{_S?sq5-qEeZ|3e|y}56^Cl{J2tDq19RcXn=eRNQ^ z&RUwqSb@ayQh%ev0vCv+5AMAcA|P-{$nm&lRADoaA7SB_a-f^*t3ZiV&RS(!Je(Gv%cB)YB4bVZgmg7E5L!&*oI%$LM;e?mo$uGKtftek~Kv(#yw}`Sn*GLh=AYJDt=egS%<39xZVaw`$pLxD=TVFfF za28khDE@eEhKZrd3^#uczxD_o>pds&2<_wAoV%E?d8*QG)C3or(w(ZY} zJ$q|pd_82We<$cw@~9-VS>}b6?*TkFq-H}*H-u+HkH8|3o~C2*4u$>Po%|GL`Sq8v z#veBdI|WC5WIa3L8QGN07(pP*x4~mMrX+g2tGD62`dPVUD3k=%zZ{-o^e)I&d3-oo z%P)8;S-qW%pHmvx4k5#py=Yj(%Z0oRW$HEt zQKq2$V8I)0*WT=I8DoXWkE3QRnvs!OL2-`aAY(K0==(AIW~Juiz@--*%4lN+Y)Otz zv-^2b=G*a}D&IY3t7@}(%9jH6+sKJT`oj+lNU{jekQqDe50{EPQR&`%ky>v1c_?S6 zg9~>|k`APO23JSMh{mPrc>Mt#9&SkuwYZLLEf` zxEWl`{_gIn481G_?UFwTIF+HFlZmcR*4Qsx7$|lvy-bI??zkpS`pI59)>`_DQij&& zD6M$X_ZFtf(B|2{4q?`kRJxxSXLP?)x@^%Zt#t?{ykA4v=~E_t7c@@EDG1Z;$F62z zwla?$(Ej$4xz2+{sH~nGRUK$Zep?%}o@Q%ynXu52W*K+HFwqFwH+d+f>Eu0o8DsN> z_!0#LBfO&c6KN7yj{X_|DbyqoOz!blzt;no43{yDg={z{GF$V#?V&R~Z!y5LNipe+ zo6avOz`5`i;Tpk7bz07HXUr8FsS)2jm8GHY@~|&g&zyE;u&{Au4_@3W$?Gcky`HnF z(iT=ctPE3NEbxno0Rb=)et(o2{tm+g01EwT7i3Y?m21IdxH&pxL_e<0J`z*U99> zPegK_NBFck+|FEDeAEmfX;E%Yo|4cKPdWU;iaQNpji(>QZ_N%8;;_HSnIlkPEE*lG z%7hu9nvV|Cpb>HeeuNN^Tl6Ly&WnY>zazHh?rRFBhAF*?S%QIjOE}AtPq3RJly7Ir z-M9(irb5>x=cMw8V-D6D>2Kbw_t#4qJ>}k&C#&Ny`l6Ahz;(xpJIC>a8<;IkCi$@k zy(B!-;!nv9TGXIF9IG$$F?JoFhOH{wt3+V{gW#AwSOWuQja=6AVh4F}hQv~*!hMZv zh+Z}R;wto61v?VDuRmU2An+1Cn@W;cFuJp;?|p}cQ2vbC4q1UkQ*ut8Jr#`*3j@RV zQ(BnQQkATg=UO)$Ds?N*tx)}pw<;t&6P&Do&K(17_ysw{_o&JH86oQxQieLgpQkiY zf1Hpci9LPW39DK0ds_?V_XqVeub=j_{P1e>a1ilFCb*I-Ns?z(K6SMm?w7-wTn#;B zL?!U)Iv_{oKF;Y{Wj2XCra$QRXQ16zBRY4D?(Tgm=Gpv&a9dSF zutX)-X!>|lsx366%6+p-+IHkcBi@a)k)2c&@I)3EvA&_9rph+8!GXwKK%M48ryP8J zYSd|*H;|WL{an^&K9LyM-h^Jf{pdO;*7u=c*(P8%cQ`=(#9|WXl8UGKn@zi@>6Gc7 ztJK1VFqwTdxRL~eKf(?5EHaq9_rE7+*%d+DH3)9zf^AJ+hB4OUd2k}IAfZ0BE@ufEr#k{7d(xVSl^rsa|D-RduNP^7yzZ_^EhwhN(Yi#Hm5ZPbo_C`Iji?tkSMGqA+O$>afvIP z?T&>Xv8CR`sGfpBe;Do1dwHE#&Q3RQbH$ZGjCuia4_v!vHhB%~DIIAlYDWAkC$l^% zE!?qj;9aq*o95neBd592G3Uw5Mz&g7GdePEVXL1;p74&lB5(y9Xx%&d9UV4AEj|dw z>XpoFR@N6*ciV0X44;%uBiS6QGJm=qvCaQs zhjZnyVkZAtso%`edg*!L{)KYg6~nvNsIH)|O^RTBwY7C5-dGH+%S#o(f=8b|CnA;m z>{Q{6P%D$l(Ivj({jaVrR-%Zl1A$F8a}R+$Av3k4bJV@(uUy(o3c7*?I}@bS`Y(%; zQWdd$Qp#d0=@8i&S-dR1WIRn1FxSa9o40w&AU;c{bot`e;E$GEshQ4pP2I6A9qmF# z_W}#Aq~{udXS4|xSB3<9n^<|E75>W|m$fmwiLoC4+z0AxzeOvJxM8`$t$Cif;cmO} zS=S%z8X!yl%xe~rJls?%suwrvc+@v1OGs8}>3|va+^cav?N?LbinjUfm(#FOz2N%8 z@^?Au4X|8Ww92ZHBwaa7s@m}rEg6>XHyQjPJ09GTwNt{b3~M5Hv1zPeZLV^uUy9fQ zr*&n+pydi)baA`5(Q7nSu)4-%Whb|L0pn0E_+gntFIQ>C?h z{0?lZR=F)|dqGJC)t9#6$}S(jkt-jbgLUb^b_)EGb+mM(*>;#YCE|J*$G@_-q?p$0 zve;R{8rxwP>sVf)zp`UKUdTSaArLzbr|3X1c7|tfY;pg`lo?yiSe9N%P3mxGp?>F# zNU67b?bwP`|D4{NYGh6!U3EUE+0_+ugZgEOd!ECrTg`UhjBQmQy}?2cS~!rc(`BYT z+G${8dmw-1+cJ0O+$QhJ1acXKMKGI9qU^$frz?d!JHvKNzIrgke4Kh~XdrgEW~^|h zW_83Qt})Wxv%>gu`9nv%esqZgGomiDRw z2@T>e$3EJLhr4`XclzoO{}m2!j@5GAS?+{gLfn+8=c=Zh&Ryq{upfXx-G>|U{`Uaez!i}_>VlZtL?yqN)3f+U?@fYXJ^R3guf`EA|2 zXA;|cyqaB0xm$Ru2{Um0N;P)ZEB$1$Bk5T+9&S=*xP!$WM%}pi-Z!~oQC#m#@qR3A zAg23*Od#pLomHdHP~P)~iwlL`Wv*j9thmnCwB`cDj$&qqM-_?lb?b5Vqg2_06LFk* zUcZ$I-9tY`qL;akE1kHl^#>iM``6*z1cXVR&s$V$-!#%w2IHl5tBb$QX+`j0vX;^r zol^h7a_zH37>$O!@_pVP1&LwUV8tA1s+<(dI-0si z%X8;R+=TpKe(C0^(T!@16~*v-5D+kq4!vp|KUy0bzPng)~`n}4UYw%fsDB&}3xm^Q+1n(p)Fbu)*m3Al~_#6EZ3UJeFv-%Y#e zL(A`16_h(aR^c2xb{aa70EC1lrcDC*x#kKQT|MQyb`@L^N}pebSkC>#e)C>>pDqy+ zcV+16m6GA4+-|bC#rBd@-3fIY561gtO_dH}d-nb!b1PmiVEg7)OwfgLjbFv|n>I+e z-=GRI-JKb9P%TXnT^VHS&h0LE9<<}tyPfG3XaDiV(sX3^QGU_Z`@Y3NRCnK$uT$N# zGoN?V-b5d!H@?M6Y+C-LYO5OC*Y_-;V0m1Tvm}1JE$1;b)kb?>u-aV@7~7IVq#czk z$RNc`UbY-xk^hSM$Q_{Ry!=mbk~X*LHnIF#X~tkr$_V zsYT}sEYm}yqPEA~&9()*aYx571+6s&{p}q*f6lup@jC)}0x}=*yGYf*%;2lPFZ-CvP zLez3DNh5EA)=s8@&~-|nb1AL0$MR?UG6gl!;L>Fam^#o_HCz`7vv7K}vNN4U=4mg0 zaWkV(TR99;EmeutI5x}O7&^3^>Aie``q`Z8c8y%@@@?484=W5rye|sr>ngpprlWc_ z=bE?Q<48}sr7q4VT*R+aN*f$EQI3z+x2|Aw*n8$NGjN!`UuP#apFdfCV`=w!g>Gl( zyS`M7yv^j{(S5s&(yB#eg4a@>=j}yTEXs5HQ`Q^X-f>xv832OlNGURHCyYqhq`>4sz+j^6~S*>6S#!o+R@Tckk!KmU>@wJqNb$aBZknLCM8#)eswD296{lA-QYDt7 zp+h*gUZhXT=;rXLJJ>)d*5g;tmYE-er?=VgTFzp`Ge?Z_kcez)q&o)&dm?seC}qR7 zCU-Q#?X+=)Vi8HS6@{e(N+-{y-*_BL#JYVr&*eZmVyU;(k^*}V@Sv}4& z<9P~=SW6+k{syWBJFT`D|A1GdW_qX=1CyDKayo*L*~<;`A?8S@R? z+rRagLb`QX`cUt;xfrH|r+W&`uY4FY%h#*Dwd)3xTr!>1W#yI56s!Gb^CvE7fBDit zFCf6XKLdt!O}{rxbRzJ*o+->cK50 zw%XLqWxE>;RF>fs5%!OE9imb#Pc3hy#oJ{(&C>o>6g5=xqOdIr zE4@T0tg`#U86vk#M;{le{ zIs5u5j#&Lh(n))P%)G zA4Y^zdB^U6gvLh6RxawQP|f^b_UER2vF@1;W(i3X+*6G&3_b<`Z*EaKHWgSrZ+dy1 zmUSgN3FFhZU`!(i3?W7Nni$g^4w^KX%HX-Gp{CvQbndQrh5?1O)vr^`*u$!KTbNR8nb2jvT*K(lx7~JnF0CovlOyQ- z7$wsvX6%lb+ty3_WyhShsJ#y(m>uzQ{2t3xkbY@%AMZxHX#a#!cbKJ0Ax3ROQz~P4 z1tabewVH9xd6x;l>8*ik3^+CCa;RrJ+8f@2RrL&4>8PdtKpjgb!ci$h>$2!F=h~=< zOLeeAlL$)5hyc|M3c2?Nri-2i3WHYel{9 zc6y16xBP0=Z`b#bTe#Q%aB**6sa_meZmjCL)>ftMZH0x|@_r7>0Q6*nxnU)D*B3G9 zN}KsI1-!!uMaY+QmbAn4;at@;C5EzM2ehdJKs{@(4a}wZ&CbsOA$k-g4=#vnTtmv~k;k6lSwZ zi}~qYj>g_xCYUBfR&5N~1qFpMSMDrd9=+`9v=Xdf5Q`VAQ+T`b2u6XD*tu}4W` zGZt!r*u8et;Cr?8xtVdf?#s~;{og-C)75syarBQ2C}ZrGSkp?aZ*R-y?-BNv%*o+(r7`O${anEgw_o zmg|0Z-^xGdxn_wT`00#yYoji3aIM;``NbT$(HL+0VrL`eSdU-+_Dpf07)E}XW;1!@ z#l9jKmk4wxW$DLi8*2p$yTxmb8Q6Ke2zZ0SB|jmYx$g#(eacgSQe0tsPE1S;|7*3y z;WqSl>OCM6DF+l+EUI0z>%KH$YQ_|M{Vm&i*<~OdGVdnf*Mo+-uW{>y=fP7&9)I6ef{&|ACnkWh+#F;Vn zI9X{0?5K|zXo=yA#v-oUJQB0XvcX1^O`lRz!5&Sf8ndPFGrJSGAiw=M_{fy->0Gg`~axZ&$p=2)Otq026Dy>2{zDm{l49pV3&C zg_P3m^_2>;;T#P@qNM;#Gkv%WNS_sEx0XuBM23VX=x?~FMo`44aJVAFdVL;9d{>L zyw*qhFCJGfn`?m)4naaXaX;t#P5FvL5`6^)e@eHqynl`N9mwBQ@Pu3wbt~hs9veCJ zw_rr2FFCk{u2Zt|SPnfcu^wj!j$aOOdJTS>8iFa_qpM+v!**F^&TYun7JMaBi+f>K zHVG_0PPRNiJT1aLYc%!b`ub=j%1hlL#fF$Tlbi09sn8S06A;vKHtW8A3>|guscvR@ z?^K{TH=JZ44V&-BdOmoI()n#MRMhPAWKWZjgtT^3f2KRy@kP(FKjznInqxUE(QvJ& zryad&N@7?C*1P77q+dzWEwKu)jV7Qw{%5N2nuZMR#6_Av-5za1votT&^jodJ5@gH1o@^+Ga@Su!1-?$@+qub=@=FYfX;n(k9n^wV1<`L<t!JO(Op?%gj6Z7TaT!p0lW6hdRy@Mrj}51(9xN#568IRb^;a<8=6lNgv%e@ z_b;K=dmS9 z6fJeqaiR5uD`9G2mr`yL&raSF9;_hHQ>L%;36-2CA72yLw4qEIk{=%s?pGOWUO~pL zUo~RlHMR2Bd$_k>f2c>Yk={pugfruDe#|+~y_&Dj8m7LWm9`;=9rv-%YCBwlgtuLEa+Snp&M7;5RVsW%>*uhG!h_ux^nn?4%h#F% zj`!h58k^Xc{U^Za&+GqYN&f%*n%BuN@RNsmQ}AG+iOKPzJcMtJFQMmq$5_KprvKM%%~J1&tCoj_Juoo751DP^P9|%KTc74xQH?w zAh6R5aj(-r<%))>jPOPqH?p5JK_uH0>^-4PO-;8`yYpZkuCxwjLd_ht2e&2~1H&f* zgp}ei^GpbOPh3z$-!FAumOkWjS#mO$a~IEgqsNt3R1YRJ?6U7+$nY(L*oGl zOt)!t-_v}u^F#eRaGQAi3`qOQFxRr(9y9^v;}QxChlH4TN*=b@Ix^i#K5h4g;oU6rEf{ z3Vvgz6&xoN%TlmslGw1K2dh#UPd@uXIFvH;({wsy+7iNHpbs386qsot!8|b>ON%ztVoyz!y1F63?>k$!RWE54zs^hI)&I7AdHi56b7uY!+Kx^(+xG< zcSfBUny6raM4VA%o={Q|C196Ic}`!)MlJJcv%hP~C~2`y(jLs-6n>_(_FKJy17s&G zhxtW96QW+9(t&YvdGdtX)C27ObnlY=${ldZ33!h3W(02U9#zPw9j0?%e4~!vvN+6{ zP8Mv+;PAXY|C%~YZG@|cwZGYR!Vp%N$8{DmQ!8W9q%l?AjGhu9JDYLc7vaGnNVrco z_Gel`@V^zD=+|@nCzkDfKmejxBR>55l zvJnaeiFpy_{W{LhyU#maTibfjUrs8)fnLb`qN=JI=#2ZP5d4pjN-x*Q$jAbd718pk z+&91hUt*}z-dkaIfkl`h0@uG)f*N+DTx+=tGg`i1z59kWL4$V3x2oXLd`O0zBv^*2 zG)4G-v$^CQTXH$H!v7Xg+J{^%81>}hxLUXt@Q?F)f7}K z1DAP+K>>OLIEn;`Hx+gpRPNn!P&nVoehG}V{%53xU3O1O&T|V2j?PAbW_b+`A?te! zh20R}B=g{b58Y-g*dw}Nb{Du7*G?ed0}MR|E{ zWuF$h;{UiN9r=&LOMKq>`-h~(duT`Tpl^li+RAR*9i_U%jh*6gxMw@&GJ}uhV-yVN z2a@?+5I7!VNNWmxl3$hurTRUw`p4AMf9z2BV7 zhO&On23RQ$E*gKB<>?Y4+~XydLdhVs5DnMKwE%knug}uQHdii#_O@e3DUn`X{9g&5 zw)AQ0T6}c#W7UA8*D?$rm?KFb7y+jUqzo5AUieW4#44$*-v!Z&W*u+|3& zd=~g(kNP*K)DJ;LlWaTH!eG&jxpe7LGAtU?Td3ABS+KuES|d;fF>O?I6h3btf(1AL z*zzq}Cb9I$*t#uV%s{);%JNIL!$O}d%o-KXrq++9Dn=*6aipe%b$o99p_9kf{Qj>| zgLh|wk}V>CoVV3?#?uTIxkMLPbUm=D+Ww332q#qFvZ3y>TVFZloo^sJE@btw>xRco zYJSVmXzMZ+v!~cw=f(JRL8Hv3OjfQ8n}w;fOFdC!kyTpATceOP@qcE*F(+9`ygogdz%dYv}tiXsPus#pY8;9s^N zrQ(r6bT{UG*`MM)kF>&AwQfzdhKGYDq!m&Zj6gnsD>P9?BC^1gNa9hzbtb#%HqK(C z&IbX9XRBeL2m>;de@#MxXhUw+9&SeyR|(qBKAWex_sr{$;HUmHg$$62rhq^YY!dI@ z*8@6+_~?&W_nDYagq7sJe|t6@yl7?Hsd@_<6hfD)u2Np~&=k+~>}hY=^VCog5`%)l z?UZKy^99(tym(Gk0Q~|5jwPvTAtgr0RDTKimhk{-Li&fuYeP+Z6qb>EAyk@tQFJfJ37##kfC3<$GAaZG^YQ3zx;Xh8O+ zT;U@BxLbEoAMRN;$Y*e{FJ!3aL_zSPB*H5R6x~brhvdvt=n4hBIHJFyrsiU+4rA9d zOYItRt3Go@m^9x07>bkqI$sUu_1b4Gg%0s#S-%Fg0}y(^6<#;j69frfXckt>jFgwJ zh)6M>t3M^;M^|`^aCvf#QUe7jzqv=6Ae-tw{TQve1$|Fcp9Ym&Z?4|(DTN_HIq!3p z5=Q+Qt^EniOiay16}TS?{aJ~<$gq8*1rYPRObBxs)Vr#Yt$CA^lQXq70asBhluTqm zl{B~hUWejK(pR#Q4pCTr8&B4(!FjENi*kdzCZt3cXXOs;3qHNG?p#> z(Ii~z@K%mm-Q|h}cA#0%#PC^0>0;(mjQw-i^(rEA{is5iNUK6RZU8k0nEf@x-S9($vCpI#(H$diKJG>l_adE=A`pD7vz{*P>2k-7(-yq zI_$O)eJZM{3Y=^eU#i$EEzaooyHxAUnEsp;a0f098ijHRNHJ<)iX9`nu!09e914er z=nGEp`wQPAQn&fnbCoS#)sP(HeCE;iUvHYk5bXS?>Fv6|$aKyScuD?ds8SiriA5BM zFzu#;ogex5-`X($-+9G9?>4ox0I3E_0Vyw}gp^lS4$sTGhh%sn{GRFZwl;LS?cWyb_+SB za={FW3XiuC@)2BR#a(-(g@$JfS(ewg`VHh~I%22bzz`^_{}hUU%=t5JHn4W&-;mK5!5WSO zeM;d7vQom4f?65#M|QyFeL;&H?t31g*>D#mjFUmWI^6 z1D7VQP`E$mIe6D13kqa$j`X-fBu#5^zdlRj4bLiFr%{LLEvVvkRa8_aVL|8Z2C`e! zGbKWHIMxKZu0O0Qqsjv|-Ll6FN-=iaXMTD=sqz<8y9NEVrd_wXtDg`u zTxtr=c&g7tGIJ-%jm+EZcC4#;GMhD&<#@BdPFGQMUpMGijzhYfiLPMKfz> zz>|~{A%cLC5XbQxAk}nX16QT6yD%DeCquh9b1o@ekC2zue5feH#SQMGD0KtIIOind zY{=*hUF0m@)UV`-gZ(L$1tFx^@c0NAV9Im(k;*wI>10=!a>JYR{G$iLq?B9_Ag2iP zbrZ-JmLyDvTH%H(yjQyEm=I!?=Fs2rIv6yY;8>XBiK5o7Io_rMv0--4Z4=UJdtp|+ zIA47#544j6X{Egj2HQQywb*-QE8o|6uY$ePK%*&1rc~qi%R5Xu3`%ei0lwiB79V97 z9Od502sWRVL>^n($t%y$v!7oESMRuW_&zvG^PkJiNh2v1*A;vOvRx3IB|{!o@r$4J z__vI2xawb^V1tsKph^Q`{%_A&Dx0~Cr)XCzpw&?Sl?1j?F<4Z z<7OO+1+^8m1pyT9#V;nVx|r=Q1O-Liig)AGA1bk8-KP zW{qsRJ(}J98)*>}&LgE_%^6EN@~{7zq8Fg=jbzZzRLhvOD<^Lahdff7&Dk3Pf5(W0 z&{D+*U10gx^ptUKBej!!o5KFz^07bB_5?f3VCn!W@u>V9tD0YO=)R(t!kYs&OnR9i z!uVPD>Ean*(jai3RrT0KM-^E)B*nO3gj{C3W|DOW3)a8>JQ59;Utob7sD|~|Z|3^5 z@-i|_E$bDRK6tpmYHf3TvebH9G%@$*f2yBg@N(*#^rJ_V5V{Q zkiPexUrMr-mO~GXt;=oMyJ&+f51A4}zgzGgJ^3ud=#6OSb~ZSeUld zkBvvd^{mQ%5!s~(bS5ZxLcAV!DoCDPz@m!16D=&oZtyaKwAJnK9K7LmQsg1Na*x!V zM`nJgIBRohSOXyvLVLd^kmDPGc+MEiyfE{aF;?otd7J$gQ_fkajaU%H4dPr z*4?!U#e0s3&+SinsT@Sanve;IhwV(PQxy6PAWGwI8y_O4q1yil+r&uEPW_o1O+aQ* zpyTsAa^r4)d-1lyC{@)c01hA+yR)$|R_!{KnUxy^u+8J-@W{`6iT2A@g}RtmbTEZkLe1f57Y@RT{=5#uVKP4a77g2Z3$#HY}WC|W9u zY=sb)^Vcx|EgPKro5H#n7>B0evOCUfluz4@oxAXQ# zs6U44ohLDbNCoQjjf*+q7X4L72eVv0LmMgTUJ;oJbAnWd9%ZpZgxHyqtyj7Ib_@2t z)>B49TxP$%yk19itstRP@XJJYwqmnhBVduQLoXrw?h?HMDKgI}vK;P%@nHk)0 z%Wfq)iHw$N;P1!L1Y;!*0QCyopnF*dVplV4!)+TFRiMNgUvQS!F-A->88$J9W$4>p z;DiH85mZ)oALhWJ#8LLw*@hjSNU<$trsq~J3l7$}S0ea+4`M$qtEkBPs~0wW1!v%M z*`RUB?&>3hZOUH*pv`5fxy~UudBSYK=Hvlu!)p=d!u5&nFWw4uJRJjaVB&mxEh?Md zY_0ApQLsh@b#~UG8&wROR>4<=ei)fSR^1qmK*5`1sU+RdcCb&Y;yMktycBCr`}niX zho2nZDIf@N>xDL04n9x(7h0uqD~RvW%YS480;e^HraHKP5<`R@1!?i(HLbY7L&f0k zjiXey0HkWK>w4rx{%5&3-BjtIBJoC}h1+7CLb%-JNRt?;)Gt*0p-m&7{r`?p{onhE zyW)`HTaF=xx34>X2wVEydlxTWL|jl?kp~9c5<+3@)dcDFY)~@>Y%UINyq)9b=5BXa zR}b@f{aV)9xhy#~RnEvL*=12NO$V63Jy{x^3fACSug0vNeYdNt%Q%uMY!JU>sF#|u z@HQ|o9C*pZlJUz5L*|+Aot1qy=MSGk}E) z;J(|qFCd^bRAOl!mQjk#Lf>fYMQPW#m7k*(zVCXpxoIz9I$VXS9C6yYV&cWluKETi9xmM?A~8y7}0y_-6k|&wsG|H-Dhwbl zhmd{(oQvyyo(=7_;KI~OdX<3G3k{Miu;8DW&G+R*BlLcc{U!dnf&9-Su!g=V?vMoA zlMOhoriA^yrnd2!=G6A#n2Eo=d8~1dYxazj1RS@QltVzS)Jz_(dv_(^=qXS<)F79L z-jhw6nVQcE>V^wcUtWu`p7yp*gFPaT+PdlqKndtC3`;{h5MbY8SvopxHv~w26JQV~ z+K=ZB%V*-uwWa1dVO=XPGVPKSbeK2NeuC8cav^tb0t{VU1-nsdiaPW(Qw6Q;!CAQ# z%&u@7fEryHSxY^}0Q4pnC!-ZM^-xk!;Q19sWCZtEwJxkK1(w5n2&6&BgRUD$(z@)j z{LN{yJdto}XK*8B_;hTtTZad#E*4i6Bbkj(k9RhbrrK<=S88Ra`m9_ z>3mP_Ked=h5-)^o-Mx?cd>d*ewLduHKU>UF3on@U;-{mnAvb=7zrF#wH%1`SCYIw> zYTR79ame1r<-TW+c=M#dQuXFE$b@sY2Fc!TlK-<5gea{ZjJZ`MRdC62>4xr+d+g=%8vBQwwDB4nm=2THbl;*QsLo$FavM>*xtb0kw z<+5&+@iZd{;R6(X&jciK1hA7-2bgXeT7 z>kyHziQmoItehZnna!VpshsWlK*OOMd$`!qNhnxRa$$5P&pQmX2BEv1R7uFs4)IkM z9197MS1UAD?4~k<{vR9@NiS2m1oEP=bZL4A_H_=VdG;~PuGVQ?>Kcv-PE+`bT&aZu zjl^y$dl3wFV}3xBN$4{`+A<|4Tr}IL+(>kdWy8kcn9RMKtBGD z&uG6O^+O_3wf_0|?*YpH@iniLA^#*@Pm5lIF~_v%?b}kwPh9`gTAmn!2SnkpuV9=$YfdFo1??;0!~itn-Do;+aWO11WBbF2>jsV zbE71mgMRkcyB{XPUl=ti~XRV?wIzkpD^b?0aV_`O$^R1$94iazvd`T#) zqO>t^FMcQI1ztrZ#i%G@7peLQ?!Mx)8e#^o z&=8Y2=f}u^1YwbRa_HW28uyTLe7n{E>KoTmX}RsnD1T(#e9jZYlot(`Cm{lhguoet zt|!v^?W1|oXZY@y-{(`C#O>4S z3AGA+77HD=s*rPB9=bh#xav1DvM)!ub!4KFgHaQhC~}0UpanQ|gn_bFo}iAInucl0 z@bjG@Sf@0N)x$#G`hQGf=q^t|8Oz=|bRk3_P=dRECneoVQS&oiZ+pu>70F2Z@uA;w zXfB0)5D{Zf=!@|~qg1UL+hY65RoK=FEAW!F<}>W&>8*n*1xDGddnS>c1m^v5?w4r4 zH?!DeGl4ZN>F=Zz`>%=1KH^V8Bd)S^&7na+oI7b%!*V=%2U|{OkQ8 z;jtx>W=OU$m9fd(+7q!O_D1s9AXsFYJa{?NlnEyUn20*egFRbr7Jg}ptk)9+YJ+JH>|gB ztHPRw+TT6}2w~9u@tHe?(k+OUe1SPkBEIQmXI&v^_li z6x`BEt~-squ!Y6gkGev**z?!}xE4aJ*9U=xpb*Wch-5%B<-S)TVZH9UIHS6o(5+j)-_i{g?q}!)%q-NrGMBWto+TW32*oyxZ#Q>Y505uY z#jL|-I$wr;`ZXfGyF23IInBkrRoKxad%t9MhVE}rX9mKr&_v$Jk$yBw@r2ZeXBVMd zK+u^q6_4ppZf9*U6LW)w-WJ%>a?t;y0D=b$;Pl3n6JY1le#=0K1Sdmpm0Vqcjn|G} ze27&tROl}|BSgaKj#w6?&O=XuyN^D%I|R<6V4|2x9wFKnC3<{dhZt>Xg^hU};Z4vZ zW%;byMKVYZ1|pSi!_)#NP1P^g0K~#6e%DF&zKw182rS<9=gJfGtGynB?p z=Jl6cHbw)1XxfXb8I|QaNO79XvSyI`Ow<_Nxe%;e%UWoB%oHma$G}m|r+t-8pMjds z^3&yLo+kb82enrtNeC=PiqwYUMpS62w}aYt56@?)lqZ!B>8y;=zC}LpLKSg!PG} zujqcJf>Wx{#%9SW&HD^XBgHy#&MkQV_DBxIfDkX-7TP~fbsOxXKZUVHwftm!)P(Ie zmL29}L+x4Hp+=m7C&AtH6Zpc!R{KAg3A{siM(nn#N!us(nK&kcfPIPYo?=?yqjo1J z26gxFKrxn6NA$XoQi-KsICF=I&1BYR)cZmU(sFP$k@hld?^{}XlPj6-In~Qig^^cM z3VST^A9&}Lor+DrS@85x@JmJT8{k(S#2tj%C|z<|9C$974tWw$7iN&I0YTk~)&5pk zP01+}bXvAZMKU&sop_!?PFq8+Vl-dmRZT!T0s#zBSI6qJF~-=u(we3;f%GUIbn3$h zB3)>pxEFFX*M7)m9qEFx<|520E1*QsK&0F-BM>t)^q6zS?R*|KJlI_u{Qnqx>#(TW zwqIBTK}0~|rV)^oE~O+S1nKT>K|;E_8>FSXyN6~#8l=0V8M>Q&;r%{)zwfd4aeUwU z!&nT%Fte^}o#(Gk4===UyKOvxnOEEl1f7DUkZj?a!s9k zlajxi-uX}eu*jwznWe}B;V(!1Zo=Y?6WR2ixox?~uEnAH1r+f5#~M*;KJ{q!409eyb5f2*itte&lSCDiE0zV?v; zNe0tn0Qt>Zs#=w6f}Eo}33wL6fduBq0V0q2XOBG;W&Y!E>&IZn5B$-HTKF28fE}T% zAKe7Si_%z!n7UEen~#$SLbKO}0J<9msduc>Ku7R5U+@zIu(fM|ZyQF73CCVg!p}A- zQA=u3R&!le$x90X6CPPga_~PCGtN2e+PX6;u$Og#tPU|O+JrcgF{6Pv@Dlr*K~!~) zSX^YgbdyfYWrXV<56GZM0&a-j0ibHLn`t&qi=oz3dz@TmBj;X;>B<<5qfI~$V>f$T zJv|E-Je2VJ4PD6`9(+g2iL4il1u6wiPr5UIp@zr1-C!D4V#;hA7Yq1RZd`#hMzJ55 z+}n485vcL~47}Q<+Y_3gMgYTL_x-LLey+Q^i*b7N1c%UW`^v>8zcNc2FI5!ey}kTa zppub{9HVW^o5v@Wx{shpm4vOaY0tfIdy_{`nU$H9%;y{XYFTCcw(}Q8sfXs#|J^=- zkIV!{yH}XUKjsMszxF6Xvs(T27_Z=l$O6IOVuczJGk{N(z%)0ys?zZcxz!d`MdG*xBSVrofV z>Q)wO*2$7W$;g?HJh?A>SdX^^?*^&?#*qa?F`wmsi9TcdgkurbouJ!V6P67GRdP` zKjk+y3rmM=)d78s~-L}jc zRrbxuB@-x&tz>met{+OGtOmrsOw+#}!ddQa*8a^{d5tsOk*)o+SvKC|zx8LIn@e?m zwK)UIa3Dc$*6lkO{7y;!lgqA91F!%h5=E=>CEBfvwwU8GqEik~RQ3cV1$^ET)v8~i z4in|Nf~=ozKvu}tr*!5Yau#j@5^Sh7c2rT_6~}7V;}}7no2n=^ z+MBa(HQM8hPb$x9%D}Ug9D(nTq)KxgEf!Sw-sXDov$kdQs3RtoyxL@JBR|v!2$r84 zI^TX%QG_CAY}{9TlCti|r9_R07i}Ggbayz9vxBjVr>K1}n4ITJ`CjCoJYDS-dbg|Y zRPI5H9PxaZ=%4H4D(T0$hyvFORz6_*=a11L^Nvn!KzcbOn4IQPEsKg$gDeoDnOYvL zQtOT)?Yw&i-4)%@y$-1>XFvJ${NHUm^;{neCzD&V{V~xTTDQ2JTu!MTCpSyVXw*uW zTp1s|*o4xIHpnAg>Fg?7iyvso0fpoPuZL>x+1gS_oEUT!#+f$aKU!@C@ARN=41tx| zw!p^>-D{Z7S9ewSU9S#%WP{;_L#?f`*|{e(n>GRZvF`UB=X-PU>vt5I4?l}ctaL|P ztL2mr|68o(qoW`UIcHUJ`#pqi7_Rq2LSot#vOBAnz+Zw)eX<~p_}j*3^dhJ{w)^W} z5mnwf{(CB@{|c1A;2_EVE9n0J@h>Y>i^qH{R#y2}&K=Mzf;W%-;=epGumGS4NCVs* zJWcxLquzuBy`$qVQt+ug)9vnIwh@y955|YKoR-*p09CVJxNuARoT*MjL4n0(D`IN1Za%_fPQS6vp(nte=E0hf>SE(%qGXe-^jlZT|@$}{`H96+6(Zb zt?@Brc+RKw!A_3^@bN-RDoRQ{AnDjFriB{^!l&;*w*Y%WOHPge^x2<)GUXK>UJ!^Q z*@@F|cX#JPh+a0mzrEU;siJCXYI;RT7)mMmvu$Zf8|l%IOCJ{OerIx3Q*`v38`uDfQeoZ zEG0lxXvHNbhXR^2*}=`^&HCzU7V+lZUdcnQbXqvbnBKuwpj9ev1J6EEq|Er{yC}H5 zfl!3&t)*5zKw0Dn;&0bpfI8*&WD zeNgZ|f=LYR7f!`b>oDOjuWuu>*L6q-iLbePKHR6^1kl!#{O9E<(rZE#>V5+6bo2fW z4i;99x?qYVT7FK=#B_ze-M2y@_Qo6LxgZ1uYpGx!Q2b4ve~r9j>Wp@^#<_<^kgx}Lj&`LSRuH}o3N~NCv0-c zf;)|6kJhWr1$57zpqNoca^tt;-~uPbpnH@lA&IhJ$Iizw_=0wCtfb_4Sm2$32CcNT zw40`yoTz!FEm?K+p}3lrC_BGXarNPkICX1HZ(66&`M6T=^Y*zekmeKC*7m6xh^B08 zRg{u7hUk2)tVEw9BO|azuEr;|iP7+2Pjo(hut2$~fUZ|Q)XsUps)pxVrp?pT88|c; zW`y_CO4=%K*WW^vLXE1flKnQ-?Mwb&KS^0x`IPvQ62G?TP0GPR81~))e^sr+_PTuo z70WYXY5H-?K+(xx?8qJSUx*92a98-T>If*G}y>|pYG7LLx*Yk z;sXJb<1|M-1_#l4jw&ec6+H$9xcu!bWRaIef!nQkIgO1XAhb&w}=h6SJ=tT){GN*`ke8gR| zC~^D7PJuMy+7YeNT@d4jM|>T9!>!}Ft5A36TWCzI!w;b2znVV0zy`|%H8(dG29v9$ z9^>_+?oU>ZmKG9H{*^)2&7^mUo6K>@u0)>_QM%x=y{`VS5JW#`uXhyDnjhJpuC9{h@6Xz-|hMP>oeq}OBHX>CZ zYk)><=uGY3ITI`~Xysn;Si7A}zWMS+qT{@z6;>`5Cw+5?(%DV5=4V>@kjM)|F8p=S+Brkrg~XAbTa`adA}hr3Giq$SUq zqBgRi9LW$UsIZcUM^iPNb!^A(qYU@@u*IQS?h`3*# z$|Bp?it9yeB2~Xot%l*7D<0Z(Z%`WVCG8P)qk3qAKJ|-R4nnOvwKO> z4KMj~-d9`cKK#{LvLVzI)I+Z>cn=fMGFp;bU(tG~XA#@z2YejoYX3UBqtu*MFtvq< zm7cSbqbx#7(#BZFT{D|5;OO)c@16~4&ioeHC%(<2<&C=!w0Mw1 z;1b{mikf4!?JtR}il_zg@$qOp7Oi*oj|$~y9n?-}s?tRI2c*>PssMF3vs1NL!NtKD zG^*L2J)0<+ddq24riCq6Ai6Iq-bI7UZe6a0t0- zRA`Jz)r3PkyiMyhnrNQ=_E}5v!yCD=Q7bM6l;q@8`mHY-XcS*U z#|wy80uEZom-1e$tiQr~6F{*;Zqgm6c)ixw)$=MOa7!#Q)r*K||4VRKTX))E_nrfH zpi85D`pFWFf?Dj5z+m|?G66>;yiVRiBeTmdCKs1&AO$~ z0#;s;I8FlX!ettIqqN2Q_!Ar)T#uce5lofS36baMv;ke>?(IQ6@>{M;ZXEWvVhL))cA-1F-Y9Zg(K6X!Mr&hl73tx~gn<%UADAN@0qVgFv}kUv z;BX%R;)O7f?3V$k`>pW;%t!NqL^8+glg(ieG04hfzpLWbilEABJk$xaaIbN3dz4gh zdb_rs+9)||eWIX3IiAU-%-g!5P!MW46#K#+LK91qpT7Nm(a8h9wpcw^TIlprmF827 zXP@}9q@W|K!;>?9J%MC*jmp{D&naKNkQlNhGQ0MOo8C~qbl%1zprN()I9OHu9#eMQw}2WYn`i53xBHt} z^Yw@8L4I7WV@)0ly=JZtCzG*9*n$GyM^lAePB+=p)0a)yZC+k*9OZlh+I&{WHtiS;>iaS3ZjgvlHBRqXWP70n`rMY=`2sLxhkd8@1*)92_q+7vR)rkb_gi7mjeniltThq$gKd_*Po@yUH67gJ51S@+|9R87Mi531+L?`r!js zyK`u*-u+?Xy|blcVU>7Jj}xrE?~VYZeZSB{Hqw;_O?%jqtdys;rXhB;_gXh0`)mxY zXQT`p|6oK%Gd~nXoLagP)Mzp?vahMUKr7|7?R25}9xM<>6DxBep;KcB!){_(_ItYi zLqFJeO!gYmJSy~yKa?}Ju~>61?t84o_8vs%p~Z)z$6?HxYX%*G(Jk11Kj6ERt)kOO7IU4+Cx*&W1><&TL8z#I7Iswgo0- zbH$k91<~+{Sbf@WPUpTs*E!cK71FqB#o8JPb*df#ePa(He| z{=q)C;O>(r{0-dAL4yK*ebHS+L`0p1+Z+ViJYBaJ(KM-Gw7poi9}yHbwkp-DzZ-4W zb5I|{54r2Ltlp(|L2eFhJ@3ad&58Spdq{p|BPj!~Y(xsh zMZa6+Qb>sOZ+#}QhP5LW(V*So(A`ogW!wm@$J$ire!DEp*c;_7lCK}?1{mY^*e-(v z+^sXysnReYzJ+0HJhF=Wyzdqe(j5OFgUJjcND9D*II;2=Bhf-cKu|_Uh3oiYAP6co)?riY3qe z2A@T2!b>H!^_gy+Xt1q1QXB`^Sqq#l*dg zWPYXN0|}Uyg==MtEgTRa=e4-6hOzJ5NGEe-G~NT5EIdCyKUe(^_&y-_e&gr{)U(dq z^xH;`V0Iut(e%NxaNNoWI9<(o+1}m`QBr}Bi@|8|>IyiX5l=y$P5MoL+Z;49$?19$ z6xfrfVS!S7Qt`0gj~g&rb88xPJS#{KQ(dLSh<*RJ2~74yP{P2#vOjccdDZ)U@_E6z z&(s5iY|Y-ds&4T|w>Y~Xp1Qc2>P#cnG*V>z*>Rbrw$?m{`XhKV1T#fY5wczX+d$HM z-hG>hAU_J}=bf+@{R^EjV8hd8{_!%O1`Ay5H8hc5=VXcUu}!qL-z5?M+q8l|q{85^ z0U-7F;Qa6xQ~3L(wei2G*x&O}^7@ZI@ZUo!tm+lUzmJcSFIOw+sa1)LH5As+VLeIzoO2QjN`a+yP4F=;Yisan||;nya+7+ov3V{cd^dBVAhF$k&0!fw@TvSFQiu z3UB?4lIcs;B+twK^Ls^qy3!oyu_xG`=E%-B9MzS08ZIGE>3re(Wa+N{X$0RWmNw%x zvXKp&hNcF9Cp>>gveciPqvdOt#i4z1b!*~R772fWt^CjchnGk>?uk3BBG(nPIMbW{-*-TPG}-O9#%#>_6eAT+=KH#~ zV6~)c{Kfe+=IXPLrgp5|C?5sgiG`F&Y z1FqOMa#Quo$R|+AW~3ZNC1FbLrZR09Cau|b#idr*8LGq=G5#;#NqQQlp=8LJ?i#f( zKywgMU2Thi^sHlH0d+-p{X28(Y%O8Zi+89!L$zDoGluHcG9_XZ$$_B)euu~9`EyII z7pz9Zs)2=yw13)fxK@AF4xT|?axzA=>j?Rk%P5q{y#~&GnkKF=V=Y|&2tG#ww^Y1P z5#k1PvTg~1^*$^e*^9EfZ>(GepT3>XTPn0E5@1pe3xpVo?pT3%eH7C!Qz)!&d{Y{^}6%K^CO%DP8Xv6mMToh(h}bB2i-_J z6AcZG@7%{RXxY3e_h^6qV2+$*xU8(KkMJHVwm5K{9kT_TZ$ zJHMl|tmR%S!t4CYh0C}xZb_Z0Litx2?ZQ#@&-|dmG}s;7HB?FgaF?UAbBA@V`oI-h zxq({&YSr*Q>0rYfo>$S`eQax2L%d%j_vVZ*qi%-k!db0tY@j)j7mPeSnD_TB>3=rs zpn1$j-^c~P8>5MXoNHFRJE`HfL~l)2_O~Qoyv4;-XTHeXb9lIUbPWs_ z#~Onf8rSnK_IKL6|LmKlxpKD0y7HpWJ5aT#HoXLEGeUNi64oY^ZETQknMOoI#~H*o zWibk;ESqh1gdB8TZ#-TeFWYQN@x~huPfzooE5h}qz@~`fJjWu*;rSX%9<7B#Xi_`rG)I41JH?o z0XdDGWM;2{07$Z8(rJ6*=5Y`Iseyri5Xb|W&1c_(_K6sLY1Auz-r>BM$r$}R9nwG6 zPvR320>SV z7`ahj^zzSw9@6$xt?@9?n;#`A6{MDTAaSPo4g|vNY(8S*BLL0TM06^)MVxFZt=XyoJ|Gxq&ys$ru;N>^Uu)dlv05Se{fGM1xpwGVeeK@1 zI~Im$GIE9JiUc%bPA@I#R&QTl^5*334?}-%o>9>zAQnG0_A?qHke>!eM!w>4JDvHE zU`;}$8($szBk~(SC+a~Gv{gTS-ImM_5V=lyO#-(wxG_I}vqj+#d8q;RSKm??s9*)a{w+R?8h1f;oC%EtUJ)ctPbN@dW*&1 zB3+x9;9WX(5gS9Quy0HINJ@`W=ynV!nVIoaX$Wg9$L`(YEuITV$ywtawjPZ!Z~M$Z zl;S~WLwsc(w{&-r=N>FNW|XBgSQt6-n-IismSwXVCfeb&jxk#W1_g;YJ0qGodn0b) z3ER<@joS)|bdvzs5ANRm5#h_JMv7+vX(l8p&ve{UT~O8U7ZTaY;W?@7{}KT#qhexc z>EzWvO5Ff;wL`+N(F7CoB&Rw8zNlXT#O{Hh#m@qiiE|=?)aXyst=}05+)kAioD@ig zqlz-#jL3c26me$i9q;fgzXAEza}*RP3S*Ne-@`3tclaAY02crX>=;cL&_em}#hf-9 zYc=PYquuTnEbgP-J0Ev^@@`JsuEUbf=;X`LozYw_KI?1`7ag5qqQkZaRqu38iFIzJuezIwjldRBr~B;0iLW6?qn%p0?u@%m^1^Bitf@uhO% z%Z|5PszGWdY9@|~8c_jo8&|dEW5<8KTatnJ(jBG1WIO+r7K64S)UWr*$Bc|xRMFbN zFvIAKranW`hNs72f(C1ObrqFeQPuch*5TsK8@tav=qP-Xtgpp-C`!7eQ$zh50~*n4 zZWYtl_x2QZwtP`q(FjZ@^1bV*Tzvx??f4`1HK%iai~cPHtx2uh$aP1+5RNkrbn7RR zz&JIK^6{bx*7C>&J(~bA_qHf>CZfP8L}zDB90c02*B~za6Hpu5{#F1q=3PCIh`8as zdgTd($T}c|iR*c^9pJRjY{b76Kep+7N{ANmEIBzj+jd%RXNrKQRIl5bZS`o?63&TO ziz9YDeRj=#pH3t+Wi}-IUu`a@ahQj6Cb8`kKfL~4H>grmB5rPm z`NFxc1LooD>+1yqrNK`9t(R+Vokeb)v#s~P3MXJzVv0k4vFOcPB;ww6!B~B(H#xN5 zx!BO)ug^w&q8phyt!e%(+5@Y9{HQ1+ayyk>z}Nv4BQBd6E-2)NIiEx?ZvkR$sy}B= z2N)Wy02{c)Tm*4AC@oViR6quHvB(ffXP#Tsdn)TDu)g8W~u3)ney!)0Gru^uT zzSs_C_v3C{R_n;r30evY$uu)ZX9yWH^Rr%>cXrGhNhV*+si>JnQ$Ag0+=bCw3Boq6 zg*MLBKkBR{^d@jw#Sk6PI9NZ0M?}Rdg3@xT_VFb48p_p4XD8~^YRuBbbft`ZPRZYf zipDuqm?AzjSBY7OCx^D?!c~yY;RTda@MD4 z`VwJ&G=rY9`6kzbo}#q#LipDLWAyjpj(f3q=smW#fyw#e4!n4*Ul?+nTy0*03 z@o@8W8z;LJXXp5?AdTer60!98e)t(dU8Y?><#erngxW?xn)1EZ&ksc_sf}E^^NnLn z*3(a1FQC?)MbYu47mHrfyz1$VZrt;G7CaNzLRikId}DqxwTw#@WjOSo4@8K2To!q5 zNDps*qW1JKW!&7Q$d>GQd^j9A>Xi}26VuOAx-$OS_kKO>wLJd1icMrzhXQ9JLyB3C z!UIE-TT`iepn>d7BomgJc3GK@fZPjj_`8*N(bVEde=zF%|3g=DFuJ6(9@a4TC>$gy zuxY#_94$#7_mGv%vlRbO~x0@;;f~Y%yc<}S-wu)~YYD4}}Nw&l& zSFuQ)%i*V~k9c~VumAQ73V)c|AZ*9P<8vVKZE9U$Q5jh&=i74O(iE&h3V^`@(wnY6 zEz;Z?I_g(-Cao4rwS?8!c7EkrO{)Atvgav znm21*JHM2f=U&xqn47bR0?p4(G2ulhb0UgA!bndT7jVrhj-9>m_D=)ZB`PO$F*0PG(a}-yvjZIX z;-c>Ro95-YoXhP^#dny5w(YOJxX(opMFJ_N&(;>thQ?XvEzDSUP?&$dG&sAC~^z2VvCF6-8aaw;Cwto3Z;{(Ti`w=Pa8= z>qr`9XqgIh3bbli5YfOwEed&)omxutp9ZhK2s4z47~R*o5hk9689pp5q%c3-9fr^4 z%uS>GvQa0J%xgpx>(yWq58fGU(*7rN_U^*C%Z2Nw;RKi4&R&!umUsLq5(3BjH>`Gn zhE>d+kktPT0*p9`9R7Llsck@#!9DnKRWpdOEr zdkUqSfwc4Z{It_w^5L;9g`W~eov7&O^woxmzV?c=0s3pxy$Kgr z{|t#azQHr(GP|mhj-p4}vA%KE{~zRcKXX^J2I9TuMe83aw1le4N=8#nJPnomArEZ} zoQSM$(v!)W=!5SPX55+jdSzcPkZx=?%A(@oZ%#uc04}j%_mPtbFNcvnZvMDmVdsiC zEzxAOf88A~P`wW~Ei#{&vEO(6$6Q_7&!6F^r=cjd2qdJL;#4Lo*yB<0fj^?(W;kH+74?jr&`kzMV0oQ}Gua6BEyt7**R3ceq%74sO_B zr&`X`{Q5r}{$DO7*rj2B4W90{(ZXY9XZUO6j9;*FWRwl%bWn)&sJ>}v+UaYMvZD}b zILTkYvlW$iy#^sPFg8dickn=PSG@{wH**z`^D^)4g(lST$-oFZ9)3wPGGQ)Aw-*08 z)_&;x751=^;yKL-T9(rs?tp>cCdVYwMnQspV@4>1x$DpcOQ;mp0z)O_z3H+@6@Q=Z93mw!oLxr53!7=^zll3-zXt@dgy*yWQ`;R|ZRBFsOY zZ^2uIylAuEpAP^rLd)X?V?FN^Ws8Ir-P#yn;!M@fgTg{Yd_nJP7DR|3IMIB!3VaWj zR)QlpiS6|bZr?5)T{1$QOL<(-x4xTgAU#0IYqWA+tmLcZWWGob*8^O7eU1FbyJTM8 zMF`^k1s^-9>N9nqYB>5;Gz?3prKWxsy+{!5h&4}tWkO-ma3y?OZa_igR$s*MFo6DR z`bg`Kn4a*vr7T~zDDjHFSAFz`c{4j)^93iU(i|82vFT(Zo z{+z;j%tY$Ad3e8Y7U)OZhGAgP-(9hX;-9mm!fx`l8~}m=(7(vUM3BLTzUpz{76|UM z;|}ABjPcmAal2;8TyL@jN>#WoPP1@-m$Bcgwc;TM^*S1N*bb;)l3AFIX@K1Eeaw^A ztJh?UxSmspyaC!uw?VZfKMU2i1MCJH%`Sqvi96gb=1bu#$11D}Z;3eU=`h%@Hjs%(Z(++rDDMYEBa&WhWlzoyPGIRT-8}*DNtkXHW%?%cqOg*9 z{30jMct>aO`^H-LM3*Atg<{4uE8|OOpf0>^f2C&Q1=-uTcy^}BjE60=r`i^E+9p&= z7?1>v*P7@ULT`ATH5C8pB)gb8yCr`TEE`J=4QR48GRyyS(NA0AWyp6?TSynB)bcq> zEaf1+bi5>cChTsOwa>HKI+9`Ver*^Eh1b&3s$W!LhznWQYxw(DX2^P>K(Rtd2wHV8 zyrta}I-frlx5fk;^q2h7BhBXBS_|I2Uzs|R4S#Kq9jVmmvesHG`JnBo>z~j2T@M5K zl8((Qr5R+@orw-(o7VO|#cy`$Sh&jp zuLY5oD~L3YZqi^!%XcYu{kaww3qjJGbe$qqEkfOB<_cHV-6dk8k`oI!h8jg2H4BG_ z*3QDYvmXjGozG|>Nz2ZPL-)hH;q2NTvd_dt9U%_{^*5#AG<4$L5oPG^cjhS;l34g^ zh;<8N>VT20?|r|Kr;mAhsWj>)r}a>=N;Q(HO6Gce^>pC+_M2i{?bZnFGxy?&yP0A} zLg@O3%Rz_VxpwkGMNEu*c(I-R6VK7Hyq${r2~#y*z1GB7pgA9NdZ*n&7<^3~79!ml zQYg@&==Z71$?RRypr-Bqnw#xg{o8MBSl z6tK83C@N7hK*pZWu#9xozOZ9K$z`^%q8gm&7uyfLCtt)#k;}7lfaOh_ixWjKxbU;DnTLlN3%V>e&Sc$)6- zBp*L#EIwQDjd*9WG^38TZksO|NTuZ{mN(aPZ7eSktc%iab&yVR1ITblMDdz`CKxtf%DVYn_$^(pS z!El5@($A!2r@5*s!?vbFOtRmzE?&gzXjlx>q{O(lV&@|L{SR!`W6N#R2X{_ZlV*Iv zt%JkWwK~Z~zALxbrP23iAF*YEmqelxVnSOASx7bRq;Q1KDJmCn(wekMjW(jO6|D;8 z)~~!QGxZ!-d?x!_xbWFlkPwhZJ00DjfbvydpIi37g5h|vS9^9 zvt|7iUNF_U9~Q}@Ec<4EyFLa`Xl}}ayi5q!F_H>)B6A_GyjLl%R{-I@Y=_==on&Wx`S+Vz8CQuOO2D0?WOCAs6^^HUgpHAVbc&^ zRZ9nx-PP!F-QoR5XgRC_7oK zXJ=YnEt9FUw=-LW$Xgl@LOg6k_M@q!S7YN-N1^avB3*g^tBvl}gI0{OO{+_aW7;Xl zpQHu$yX~7cKVgJ7=pbara&@Z5|6T#T?>WlN6W`Cd;~Ksy;OLJ`#91AE6_o%7itG2I zpVbjr8BI6ZK|HywM|wz$79o-aquAh%q6`~Heh)(w%g|b`puW`YZc)~qC778v`RcwW+c?RP6S`?$FiHX~{)`*Md**^X(i zgG>VyIki_yDTfy))Yfm1NoVWKy?*RuSU8YTQ#bs+p_u>ZM1v2Quoy}&uluhHh19hN z^C4(^YFUO={@)#J1VeJe#&c+YvYUJA`rNEWd?2ur-c&!+pI!Qmaewc=-eg-LZscNXIkvdQ9%|53Cl$S`J2WD?a(gIk0Ob$3imy1 zGm+QtY@h{|Uj9wy&#nsKF&URj{ESYMbvinUE2H8}1(9k>!l9CfN`~sqP;a&}qd)vL zdpSvTfs)mkcaakFcX6TtD#@o|fr0ss(cc9q(W%AO#a6fm-UX2-o6hAnq`K9;+Xs+q zjSHU`<~D=#W(oVsz01Mxl3W9CY&^4OlT~Nu+Yy3c-=7zYZbe#@nsA1R3Zu2ABM5*4 zC_}P{pQ~FK)k2?i|JF@lhr)ndYM|qQ-1OpOy1`#ny+L-@^@s|s!TdN}T7qm*X2$Q0 zE7_yAv;*grMP^C`T1%n)7ui|~CcgrPUD_s|sd$he6(q9l@|b+q`zeW}DftaTvf!F) zRZCyD9~Jx7;syEAKl9wl-?M{^x7wN9pXp)L0gQbH2ytJG$oDorNgaZg^#kUM)>yZSNp)V}1iUSAD*> z)Yt*BWO1HciDc=uZyghy$-$V{kTO*jT6a^Jb(B1O`_n=48OIx3r5ZzzM<;S*L~ zFw5*=xqP<2pI_8brevqVw4%;x)V%AJm-qAcoiL7p(Hu#VxP&}XrYf6b{;K~%w`a^t zf_o*nSkY%1=*{{arxZrXq4FO}-XhjFZrFd8V4f*IE5~A~Et*F=VohJ-v8b;pprsoi zDgAky@dM1LchDAE^s{C@(5dxyG7tWMisO&4Hal#6y%zuu8R1+F!y+uAkR)(-kFAe;jwVDm_`_K4O z(7c1OlZhjlA}KWFa>Gi~VC#uS2sbHw23h@i#<#K=qa6ovpbaP~JpU+;o{4X_^FDVl zxd^>H2b<&LoHvj)wQmF`Kc2D~n8FrZ&^okaX>f`jG$Mu^>^gg+M>x`J zjj5_$i0+(PKTrW1NVib7aB5;RvE3PeKkVT)kX3{xIO5H^DCjZ{&QJ7k65dm8dkC7nI8#8l+$vPOUPe+y< zd)pm2Z>RsMR?}*3g40Gn7gDH1eULa4RtT1KV2rtK>z?ym7;CZ%p~GWhWudP75O08H zD(|e+8LQ8IrgMBe56?R&Ac^IBS2+{hP*zXYHWi49*>vhA=a0b_T{{+eD6A&*;hP+B zcen^?v8$@%4KU>%3vrK^pyWzD5CSKqkW-E8;H>030y#v`k_A%{AB5i(+B^1EYL66~ zz`uDH2KpejyDHSh#l?C6io6LdQESGQKHmukdeHz*S^`GyC4xLfmG_5d%(y&eBxrAV z{l_Yf9=l8|A^NsyP-WD=--gcMav!R?sb)E2jTb9DTe?3)QDd_#hUT}!-~54n=G&f> zL!Wo~C>)mJZIz|v?#Ixx^HId_&rIxKLO9Ech(Cts#qcFBFkoQge)Ls$OdY``r)0%b6Ofssn z-FH1ET&r_w$en{08Z*P`HxG&nP zuFR2fEk(v;b{@|wJWD76`qM;CM69@(@mdsW5=~ivL1k6wh~w{r;qFp@BRwFC_Tv6-jX;at@Tfw#Sg;|`7{}>`Lf;&O5`{+arbKkREJn=M z6c&}!;ZoXp9<1*9^~FMem%Ez-(e6$u4i*%;ytf_H6P>3(f*ldixa1>IGnk(Kq`s)q zKOlB$?)31mt@cf$-Bj#hxy)H}bvs|1v}N~KTSY^3;e6-RMEPHZo@ShdN~P(>AAfYu zs^=kxIBNuRA;0);Oa%`X5Qx|MhkL?3i|IH?JxA=b1~p4yU8v8g?ZcEJg($(P+n?79 zgY)utEgrYq%7w={Z`JC|3e%KnY|sd~y-ObUeP#EepTggc)Y`FL-dz0|<3u^V&ohT@ zIKi|^)7zdjqtlDUaiZWA%K5=f*0eW&1dCDgALQO_tb{uDNK++UezYWYO6V=3=!e>sYfP5u^%0CQ0LWFp~5XxZkc`H{=21k(%-Hv)aj{4UP?+AQ~4m*noNNM;PU1MK%JtOy4xPDXK~492?LbAF~|Z3dDlgEKb|QTvX9mA zU4BJtQ7gX;_Ei=*FA<)~oV&#hV}Mlpm7+d2o+DJidq^SnzKXf3s(kk4^RxG4BAO6Q zy(vVzGHMb zm4S-+e8zjX|AbHa-fs89?o~gfzK&==V+e&v8u{8%ZRq;?#)eut?U#(Qw@YU++5cRo zB~EIp;oys_7PXC6zr>$4g`H z(fWCBqj(v0CPuHE`9^ngU5%|tR$WE>UABNqu<6l@utp>^8D3a`44!M?6Z!*YI@<)|jQ)gdsIO zHx!3aR&T{$m^5s__MRDcJ=%)5SYnQP5H+9CZMF3%WJ$T+8% z5%+)?j~5b=6Jkjcd;8=WAJnXN`JhJp=zd()@o0vZCdM>kE#@Cvm~*_v-s%3wEI?UU z;0PjXu>6S7?F$|LovX+?KXx_wRR6seaR0868XLyE9MoyD_9DXis~Q*e4io%yQwH}gv78^5zHEIoNj86s5@93_rb6*6{+oVBx955o3 zwcVAL;$U$1_gmvD!aURN@r@R}No!Ev$%%BFn~f5eoDSMVPxAg`MFUS@Hw|m^q-CeX z{m&2n?%;qVYhp~y#Qsq1^|@mB-PhJ?zC~Wvw_r;q&M*|zQOc0e%?P_Sq9UhQ5&N;= z&(m~mnYhw4wRaQPasENM5<}c5Z=jC`lG7|9fw4UN1g55|&imWe$C0)e*JfK3;oU3#Rt!(!r|ddQ_ztYtwb)dem;iIZ?SC7NDh~2@%qlF9brn70L}e z0bilB=uNz`v*S(dt`@|&$tbKR_hK=H176%iwFFw$#mCSslI9*ewF54h(ok@_*J)`h zb{=H`TO}riW@m=Jv&>CL&wU=4U#48#PpXOgmx?5{?kOvp{L}TnN;~VYD!X;vTfj#M zDpFDc3QCuhbc3YQjg)ja>29S%Kw3H`U6V#~5-K6xDLv_~GbVm(ecxJp?S1xj&ijX4 zm)FcO-!aBBp69-Q&$6rjT179j!{Xy_s@^{@eFGy?BE5&2uKdyJrv0}|?5g*wuxSnL zgzW4vG+<`=CUItJO&mz6r_Zg61HF1Sf#e*Yl0WVopHWJnXu=$7?@P!hiAG@*4qJ|f z=!848jyejo5rgCU$k4jYai?utJ59RQ*u8JM$5e1$D#}_P-Ui;eUn-EtRIN0fTMkmN z_appHF8KVdUz9qO@(Hp3JfGZ;6mh$7v$s*ovTL85Xyj)myc==V5lR+<9&JML;svR? zae01PRl2bM?uU=EU(>f@)7(xJE=Dba#ZsTx*KNru6>F%E92aXg5uF{8ivvNYnw^~j zMSaqAqx&~pYis$;ya<_Eb9M^_v`wpb>R+_7NrT*I>DEhp zkcLk{Jut%~jNZ0*ux2q{uO05I{JPF|-3qZ*iFa`N`X^goh1Bi7N&~z0WZRa&6u6TCA*P(Ky-)D>^CsjdDw<;(cu5R*dkn8;9z zA2O1n5cRdeeC&)jS!-#pfdAxP`Jk@lJI23@{=e~KY4ZLIPYhnmm(220lFkx5BG zxo`TbvkY97uyDj6G9=yp0eWluy+|QWu6Es{L&Qrz5ZNi208n z;tK)x3ggJ0AS2qUpneF{$tgHf!53&abA@xoKpiL-?!UU8O#DtI|6Tx`Y&8gF=G+e` zt0)*4ZhxdbE|1<^i4S`7df(NPWrpfn#-Q4tvY=8hn&8XAH&x;QLy?6N?F?F4eLij= zidSFGmZZDR6FepQ;V%aOS-W<BUWrVI`a5y0*9eE&5SBRtWik_iE&<4Rz_>dr zgs#Hgwea0l0y-nF83j_pu{KhO&cswqDS`ql4fMJvIoOY-Tl5g(%In8Jdz+f!6Kuu7 z6DOTcu8z~gbM@K_uWBpTIoBWOo5f(ze%o#CERD9g;&WUXb{T@Iv~|x6>#yz(xlDEg zCddV$X{a*HL6#k40)$RFa)~bvl_0B=QtqAu`ykL7#N-#iK>;$+qV!YRkSG!Pm$?A>13jU?r1g0l>iEsxyf@j!| zJkT{=xMXkS(=Y~`UI#QK_dqGYgOr%Y`OnpWQvU}7sgEa>c;@9E>)!Y|4gu%nfq`7}xCw`{>@*4D-KXFy@x9W^r&NWO6$V@SueU|2>zfj^-Mlh9wtwtP2H`#gxs*|Lm@=-^NZ zU{k6(hgI!#H#LSJ(DVVxTX~v1Q=c8{JQ_4m5Ou2^D$X#d5_Ye7JWXN6;!8XaO|&VO z?~NYeXj@&H0tId>D5Por^51{%|na@ivhoFE2l)(Z${6yN2iWcXLnN zP^V9ZLo!NLDvFnH;c)7~73s3i4f~si$tT{IEc@XVn%8}qk)U>RxiQAgfhV&>_N0w^ zLdnah^-Gb}$~j1{tIjo}PBK5iRaSaTofcPK?r8gITl&-CcDKikX9S1q59UX=%12y! z*ax{yiW1Apc8AN!Lrjw5?>>9^QZrDvOv@Oz%=!&k>Q!l(_F0I~fKf|hn7jL#L-`(G z@ddXXSozC!yWitGg|RKctdE*TBBEik+-aX-vuGgJmekd~I#K}!m)4al+2&WpVLvpH zDrWkV?=d?ewUa|qIFlf?KsKGitPLBc4J$0kOt;-U^|aZSG>pXcmsE3HQ(`|oa3{%9 zR@9d6b=18yGEwEy?jAKoEg!S!d5!j5-2_`HlN_3p^M0_q>|YTYpXaq00ZoV!M!^th zc$Ac6)#|%tw5syvs(veGg7gP9+r>kz8GiG*enR5>{1Pb*Eva|*tj*bVmqvLLWJNWF z#cmB)fSU6qTnMICmNJ&jt8?ih&!N8}f>?R^JtTE{U$7*VXZ!U8`kRT;UOPz?DYfX7 z6!a@fRO<#Zj`NFR>`=G*28cq1@zhLyh{wC}YBEr@$?dtAJ|l1KnYi7{JBFJ9mlY2+ zQ;tYT2|KP9t^ZT{z(|V@v9}j#J*_NpUT35~KcqQ7{!Zg$(B|rLW+2fR!m$g^OWw+s% zlDrB8xxZqrJQs4PxVY{C-64zF=ndY(LAtdXo#tZbrLeTdqwz}1NX|Wl0G^vJ*7Fih z=52JR&eWi!#b)g8kF4e!(8#?VV`^GhoyJ*EGX(IMumwaN&o92-s0+pFEOF=K*nu{- z7Yw(jb;!h0ykZO=1ZWI~L+jD9BJXsYny$(no!K)y&7Db^bu-w|qId1v+T6_Zl>a&C z@%YQbrf`P`;$2NpV67T3Aj2DCl{%z|-5~jPZZjE=FWYXQ=ja@2i~cb`-H;g+%41bm z?Jn&tf&9;lg$i{QBkKXHgL~=QPaY||G`d_T{}HY7IZB6-J3Q9P{Q)Zj$%ePUW!%fy zX!@5k+}RCw(nxn>c6pQgByBhw445U$v@f_stR6pI_z0XV+`q8*+b$*PV#9X@XX6om z`X*Ja+xbqB8q(aQyHVaoLxGybRnkmEGaDGASv-!gun#lDM&P_spN$fmwEY8&Iv;nN{Z1}!A5gVG z6RtE5_IjjhB8WmW?LRAC+}_>a_UlSgo^_|a6!`=9J~xglT>%h8p#d9_SP|XX^H*{l zVDgFtsKXk$fJML z6h3EI0#;l0z8mE7tuNwbc+o)$w4&`8X)YvDMzp-FwS*AN9Anu> zdOzZ3^g%yuE@#$9bw{M4SwVB1NNKUU);74XXJB5FRfI1qDnzX z?SPmL1op3rrqZYQ^rz^$(8ue*;RX^EoYm63oq_-~6PEXwPF9j zX0{m=nsIhJxqta&1mgAL?KeY+O0Vror|qY^=pCbxzC%(%_6^A(`!N?6?k=ocU=_roGD!FYNCeBA`lS z9Q2O>8}O#^Gi)**q$QFROX_ijYwu0G?FzBBIfGi))JX?NM-kv%iAees!fNDmKQ+P+ zoNNqpN1jx|HDR0LJ-xyro2atNHj9y8i^h%T&=y8E*?BBCwY9t+jP-`P zfP27_@?NR!4v!v>9sBFT)V$$d()t&`lR{zS15u4`;;<45&c1sULP#BTaQ7NMO&C)$8|c2GuPv|9yx)u8fR{=_Z~#jN>_ z64x^ML$3kpY|6M{tD>xmk6BV3_R)-f(?9s6+J`8w0-|0I+$UKH9H3>SAfjjvyFx9& z)bC$(k~|a=31|xJ7QDQZjz=#XdQguw$TOjo|n64FP$0 zn?ds=4wrMMy|Dt?3dh+onFH&Oz_>VqBAV6gW;<=(f1sX_G?nLwrMF(nWnAyQmU1T< zvBQQQOpw2Cg#VgC@H5&!8T z9k`Qd+}wir#iP9>I%3SUn!?C9`_+Y0CxfJX$9qk_^p^YDo71A7_ZF3W>&?|U-sybv zc^_fJhfS!dtF0?e6rqCMx#SxmfP)jh?lJKA@2WD&T1XswU2i=6;fv^q49yr^Uwm2O zIgMwVH2A#5^R8b#Z=Xxpyy%>u#r?4F`pZ_$QbjpZasPww1GN}*j5hBNR3Lj}GsVN| zcyd6Tr_lXmCVa~EFypAaI9=F@dulg7cUyWm?&D3e`YSoWg1ry9SXKW6SO-Pp{1^aZ zC~ELlur!(Ldep$Ue>;D<`l|cIaJ{X+^yzxjaOw+9J#CV+0zaH1m-`?ZA4nVaCpT#A ziq6^pR>r#1S$TQcz4z({O;a*!#-_FBc^1u`n$Kk>CYrMVCGZ?qeC_76w$`Zqw#x#I zAkq%q-U13e2RE7>STExua52wr)Juek{gPX@DqWdCqY7K=nP;X_QOrE=dY-4Yvz|~N z-L&$v)I_fSy>4PX0yGWb>uhq5zu==qFV?MfU{XIc*ZB~gl$5V)eu@9EjJL;PIJJph zUg;<&_oy&e1;5?9Jt{-@s2)Wism9V_8_2`?4lt_D>u~YtJ~e^C&&!L$a-2I zHA0IEGc-&{+6`ww;;|TSYwE?&TX-+kJ8X;xZh2~aRI+b5ZCk+6-@5#RPi`vKvX14X zVa|n=doEO7vaM<2Xtwo8!eBHP9hk8B-^#%k+C3T+le)T22Z>T!wJ&~wAD9T#)s>1J zvr?Y!UO3M9f`DOXug9k|?WeXjeWNqoi#$VGf`8;>WM%FTEaH#^Z1@*ojB$F)K zeLuChSyukNR&#g|J!89j-C*db?k4Y$$>c&aqf4^wmaIge14F544>s@KA{F5gBG61= zDZlYb{&BtAv!L4->hh;*?mzgq5BAy{lC7I_Qkeu_OJj6|xdBxwvuh2;`{<#vI9;a) zGwxTxh+oA`>()5_8Kbv)Y+VPfw<9=bghJPpAx~TUSB5ia6?pCw`M2-NVeo?NC7Hh|d2cgt&)CN`2jAVz;)M@cueUJeG=CrR_pN$)-{S%D-dx;gRVs9U zD~wdj5~|K#8-f6ydF^N&0-<-|hmb#dksIun zY535jrm*L=B2484A9gQ{w!}#UMO6gtsI_dVY2AMD$@^2dZoc5UbFQ=tkjXh>xfha8 zJ5{FEUZMrn$>6oM!)aEx*S7l?s`K3G_7y3<3gNv&M}PlhcD&XSM@MNUI5qYnU}VVk zNwMc@Z&O-zL;&|dS$>tXK>uy-2-l%M|}iMMH z9cB}Ezg(i^9X(*2!Vn?6JCIBd>xpQ16$A~&v>Plt_Z=WOYEHg5ciTov<@CGZ0K_Id zRz~4c6YQcrfFT7$;QA9`e3Gwv1=UUzrpASeGvvN*XwwGM#jjmnI0od62w?WJ*aD04 z9n`6qzI06ubW?A9JiO3HL$nJ3r8cQOHHiKmQIPQLm-Zh<^kk-{?4Hfxg#5;or-#m> z?uU#uj^lRn-<~dhIx?LNmVbM!xs@SOr$Vb1@sm%a)a8PeZt`raKCfvV9sT1o-J${= z#?-8f^65r%9J9gP{JOwy69gi~RbxHjlM96rfhfs#@*+>Jxs2)F4s@py6K=6AvapDF0wIM zsf$K%cYS1-p%hTro&uk07~BPdqV0ZIx&j(=_~Uehw|R|BzrI4yYV9vkxSxZ@`pjvj-oobxZk(qjkz{@c#b5@DZi5Y<8j_C~Ak zRM*r|K6`v)XXoRyH`J1zhye|$TK2B>(4a8yaL|{YQ#(msa+B?gl){N&ap11-7*IN; z=_Dt(l(^^~oYd9SzCB90{qBn}sdM|+)gu@B%)6TMA zi*nt1shNTt&lxPVhdcy3vZH1oXM$I8(q!b$yAQ3bW>9aR3Z$VqIqJiNa{eq4R!oqc}b)qUQQVGfI2CH{s^+Qn@FS5lL+fbR4Df0StF)(C)Cp>I*j!hcyRa~Ha((!)7_o1 z_ywpUYHEv#Wylq-5x#(UA&kw-W6t%epImEiAVh>uJ z(den}rGGSCR>|RXBbJtIziS$f`O?Kllmp1Z%4nM9wBQz2aS*|Oc>2MpFTHd4GQ;_O zsLEBAreK%g`60_i5$aeK7CkvGaj~Y?!HNeCPcnMZ#?jBE=N7!;@k;G)OIJQBGdg_h zK>FI{C6TndAg%EKFf#5ZG-;!y@0l9UY(dp1w2!Svx{o3*>b;guijjzT01))#xv2aA z2&A@6Sd|2*PGNrXW=yUR-1+zsbT5`T$d7@j;bpYsfNnUH^L>i+=Li!}Fh&a_;r0eT zfpqC5j5W5duU)x>g9VBx@UUc21sksv#X%rGcFGJ9Q@LAe`*H34D{*j`FL8(KfEt>n zr-}(I;yqHyhP?WfqXf9jCUeOW*o(ry$>4^Og|{WxCnCu-l5$eYH`7rN2(s<{eXn2) zCOZ?9^I~Xf2$1}0*!M7&kEM%5keG)RDHObZP6C^p#AeWB7jEC)b?!CADI(O-ZXDc) zoS*k>b#}A1mhTl3o*@FA3M5p)^2u92xg}LO=?7Fj-_(y7KuNWk&v7P*Th~39gCSuS zB13;j$`u0g1p1Sm%})6<_mJxKdX5cCEVmugDU5a4%+-MJde4&^TVc)@N&f(~fQUs^ z`|@5_PY*6IZ4Y`1*Cw}j(9DkRJR=eCcp&xsxum4zaJZDToHrDswBEg84n*W%y>2Ke zP$)a8J?oCc5wg+KOIuK9sjjt~R95&L8Hry06irI1h9MgW(;k3!En+q{H-IcK{8I%4 z(r|=5Sg1`3AO-$G`b+zpidTs~FyLQVh|DPg^2B!|RVbh@b=U_HxGgKK2F`veDBx)F zOy^!b`2uldB2@n;wKsjeduRwxe@Xa2cS1#0=!eP7rAUNM%D|AyE%@hAt_EI4Mlv#5 zuhXRw0mX7-D==yA{9l(@pi4n6yesph0{8vF85+b6&6&4o;|R^#1iUlI?r~<-^jmq_){eI4$=y~M3{||4Ujb_{1 z-96w57)PWjDL+`=5&T>@GBF6(;VvA?}+dCHtvKKCpmWQhW871dYh%Gt^3Yfld*VC%Nqm1@xw zT>n9y7es$Ff4K|;#GaU@!uF4y&q&1?bSqIO95(t#aKIGz5fh+b`{~E@i(xq26mV^y zoQ~Hq-2$}ZV4dMyAxmYJ4GamQZ^q>dn^oX5Ng6;4qQnwvbFxe*zy79df0j|NJ@^2F z5ggqX75nfp6h63`n$f#}^Y(BVvoGVUK~h?-dDyb9Z@u4&@N!rbi+uM1wM>dXe*>AQ zh8fT0r3tal#t2#lT(0E;*u`WHwk(&6+?{q#`18v~OBddSdH)kpCdT8o$c3n1FdhUL zRXo~H@RW=DlD(axzNhw(8w!_qtSU^LoAGp<&ya8QX^-+P(za?XnmPlgKnEvRXUgZ?>f$C6W@bEg zgBRTRm@>4pQH*{7#Zt1k`1qwJ16#ZexZ-OC;ST{P79fJxxTUzhRgt#8Y`ojdxGbmiz6lNQiwPeFE6i>7fMds zU;Q6u=L=TxxR6!5KVOym|LbG?)1(gCLi~+XCssCQfx_5JLj%ueDpa9kY%{CW-mmrf z>&xIh-!){%zbYXC^J1ndP?_@OL^_kUR)~}1JeEC!V{&Kj}CS* zQPh!!;@=moPb$)Ec)?_G=?DaS#~z>!4$%V5cRwhUE&#{*zAjMt(okkZd``L*-Q}km zc!9%o;Cd?&w$TQx&wb0K2qd}}Mk5nAWZVvX#)>TUB$sOCiq=gAfmLGs0@nz`>)}z1 zWL17PlrKhAe=)qScG^Qrui5y>QiH=k97cfLElO?Co!tBOfZRabOoOL(%8|fr$UdXr z%drEiIm)Q%?Ho)%e zhm@$7!V%XNS3OirCcnixRh?M^lz1ve0axkl<?@j{g>%C@`l&zmoGMaCg82;IFrT?iu zSk#}=(4d*F#sdxnh(cqqsqI@BX%N%XI+Hl}xLN+T2>(BTCI6b4|0@#Zh$tvYv9ikA zJp8Q>Lyjx$YdN{F#&4aSe&3CKLALag{}JKk&u6fAqT*bTcYi`}X>gng01>Rg*7P539q@%@7Lk(0oYc9sUp}LwcOW%q_%g|t z+-2qEEXHk*-A)dUa$o29U{@^e5D|sJfEo0!tw;l=k4fgdqElfpO=umS8g_bMu0Q)q zel^1Eq}WsM4B=kAF#J1U2WmwFzS?x4B~&X%SVL;cfx&XxI|DBN#V*T63*#E2_50SL zd|{2!L-hW_yS|=RU-2r1myZ{i^7F2eEhlPl zKtk*_ytl|kRX;8+Cr6~W_qY5Od7^ci7Zz1I1;@}@_H|Axv7gijbA>G+6^?~^yNLR4 zcM`#Ov&uP8Q?s{uTIAsL^l$ryHhTsOXGtx7v-0$NpZ`1^|7+Kw>oNTABhO#IlX=K{ zX{bPjj#WmT`4N_l_}^pBjQ&4#t(my!{`yJ&KYM*7K+l=z=w4l+Bax|qdPsEL>)>Dd zc3gkEpm{Ua_+9KD{s1V)FKO~`aFc(;k{|jlI8w{E-Fk{%{O3ggkBK93GpNXsnz1}(w_zMG0UJC z_G*agJu_s{NsIA!%o-Kcl?vJd0J@tEXlL?gybAtYPq9UkNv)}f*pxI)busZ2;lx;3 zWK33n*j+9uBOMT4%z1wMeD*CwCu{3<|XFbP~DpQmEc#CCUT&oSGTjXgKX;kW6v3W!paf;I}#?aITw9+3oxD5 z7eXs4u=s{!`rirG^p$bpk@|uIw!Pa%P(gu(lT&SL`@2B{BV{Zm9AMF}L}cN#|M(A6 miI$8D8E5{!{`*z*Od#3B@d?$Bn<2;!V^U&rqD8`bKK}<;l8od4 literal 0 HcmV?d00001 diff --git a/docs/my-website/release_notes/v1.65.0-stable/index.md b/docs/my-website/release_notes/v1.65.0-stable/index.md index 36e571be93..ec5f3bd441 100644 --- a/docs/my-website/release_notes/v1.65.0-stable/index.md +++ b/docs/my-website/release_notes/v1.65.0-stable/index.md @@ -18,12 +18,7 @@ import Image from '@theme/IdealImage'; v1.65.0-stable is live now. Here are the key highlights of this release: - **MCP Support**: Support for adding and using MCP servers on the LiteLLM proxy. -- **Custom Prompt Management**: Connect LiteLLM to your prompt management system with custom hooks. - - - - - +- **UI view total usage after 1M+ logs**: You can now view usage analytics after crossing 1M+ logs in DB. ## Model Context Protocol (MCP) @@ -39,21 +34,71 @@ Read more about MCP [here](https://docs.litellm.ai/docs/mcp). Expose and use MCP servers through LiteLLM

-## Custom Prompt Management +## UI view total usage after 1M+ logs -This release allows you to connect LiteLLM to any prompt management service through our custom prompt management hooks. As proxy admin all you need to do is implement a `get_chat_completion_prompt` hook which accepts a prompt_id and prompt_variables and returns a formatted prompt. +This release brings the ability to view total usage analytics even after exceeding 1M+ logs in your database. We've implemented a scalable architecture that stores only aggregate usage data, resulting in significantly more efficient queries and reduced database CPU utilization. -Read more about custom prompt management [here](https://docs.litellm.ai/docs/proxy/custom_prompt_management).

- Connect LiteLLM to your prompt management service through custom hooks + View total usage after 1M+ logs

+- How this works: + - We now aggregate usage data into a dedicated DailyUserSpend table, significantly reducing query load and CPU usage even beyond 1M+ logs. + +- Daily Spend Breakdown API: + + - Retrieve granular daily usage data (by model, provider, and API key) with a single endpoint. + Example Request: + + ```shell title="Daily Spend Breakdown API" showLineNumbers + curl -L -X GET 'http://localhost:4000/user/daily/activity?start_date=2025-03-20&end_date=2025-03-27' \ + -H 'Authorization: Bearer sk-...' + ``` + + ```json title="Daily Spend Breakdown API Response" showLineNumbers + { + "results": [ + { + "date": "2025-03-27", + "metrics": { + "spend": 0.0177072, + "prompt_tokens": 111, + "completion_tokens": 1711, + "total_tokens": 1822, + "api_requests": 11 + }, + "breakdown": { + "models": { + "gpt-4o-mini": { + "spend": 1.095e-05, + "prompt_tokens": 37, + "completion_tokens": 9, + "total_tokens": 46, + "api_requests": 1 + }, + "providers": { "openai": { ... }, "azure_ai": { ... } }, + "api_keys": { "3126b6eaf1...": { ... } } + } + } + ], + "metadata": { + "total_spend": 0.7274667, + "total_prompt_tokens": 280990, + "total_completion_tokens": 376674, + "total_api_requests": 14 + } + } + ``` + + + + ## New Models / Updated Models - Support for Vertex AI gemini-2.0-flash-lite & Google AI Studio gemini-2.0-flash-lite [PR](https://github.com/BerriAI/litellm/pull/9523) - Support for Vertex AI Fine-Tuned LLMs [PR](https://github.com/BerriAI/litellm/pull/9542) From da953c74a1d331cb65be595c78c4bbde09ce1f75 Mon Sep 17 00:00:00 2001 From: jmarshall-medallia <110042657+jmarshall-medallia@users.noreply.github.com> Date: Sun, 30 Mar 2025 03:38:54 -0300 Subject: [PATCH 240/244] Add password to sync sentinel client (#9622) --- litellm/_redis.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/litellm/_redis.py b/litellm/_redis.py index 5b2f85b1af..5c3e7776d6 100644 --- a/litellm/_redis.py +++ b/litellm/_redis.py @@ -202,6 +202,7 @@ def init_redis_cluster(redis_kwargs) -> redis.RedisCluster: def _init_redis_sentinel(redis_kwargs) -> redis.Redis: sentinel_nodes = redis_kwargs.get("sentinel_nodes") + sentinel_password = redis_kwargs.get("sentinel_password") service_name = redis_kwargs.get("service_name") if not sentinel_nodes or not service_name: @@ -212,7 +213,11 @@ def _init_redis_sentinel(redis_kwargs) -> redis.Redis: verbose_logger.debug("init_redis_sentinel: sentinel nodes are being initialized.") # Set up the Sentinel client - sentinel = redis.Sentinel(sentinel_nodes, socket_timeout=0.1) + sentinel = redis.Sentinel( + sentinel_nodes, + socket_timeout=0.1, + password=sentinel_password, + ) # Return the master instance for the given service From a8673246dc5c86f526d845e92ddc2d8469993137 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 30 Mar 2025 17:40:34 +1100 Subject: [PATCH 241/244] fix: Anthropic prompt caching on GCP Vertex AI (#9605) * fix: Anthropic prompt caching on GCP Vertex AI * test(vertex): anthropic prompt caching --- litellm/llms/anthropic/chat/transformation.py | 10 +- .../test_vertex_anthropic_prompt_caching.py | 137 ++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/litellm/llms/vertex_ai/test_vertex_anthropic_prompt_caching.py diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index a8f36cdcad..0780f5b4d4 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -141,9 +141,15 @@ class AnthropicConfig(BaseConfig): if user_anthropic_beta_headers is not None: betas.update(user_anthropic_beta_headers) - # Don't send any beta headers to Vertex, Vertex has failed requests when they are sent + # Handle beta headers for Vertex AI + # We allow prompt caching beta header for Vertex, but exclude other beta headers that might cause issues if is_vertex_request is True: - pass + vertex_safe_betas = set() + # Allow prompt caching beta header for Vertex + if "prompt-caching-2024-07-31" in betas: + vertex_safe_betas.add("prompt-caching-2024-07-31") + if len(vertex_safe_betas) > 0: + headers["anthropic-beta"] = ",".join(vertex_safe_betas) elif len(betas) > 0: headers["anthropic-beta"] = ",".join(betas) diff --git a/tests/litellm/llms/vertex_ai/test_vertex_anthropic_prompt_caching.py b/tests/litellm/llms/vertex_ai/test_vertex_anthropic_prompt_caching.py new file mode 100644 index 0000000000..c417451921 --- /dev/null +++ b/tests/litellm/llms/vertex_ai/test_vertex_anthropic_prompt_caching.py @@ -0,0 +1,137 @@ +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.anthropic.chat.transformation import AnthropicConfig + + +def test_anthropic_prompt_caching_headers_for_vertex(): + """ + Test that the prompt caching beta header is correctly set for Vertex AI requests + with Anthropic models when cache control is present in the messages. + """ + # Create an instance of AnthropicConfig + config = AnthropicConfig() + + # Test case 1: Vertex request with prompt caching + # Create a message with cache control + messages = [ + { + "role": "system", + "content": "You are a helpful assistant.", + "cache_control": {"type": "ephemeral"} + }, + { + "role": "user", + "content": "Tell me about the solar system." + } + ] + + # Check if cache control is detected + is_cache_control_set = config.is_cache_control_set(messages=messages) + assert is_cache_control_set is True, "Cache control should be detected in messages" + + # Generate headers for a Vertex AI request with prompt caching + headers = config.get_anthropic_headers( + api_key="test-api-key", + prompt_caching_set=is_cache_control_set, + is_vertex_request=True + ) + + # Verify that the anthropic-beta header is set with prompt-caching-2024-07-31 + assert "anthropic-beta" in headers, "anthropic-beta header should be present" + assert "prompt-caching-2024-07-31" in headers["anthropic-beta"], "prompt-caching-2024-07-31 should be in the beta header" + + # Test case 2: Vertex request without prompt caching + messages_without_cache = [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Tell me about the solar system." + } + ] + + # Check if cache control is detected + is_cache_control_set = config.is_cache_control_set(messages=messages_without_cache) + assert is_cache_control_set is False, "Cache control should not be detected in messages" + + # Generate headers for a Vertex AI request without prompt caching + headers = config.get_anthropic_headers( + api_key="test-api-key", + prompt_caching_set=is_cache_control_set, + is_vertex_request=True + ) + + # Verify that the anthropic-beta header is not set + assert "anthropic-beta" not in headers, "anthropic-beta header should not be present" + + +def test_anthropic_prompt_caching_with_content_blocks(): + """ + Test that prompt caching is correctly detected when cache control is in content blocks. + """ + config = AnthropicConfig() + + # Message with cache control in content blocks + messages = [ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "You are a helpful assistant.", + "cache_control": {"type": "ephemeral"} + } + ] + }, + { + "role": "user", + "content": "Tell me about the solar system." + } + ] + + # Check if cache control is detected + is_cache_control_set = config.is_cache_control_set(messages=messages) + assert is_cache_control_set is True, "Cache control should be detected in content blocks" + + # Generate headers for a Vertex AI request with prompt caching + headers = config.get_anthropic_headers( + api_key="test-api-key", + prompt_caching_set=is_cache_control_set, + is_vertex_request=True + ) + + # Verify that the anthropic-beta header is set with prompt-caching-2024-07-31 + assert "anthropic-beta" in headers, "anthropic-beta header should be present" + assert "prompt-caching-2024-07-31" in headers["anthropic-beta"], "prompt-caching-2024-07-31 should be in the beta header" + + +def test_anthropic_vertex_other_beta_headers(): + """ + Test that other beta headers are not included for Vertex AI requests. + """ + config = AnthropicConfig() + + # Generate headers with multiple beta features + headers = config.get_anthropic_headers( + api_key="test-api-key", + prompt_caching_set=True, + computer_tool_used=True, # This should be excluded for Vertex + pdf_used=True, # This should be excluded for Vertex + is_vertex_request=True + ) + + # Verify that only prompt-caching is included in the beta header + assert "anthropic-beta" in headers, "anthropic-beta header should be present" + assert headers["anthropic-beta"] == "prompt-caching-2024-07-31", "Only prompt-caching should be in the beta header" + assert "computer-use-2024-10-22" not in headers["anthropic-beta"], "computer-use beta should not be included" + assert "pdfs-2024-09-25" not in headers["anthropic-beta"], "pdfs beta should not be included" From 69db775e73f082a5b57c763c192acc094ba89937 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sun, 30 Mar 2025 00:28:45 -0700 Subject: [PATCH 242/244] docs(vertex.md): update docs to show 'file' message usage --- docs/my-website/docs/providers/gemini.md | 20 +++++++---- docs/my-website/docs/providers/vertex.md | 44 +++++++++++++++--------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/my-website/docs/providers/gemini.md b/docs/my-website/docs/providers/gemini.md index 4a6cfdf1a3..8171a9dd04 100644 --- a/docs/my-website/docs/providers/gemini.md +++ b/docs/my-website/docs/providers/gemini.md @@ -589,8 +589,10 @@ response = litellm.completion( "content": [ {"type": "text", "text": "Please summarize the audio."}, { - "type": "image_url", - "image_url": "data:audio/mp3;base64,{}".format(encoded_data), # 👈 SET MIME_TYPE + DATA + "type": "file", + "file": { + "file_data": "data:audio/mp3;base64,{}".format(encoded_data), # 👈 SET MIME_TYPE + DATA + } }, ], } @@ -640,8 +642,11 @@ response = litellm.completion( "content": [ {"type": "text", "text": "Please summarize the file."}, { - "type": "image_url", - "image_url": "https://storage..." # 👈 SET THE IMG URL + "type": "file", + "file": { + "file_id": "https://storage...", # 👈 SET THE IMG URL + "format": "application/pdf" # OPTIONAL + } }, ], } @@ -668,8 +673,11 @@ response = litellm.completion( "content": [ {"type": "text", "text": "Please summarize the file."}, { - "type": "image_url", - "image_url": "gs://..." # 👈 SET THE cloud storage bucket url + "type": "file", + "file": { + "file_id": "gs://storage...", # 👈 SET THE IMG URL + "format": "application/pdf" # OPTIONAL + } }, ], } diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 103cc07ef5..2865a82ce1 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1720,23 +1720,25 @@ assert isinstance( ``` -## Usage - PDF / Videos / etc. Files +## Usage - PDF / Videos / Audio etc. Files Pass any file supported by Vertex AI, through LiteLLM. -LiteLLM Supports the following image types passed in url +LiteLLM Supports the following file types passed in url. + +Using `file` message type for VertexAI is live from v1.65.1+ ``` -Images with Cloud Storage URIs - gs://cloud-samples-data/generative-ai/image/boats.jpeg -Images with direct links - https://storage.googleapis.com/github-repo/img/gemini/intro/landmark3.jpg +Files with Cloud Storage URIs - gs://cloud-samples-data/generative-ai/image/boats.jpeg +Files with direct links - https://storage.googleapis.com/github-repo/img/gemini/intro/landmark3.jpg Videos with Cloud Storage URIs - https://storage.googleapis.com/github-repo/img/gemini/multimodality_usecases_overview/pixel8.mp4 -Base64 Encoded Local Images +Base64 Encoded Local Files ``` -### **Using `gs://`** +### **Using `gs://` or any URL** ```python from litellm import completion @@ -1748,8 +1750,11 @@ response = completion( "content": [ {"type": "text", "text": "You are a very professional document summarization specialist. Please summarize the given document."}, { - "type": "image_url", - "image_url": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", # 👈 PDF + "type": "file", + "file": { + "file_id": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", + "format": "application/pdf" # OPTIONAL - specify mime-type + } }, ], } @@ -1783,8 +1788,10 @@ response = completion( "content": [ {"type": "text", "text": "You are a very professional document summarization specialist. Please summarize the given document."}, { - "type": "image_url", - "image_url": f"data:application/pdf;base64,{encoded_file}", # 👈 PDF + "type": "file", + "file": { + "file_data": f"data:application/pdf;base64,{encoded_file}", # 👈 PDF + } }, ], } @@ -1830,8 +1837,11 @@ curl http://0.0.0.0:4000/v1/chat/completions \ "text": "You are a very professional document summarization specialist. Please summarize the given document" }, { - "type": "image_url", - "image_url": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf" # 👈 PDF + "type": "file", + "file": { + "file_id": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", + "format": "application/pdf" # OPTIONAL + } } } ] @@ -1858,10 +1868,11 @@ curl http://0.0.0.0:4000/v1/chat/completions \ "text": "You are a very professional document summarization specialist. Please summarize the given document" }, { - "type": "image_url", - "image_url": "data:application/pdf;base64,{encoded_file}" # 👈 PDF - } - } + "type": "file", + "file": { + "file_data": f"data:application/pdf;base64,{encoded_file}", # 👈 PDF + } + }, ] } ], @@ -1872,6 +1883,7 @@ curl http://0.0.0.0:4000/v1/chat/completions \ + ## Chat Models | Model Name | Function Call | |------------------|--------------------------------------| From 17ad8a04175402b71bbb3f67c9d8e5ff248c5228 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sun, 30 Mar 2025 00:40:23 -0700 Subject: [PATCH 243/244] docs: cleanup docs --- docs/my-website/docs/providers/vertex.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 2865a82ce1..ab13a51137 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1793,6 +1793,12 @@ response = completion( "file_data": f"data:application/pdf;base64,{encoded_file}", # 👈 PDF } }, + { + "type": "audio_input", + "audio_input { + "audio_input": f"data:audio/mp3;base64,{encoded_file}", # 👈 AUDIO File ('file' message works as too) + } + }, ], } ], @@ -1871,9 +1877,15 @@ curl http://0.0.0.0:4000/v1/chat/completions \ "type": "file", "file": { "file_data": f"data:application/pdf;base64,{encoded_file}", # 👈 PDF + }, + }, + { + "type": "audio_input", + "audio_input { + "audio_input": f"data:audio/mp3;base64,{encoded_file}", # 👈 AUDIO File ('file' message works as too) } }, - ] + ] } ], "max_tokens": 300 From ce5f55d04e392e33f96a36ddce492d8cd1e7d9da Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 31 Mar 2025 14:20:47 -0700 Subject: [PATCH 244/244] test fix update spend --- tests/proxy_unit_tests/test_update_spend.py | 43 ++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/tests/proxy_unit_tests/test_update_spend.py b/tests/proxy_unit_tests/test_update_spend.py index 36965cafa7..641768a7d2 100644 --- a/tests/proxy_unit_tests/test_update_spend.py +++ b/tests/proxy_unit_tests/test_update_spend.py @@ -20,7 +20,12 @@ from litellm.proxy.utils import update_spend, DB_CONNECTION_ERROR_TYPES class MockPrismaClient: def __init__(self): - self.db = MagicMock() + # Create AsyncMock for db operations + self.db = AsyncMock() + self.db.litellm_spendlogs = AsyncMock() + self.db.litellm_spendlogs.create_many = AsyncMock() + + # Initialize transaction lists self.spend_log_transactions = [] self.user_list_transactons = {} self.end_user_list_transactons = {} @@ -33,6 +38,20 @@ class MockPrismaClient: def jsonify_object(self, obj): return obj + def add_spend_log_transaction_to_daily_user_transaction(self, payload): + # Mock implementation + pass + + +def create_mock_proxy_logging(): + print("creating mock proxy logging") + proxy_logging_obj = MagicMock() + proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj.db_spend_update_writer = AsyncMock() + proxy_logging_obj.db_spend_update_writer.db_update_spend_transaction_handler = AsyncMock() + print("returning proxy logging obj") + return proxy_logging_obj + @pytest.mark.asyncio @pytest.mark.parametrize( @@ -47,8 +66,11 @@ async def test_update_spend_logs_connection_errors(error_type): """Test retry mechanism for different connection error types""" # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() + + # Create AsyncMock for db_spend_update_writer + proxy_logging_obj.db_spend_update_writer = AsyncMock() + proxy_logging_obj.db_spend_update_writer.db_update_spend_transaction_handler = AsyncMock() # Add test spend logs prisma_client.spend_log_transactions = [ @@ -90,8 +112,7 @@ async def test_update_spend_logs_max_retries_exceeded(error_type): """Test that each connection error type properly fails after max retries""" # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() # Add test spend logs prisma_client.spend_log_transactions = [ @@ -123,8 +144,7 @@ async def test_update_spend_logs_non_connection_error(): """Test handling of non-connection related errors""" # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() # Add test spend logs prisma_client.spend_log_transactions = [ @@ -155,8 +175,7 @@ async def test_update_spend_logs_exponential_backoff(): """Test that exponential backoff is working correctly""" # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() # Add test spend logs prisma_client.spend_log_transactions = [{"id": "1", "spend": 10}] @@ -198,8 +217,7 @@ async def test_update_spend_logs_multiple_batches_success(): """ # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() # Create 150 test spend logs (1.5x BATCH_SIZE) prisma_client.spend_log_transactions = [ @@ -245,8 +263,7 @@ async def test_update_spend_logs_multiple_batches_with_failure(): """ # Setup prisma_client = MockPrismaClient() - proxy_logging_obj = MagicMock() - proxy_logging_obj.failure_handler = AsyncMock() + proxy_logging_obj = create_mock_proxy_logging() # Create 400 test spend logs (4x BATCH_SIZE) prisma_client.spend_log_transactions = [
{/* Left side - Title and description */}
Model Management - {!all_admin_roles.includes(userRole) && - Add models for teams you are an admin for. - } + {!all_admin_roles.includes(userRole) ? ( + + Add models for teams you are an admin for. + + ) : ( + + Add and manage models for the proxy + + )}
{/* Right side - Filter */} @@ -1135,6 +1142,7 @@ const ModelDashboard: React.FC = ({ )} isLoading={false} // Add loading state if needed /> + Date: Thu, 27 Mar 2025 20:27:19 -0400 Subject: [PATCH 121/244] Support max_completion_tokens on Mistral (#9589) * Support max_completion_tokens on Mistral * test fix --- .../mistral/mistral_chat_transformation.py | 9 +++- .../mistral/test_mistral_transformation.py | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/litellm/llms/mistral/test_mistral_transformation.py diff --git a/litellm/llms/mistral/mistral_chat_transformation.py b/litellm/llms/mistral/mistral_chat_transformation.py index 3e7a97c92f..7f93ddcc29 100644 --- a/litellm/llms/mistral/mistral_chat_transformation.py +++ b/litellm/llms/mistral/mistral_chat_transformation.py @@ -28,7 +28,9 @@ class MistralConfig(OpenAIGPTConfig): - `top_p` (number or null): An alternative to sampling with temperature, used for nucleus sampling. API Default - 1. - - `max_tokens` (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. + - `max_tokens` [DEPRECATED - use max_completion_tokens] (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. + + - `max_completion_tokens` (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. - `tools` (list or null): A list of available tools for the model. Use this to specify functions for which the model can generate JSON inputs. @@ -46,6 +48,7 @@ class MistralConfig(OpenAIGPTConfig): temperature: Optional[int] = None top_p: Optional[int] = None max_tokens: Optional[int] = None + max_completion_tokens: Optional[int] = None tools: Optional[list] = None tool_choice: Optional[Literal["auto", "any", "none"]] = None random_seed: Optional[int] = None @@ -58,6 +61,7 @@ class MistralConfig(OpenAIGPTConfig): temperature: Optional[int] = None, top_p: Optional[int] = None, max_tokens: Optional[int] = None, + max_completion_tokens: Optional[int] = None, tools: Optional[list] = None, tool_choice: Optional[Literal["auto", "any", "none"]] = None, random_seed: Optional[int] = None, @@ -80,6 +84,7 @@ class MistralConfig(OpenAIGPTConfig): "temperature", "top_p", "max_tokens", + "max_completion_tokens" "tools", "tool_choice", "seed", @@ -105,6 +110,8 @@ class MistralConfig(OpenAIGPTConfig): for param, value in non_default_params.items(): if param == "max_tokens": optional_params["max_tokens"] = value + if param == "max_completion_tokens": # max_completion_tokens should take priority + optional_params["max_tokens"] = value if param == "tools": optional_params["tools"] = value if param == "stream" and value is True: diff --git a/tests/litellm/llms/mistral/test_mistral_transformation.py b/tests/litellm/llms/mistral/test_mistral_transformation.py new file mode 100644 index 0000000000..4594e8ca2a --- /dev/null +++ b/tests/litellm/llms/mistral/test_mistral_transformation.py @@ -0,0 +1,45 @@ +import os +import sys +from unittest.mock import MagicMock + + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.mistral.mistral_chat_transformation import MistralConfig + + +class TestMistralTransform: + def setup_method(self): + self.config = MistralConfig() + self.model = "mistral-small-latest" + self.logging_obj = MagicMock() + + def test_map_mistral_params(self): + """Test that parameters are correctly mapped""" + test_params = {"temperature": 0.7, "max_tokens": 200, "max_completion_tokens": 256} + + result = self.config.map_openai_params( + non_default_params=test_params, + optional_params={}, + model=self.model, + drop_params=False, + ) + + # The function should properly map max_completion_tokens to max_tokens and override max_tokens + assert result == {"temperature": 0.7, "max_tokens": 256} + + def test_mistral_max_tokens_backward_compat(self): + """Test that parameters are correctly mapped""" + test_params = {"temperature": 0.7, "max_tokens": 200,} + + result = self.config.map_openai_params( + non_default_params=test_params, + optional_params={}, + model=self.model, + drop_params=False, + ) + + # The function should properly map max_tokens if max_completion_tokens is not provided + assert result == {"temperature": 0.7, "max_tokens": 200} From 894306141e4a34393a86fa5a45e8fccd45a44792 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 18:07:23 -0700 Subject: [PATCH 122/244] refactor, use commit_update_transactions_to_db --- litellm/proxy/db/db_spend_update_writer.py | 250 ++++++++++++++++++++- litellm/proxy/utils.py | 200 +---------------- 2 files changed, 252 insertions(+), 198 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 34e8eae173..cc45b6b96a 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -7,16 +7,28 @@ Module responsible for import asyncio import os +import time import traceback -from datetime import datetime +from datetime import datetime, timedelta from typing import Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger from litellm.caching import DualCache -from litellm.proxy._types import Litellm_EntityType, LiteLLM_UserTable, SpendLogsPayload +from litellm.proxy._types import ( + DB_CONNECTION_ERROR_TYPES, + Litellm_EntityType, + LiteLLM_UserTable, + SpendLogsPayload, +) from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload -from litellm.proxy.utils import PrismaClient, ProxyUpdateSpend, hash_token +from litellm.proxy.utils import ( + PrismaClient, + ProxyLogging, + ProxyUpdateSpend, + _raise_failed_update_spend_exception, + hash_token, +) class DBSpendUpdateWriter: @@ -346,3 +358,235 @@ class DBSpendUpdateWriter: payload.copy() ) return prisma_client + + @staticmethod + async def commit_update_transactions_to_db( # noqa: PLR0915 + prisma_client: PrismaClient, + n_retry_times: int, + proxy_logging_obj: ProxyLogging, + ): + """ + Handles commiting update spend transactions to db + + UPDATES can lead to deadlocks, hence we handle them separately + + Args: + prisma_client: PrismaClient object + n_retry_times: int, number of retry times + proxy_logging_obj: ProxyLogging object + """ + ### UPDATE USER TABLE ### + if len(prisma_client.user_list_transactons.keys()) > 0: + for i in range(n_retry_times + 1): + start_time = time.time() + try: + async with prisma_client.db.tx( + timeout=timedelta(seconds=60) + ) as transaction: + async with transaction.batch_() as batcher: + for ( + user_id, + response_cost, + ) in prisma_client.user_list_transactons.items(): + batcher.litellm_usertable.update_many( + where={"user_id": user_id}, + data={"spend": {"increment": response_cost}}, + ) + prisma_client.user_list_transactons = ( + {} + ) # Clear the remaining transactions after processing all batches in the loop. + break + except DB_CONNECTION_ERROR_TYPES as e: + if ( + i >= n_retry_times + ): # If we've reached the maximum number of retries + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + # Optionally, sleep for a bit before retrying + await asyncio.sleep(2**i) # Exponential backoff + except Exception as e: + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) + + ### UPDATE END-USER TABLE ### + verbose_proxy_logger.debug( + "End-User Spend transactions: {}".format( + len(prisma_client.end_user_list_transactons.keys()) + ) + ) + if len(prisma_client.end_user_list_transactons.keys()) > 0: + await ProxyUpdateSpend.update_end_user_spend( + n_retry_times=n_retry_times, + prisma_client=prisma_client, + proxy_logging_obj=proxy_logging_obj, + ) + ### UPDATE KEY TABLE ### + verbose_proxy_logger.debug( + "KEY Spend transactions: {}".format( + len(prisma_client.key_list_transactons.keys()) + ) + ) + if len(prisma_client.key_list_transactons.keys()) > 0: + for i in range(n_retry_times + 1): + start_time = time.time() + try: + async with prisma_client.db.tx( + timeout=timedelta(seconds=60) + ) as transaction: + async with transaction.batch_() as batcher: + for ( + token, + response_cost, + ) in prisma_client.key_list_transactons.items(): + batcher.litellm_verificationtoken.update_many( # 'update_many' prevents error from being raised if no row exists + where={"token": token}, + data={"spend": {"increment": response_cost}}, + ) + prisma_client.key_list_transactons = ( + {} + ) # Clear the remaining transactions after processing all batches in the loop. + break + except DB_CONNECTION_ERROR_TYPES as e: + if ( + i >= n_retry_times + ): # If we've reached the maximum number of retries + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + # Optionally, sleep for a bit before retrying + await asyncio.sleep(2**i) # Exponential backoff + except Exception as e: + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) + + ### UPDATE TEAM TABLE ### + verbose_proxy_logger.debug( + "Team Spend transactions: {}".format( + len(prisma_client.team_list_transactons.keys()) + ) + ) + if len(prisma_client.team_list_transactons.keys()) > 0: + for i in range(n_retry_times + 1): + start_time = time.time() + try: + async with prisma_client.db.tx( + timeout=timedelta(seconds=60) + ) as transaction: + async with transaction.batch_() as batcher: + for ( + team_id, + response_cost, + ) in prisma_client.team_list_transactons.items(): + verbose_proxy_logger.debug( + "Updating spend for team id={} by {}".format( + team_id, response_cost + ) + ) + batcher.litellm_teamtable.update_many( # 'update_many' prevents error from being raised if no row exists + where={"team_id": team_id}, + data={"spend": {"increment": response_cost}}, + ) + prisma_client.team_list_transactons = ( + {} + ) # Clear the remaining transactions after processing all batches in the loop. + break + except DB_CONNECTION_ERROR_TYPES as e: + if ( + i >= n_retry_times + ): # If we've reached the maximum number of retries + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + # Optionally, sleep for a bit before retrying + await asyncio.sleep(2**i) # Exponential backoff + except Exception as e: + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) + + ### UPDATE TEAM Membership TABLE with spend ### + if len(prisma_client.team_member_list_transactons.keys()) > 0: + for i in range(n_retry_times + 1): + start_time = time.time() + try: + async with prisma_client.db.tx( + timeout=timedelta(seconds=60) + ) as transaction: + async with transaction.batch_() as batcher: + for ( + key, + response_cost, + ) in prisma_client.team_member_list_transactons.items(): + # key is "team_id::::user_id::" + team_id = key.split("::")[1] + user_id = key.split("::")[3] + + batcher.litellm_teammembership.update_many( # 'update_many' prevents error from being raised if no row exists + where={"team_id": team_id, "user_id": user_id}, + data={"spend": {"increment": response_cost}}, + ) + prisma_client.team_member_list_transactons = ( + {} + ) # Clear the remaining transactions after processing all batches in the loop. + break + except DB_CONNECTION_ERROR_TYPES as e: + if ( + i >= n_retry_times + ): # If we've reached the maximum number of retries + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + # Optionally, sleep for a bit before retrying + await asyncio.sleep(2**i) # Exponential backoff + except Exception as e: + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) + + ### UPDATE ORG TABLE ### + if len(prisma_client.org_list_transactons.keys()) > 0: + for i in range(n_retry_times + 1): + start_time = time.time() + try: + async with prisma_client.db.tx( + timeout=timedelta(seconds=60) + ) as transaction: + async with transaction.batch_() as batcher: + for ( + org_id, + response_cost, + ) in prisma_client.org_list_transactons.items(): + batcher.litellm_organizationtable.update_many( # 'update_many' prevents error from being raised if no row exists + where={"organization_id": org_id}, + data={"spend": {"increment": response_cost}}, + ) + prisma_client.org_list_transactons = ( + {} + ) # Clear the remaining transactions after processing all batches in the loop. + break + except DB_CONNECTION_ERROR_TYPES as e: + if ( + i >= n_retry_times + ): # If we've reached the maximum number of retries + _raise_failed_update_spend_exception( + e=e, + start_time=start_time, + proxy_logging_obj=proxy_logging_obj, + ) + # Optionally, sleep for a bit before retrying + await asyncio.sleep(2**i) # Exponential backoff + except Exception as e: + _raise_failed_update_spend_exception( + e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj + ) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 7f1ac814a8..013ecf97c4 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -62,6 +62,7 @@ from litellm.proxy.db.create_views import ( create_missing_views, should_create_missing_views, ) +from litellm.proxy.db.db_spend_update_writer import DBSpendUpdateWriter from litellm.proxy.db.log_db_metrics import log_db_metrics from litellm.proxy.db.prisma_client import PrismaWrapper from litellm.proxy.hooks.cache_control_check import _PROXY_CacheControlCheck @@ -2674,202 +2675,11 @@ async def update_spend( # noqa: PLR0915 spend_logs: list, """ n_retry_times = 3 - i = None - ### UPDATE USER TABLE ### - if len(prisma_client.user_list_transactons.keys()) > 0: - for i in range(n_retry_times + 1): - start_time = time.time() - try: - async with prisma_client.db.tx( - timeout=timedelta(seconds=60) - ) as transaction: - async with transaction.batch_() as batcher: - for ( - user_id, - response_cost, - ) in prisma_client.user_list_transactons.items(): - batcher.litellm_usertable.update_many( - where={"user_id": user_id}, - data={"spend": {"increment": response_cost}}, - ) - prisma_client.user_list_transactons = ( - {} - ) # Clear the remaining transactions after processing all batches in the loop. - break - except DB_CONNECTION_ERROR_TYPES as e: - if i >= n_retry_times: # If we've reached the maximum number of retries - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - # Optionally, sleep for a bit before retrying - await asyncio.sleep(2**i) # Exponential backoff - except Exception as e: - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - - ### UPDATE END-USER TABLE ### - verbose_proxy_logger.debug( - "End-User Spend transactions: {}".format( - len(prisma_client.end_user_list_transactons.keys()) - ) + await DBSpendUpdateWriter.commit_update_transactions_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, ) - if len(prisma_client.end_user_list_transactons.keys()) > 0: - await ProxyUpdateSpend.update_end_user_spend( - n_retry_times=n_retry_times, - prisma_client=prisma_client, - proxy_logging_obj=proxy_logging_obj, - ) - ### UPDATE KEY TABLE ### - verbose_proxy_logger.debug( - "KEY Spend transactions: {}".format( - len(prisma_client.key_list_transactons.keys()) - ) - ) - if len(prisma_client.key_list_transactons.keys()) > 0: - for i in range(n_retry_times + 1): - start_time = time.time() - try: - async with prisma_client.db.tx( - timeout=timedelta(seconds=60) - ) as transaction: - async with transaction.batch_() as batcher: - for ( - token, - response_cost, - ) in prisma_client.key_list_transactons.items(): - batcher.litellm_verificationtoken.update_many( # 'update_many' prevents error from being raised if no row exists - where={"token": token}, - data={"spend": {"increment": response_cost}}, - ) - prisma_client.key_list_transactons = ( - {} - ) # Clear the remaining transactions after processing all batches in the loop. - break - except DB_CONNECTION_ERROR_TYPES as e: - if i >= n_retry_times: # If we've reached the maximum number of retries - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - # Optionally, sleep for a bit before retrying - await asyncio.sleep(2**i) # Exponential backoff - except Exception as e: - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - - ### UPDATE TEAM TABLE ### - verbose_proxy_logger.debug( - "Team Spend transactions: {}".format( - len(prisma_client.team_list_transactons.keys()) - ) - ) - if len(prisma_client.team_list_transactons.keys()) > 0: - for i in range(n_retry_times + 1): - start_time = time.time() - try: - async with prisma_client.db.tx( - timeout=timedelta(seconds=60) - ) as transaction: - async with transaction.batch_() as batcher: - for ( - team_id, - response_cost, - ) in prisma_client.team_list_transactons.items(): - verbose_proxy_logger.debug( - "Updating spend for team id={} by {}".format( - team_id, response_cost - ) - ) - batcher.litellm_teamtable.update_many( # 'update_many' prevents error from being raised if no row exists - where={"team_id": team_id}, - data={"spend": {"increment": response_cost}}, - ) - prisma_client.team_list_transactons = ( - {} - ) # Clear the remaining transactions after processing all batches in the loop. - break - except DB_CONNECTION_ERROR_TYPES as e: - if i >= n_retry_times: # If we've reached the maximum number of retries - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - # Optionally, sleep for a bit before retrying - await asyncio.sleep(2**i) # Exponential backoff - except Exception as e: - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - - ### UPDATE TEAM Membership TABLE with spend ### - if len(prisma_client.team_member_list_transactons.keys()) > 0: - for i in range(n_retry_times + 1): - start_time = time.time() - try: - async with prisma_client.db.tx( - timeout=timedelta(seconds=60) - ) as transaction: - async with transaction.batch_() as batcher: - for ( - key, - response_cost, - ) in prisma_client.team_member_list_transactons.items(): - # key is "team_id::::user_id::" - team_id = key.split("::")[1] - user_id = key.split("::")[3] - - batcher.litellm_teammembership.update_many( # 'update_many' prevents error from being raised if no row exists - where={"team_id": team_id, "user_id": user_id}, - data={"spend": {"increment": response_cost}}, - ) - prisma_client.team_member_list_transactons = ( - {} - ) # Clear the remaining transactions after processing all batches in the loop. - break - except DB_CONNECTION_ERROR_TYPES as e: - if i >= n_retry_times: # If we've reached the maximum number of retries - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - # Optionally, sleep for a bit before retrying - await asyncio.sleep(2**i) # Exponential backoff - except Exception as e: - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - - ### UPDATE ORG TABLE ### - if len(prisma_client.org_list_transactons.keys()) > 0: - for i in range(n_retry_times + 1): - start_time = time.time() - try: - async with prisma_client.db.tx( - timeout=timedelta(seconds=60) - ) as transaction: - async with transaction.batch_() as batcher: - for ( - org_id, - response_cost, - ) in prisma_client.org_list_transactons.items(): - batcher.litellm_organizationtable.update_many( # 'update_many' prevents error from being raised if no row exists - where={"organization_id": org_id}, - data={"spend": {"increment": response_cost}}, - ) - prisma_client.org_list_transactons = ( - {} - ) # Clear the remaining transactions after processing all batches in the loop. - break - except DB_CONNECTION_ERROR_TYPES as e: - if i >= n_retry_times: # If we've reached the maximum number of retries - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) - # Optionally, sleep for a bit before retrying - await asyncio.sleep(2**i) # Exponential backoff - except Exception as e: - _raise_failed_update_spend_exception( - e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj - ) ### UPDATE SPEND LOGS ### verbose_proxy_logger.debug( From ad720781676e441b24eb7c772c1d853c23e9cd0a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 18:21:33 -0700 Subject: [PATCH 123/244] basic structure for commit update txs to redis --- litellm/proxy/db/db_spend_update_writer.py | 70 +++++++++++++++++++++- litellm/proxy/utils.py | 2 +- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index cc45b6b96a..d85e69c472 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -29,6 +29,7 @@ from litellm.proxy.utils import ( _raise_failed_update_spend_exception, hash_token, ) +from litellm.secret_managers.main import str_to_bool class DBSpendUpdateWriter: @@ -360,7 +361,7 @@ class DBSpendUpdateWriter: return prisma_client @staticmethod - async def commit_update_transactions_to_db( # noqa: PLR0915 + async def db_spend_transaction_handler( prisma_client: PrismaClient, n_retry_times: int, proxy_logging_obj: ProxyLogging, @@ -368,12 +369,77 @@ class DBSpendUpdateWriter: """ Handles commiting update spend transactions to db - UPDATES can lead to deadlocks, hence we handle them separately + `UPDATES` can lead to deadlocks, hence we handle them separately Args: prisma_client: PrismaClient object n_retry_times: int, number of retry times proxy_logging_obj: ProxyLogging object + + How this works: + - Check `general_settings.use_redis_transaction_buffer` + - If enabled, write in-memory transactions to Redis + - Check if this Pod should read from the DB + else: + - Regular flow of this method + """ + if DBSpendUpdateWriter._should_commit_spend_updates_to_redis(): + pass + + if DBSpendUpdateWriter._should_commit_spend_updates_to_db(): + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + ) + + pass + + @staticmethod + def _should_commit_spend_updates_to_redis() -> bool: + """ + Checks if the Pod should commit spend updates to Redis + + This setting enables buffering database transactions in Redis + to improve reliability and reduce database contention + """ + from litellm.proxy.proxy_server import general_settings + + _use_redis_transaction_buffer: Optional[Union[bool, str]] = ( + general_settings.get("use_redis_transaction_buffer", False) + ) + if isinstance(_use_redis_transaction_buffer, str): + _use_redis_transaction_buffer = str_to_bool(_use_redis_transaction_buffer) + if _use_redis_transaction_buffer is None: + return False + return _use_redis_transaction_buffer + + @staticmethod + async def _commit_spend_updates_to_redis( + prisma_client: PrismaClient, + ): + """ + Commits all the spend updates to Redis for each entity type + + once committed, the transactions are cleared from the in-memory variables + """ + pass + + @staticmethod + def _should_commit_spend_updates_to_db() -> bool: + """ + Checks if the Pod should commit spend updates to the Database + """ + return False + + @staticmethod + async def _commit_spend_updates_to_db( # noqa: PLR0915 + prisma_client: PrismaClient, + n_retry_times: int, + proxy_logging_obj: ProxyLogging, + ): + """ + Commits all the spend updates to the Database """ ### UPDATE USER TABLE ### if len(prisma_client.user_list_transactons.keys()) > 0: diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 013ecf97c4..435bf38c0e 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -2675,7 +2675,7 @@ async def update_spend( # noqa: PLR0915 spend_logs: list, """ n_retry_times = 3 - await DBSpendUpdateWriter.commit_update_transactions_to_db( + await DBSpendUpdateWriter._commit_spend_updates_to_db( prisma_client=prisma_client, n_retry_times=n_retry_times, proxy_logging_obj=proxy_logging_obj, From 963791bbb58f8d36c8551c6b46c3572d89a213f3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 19:12:51 -0700 Subject: [PATCH 124/244] use redis update buffer class --- litellm/proxy/db/db_spend_update_writer.py | 68 +++++++------ litellm/proxy/db/redis_update_buffer.py | 110 +++++++++++++++++++++ litellm/proxy/utils.py | 5 +- 3 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 litellm/proxy/db/redis_update_buffer.py diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index d85e69c472..00f60a76f2 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -10,26 +10,24 @@ import os import time import traceback from datetime import datetime, timedelta -from typing import Any, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger -from litellm.caching import DualCache +from litellm.caching import DualCache, RedisCache, RedisClusterCache from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, Litellm_EntityType, LiteLLM_UserTable, SpendLogsPayload, ) -from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload -from litellm.proxy.utils import ( - PrismaClient, - ProxyLogging, - ProxyUpdateSpend, - _raise_failed_update_spend_exception, - hash_token, -) -from litellm.secret_managers.main import str_to_bool +from litellm.proxy.db.redis_update_buffer import RedisUpdateBuffer + +if TYPE_CHECKING: + from litellm.proxy.utils import PrismaClient, ProxyLogging +else: + PrismaClient = Any + ProxyLogging = Any class DBSpendUpdateWriter: @@ -40,6 +38,12 @@ class DBSpendUpdateWriter: 2. Reading increments from redis or in memory list of transactions and committing them to db """ + def __init__( + self, redis_cache: Optional[Union[RedisCache, RedisClusterCache]] = None + ): + self.redis_cache = redis_cache + self.redis_update_buffer = RedisUpdateBuffer(redis_cache=redis_cache) + @staticmethod async def update_database( # LiteLLM management object fields @@ -61,6 +65,7 @@ class DBSpendUpdateWriter: prisma_client, user_api_key_cache, ) + from litellm.proxy.utils import ProxyUpdateSpend, hash_token try: verbose_proxy_logger.debug( @@ -315,6 +320,10 @@ class DBSpendUpdateWriter: response_cost: Optional[float], prisma_client: Optional[PrismaClient], ): + from litellm.proxy.spend_tracking.spend_tracking_utils import ( + get_logging_payload, + ) + try: if prisma_client: payload = get_logging_payload( @@ -360,8 +369,8 @@ class DBSpendUpdateWriter: ) return prisma_client - @staticmethod - async def db_spend_transaction_handler( + async def db_update_spend_transaction_handler( + self, prisma_client: PrismaClient, n_retry_times: int, proxy_logging_obj: ProxyLogging, @@ -383,8 +392,10 @@ class DBSpendUpdateWriter: else: - Regular flow of this method """ - if DBSpendUpdateWriter._should_commit_spend_updates_to_redis(): - pass + if RedisUpdateBuffer._should_commit_spend_updates_to_redis(): + await self.redis_update_buffer.store_in_memory_spend_updates_in_redis( + prisma_client=prisma_client, + ) if DBSpendUpdateWriter._should_commit_spend_updates_to_db(): await DBSpendUpdateWriter._commit_spend_updates_to_db( @@ -395,25 +406,6 @@ class DBSpendUpdateWriter: pass - @staticmethod - def _should_commit_spend_updates_to_redis() -> bool: - """ - Checks if the Pod should commit spend updates to Redis - - This setting enables buffering database transactions in Redis - to improve reliability and reduce database contention - """ - from litellm.proxy.proxy_server import general_settings - - _use_redis_transaction_buffer: Optional[Union[bool, str]] = ( - general_settings.get("use_redis_transaction_buffer", False) - ) - if isinstance(_use_redis_transaction_buffer, str): - _use_redis_transaction_buffer = str_to_bool(_use_redis_transaction_buffer) - if _use_redis_transaction_buffer is None: - return False - return _use_redis_transaction_buffer - @staticmethod async def _commit_spend_updates_to_redis( prisma_client: PrismaClient, @@ -439,8 +431,14 @@ class DBSpendUpdateWriter: proxy_logging_obj: ProxyLogging, ): """ - Commits all the spend updates to the Database + Commits all the spend `UPDATE` transactions to the Database + """ + from litellm.proxy.utils import ( + ProxyUpdateSpend, + _raise_failed_update_spend_exception, + ) + ### UPDATE USER TABLE ### if len(prisma_client.user_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py new file mode 100644 index 0000000000..843ec445a3 --- /dev/null +++ b/litellm/proxy/db/redis_update_buffer.py @@ -0,0 +1,110 @@ +""" +Handles buffering database `UPDATE` transactions in Redis before committing them to the database + +This is to prevent deadlocks and improve reliability +""" + +from typing import TYPE_CHECKING, Any, Dict, Optional, TypedDict, Union, cast + +from litellm.caching import RedisCache, RedisClusterCache +from litellm.secret_managers.main import str_to_bool + +if TYPE_CHECKING: + from litellm.proxy.utils import PrismaClient +else: + PrismaClient = Any + + +class DBSpendUpdateTransactions(TypedDict): + user_list_transactons: Dict[str, float] + end_user_list_transactons: Dict[str, float] + key_list_transactons: Dict[str, float] + team_list_transactons: Dict[str, float] + team_member_list_transactons: Dict[str, float] + org_list_transactons: Dict[str, float] + + +class RedisUpdateBuffer: + """ + Handles buffering database `UPDATE` transactions in Redis before committing them to the database + + This is to prevent deadlocks and improve reliability + """ + + def __init__( + self, redis_cache: Optional[Union[RedisCache, RedisClusterCache]] = None + ): + self.redis_cache = redis_cache + + @staticmethod + def _should_commit_spend_updates_to_redis() -> bool: + """ + Checks if the Pod should commit spend updates to Redis + + This setting enables buffering database transactions in Redis + to improve reliability and reduce database contention + """ + from litellm.proxy.proxy_server import general_settings + + _use_redis_transaction_buffer: Optional[Union[bool, str]] = ( + general_settings.get("use_redis_transaction_buffer", False) + ) + if isinstance(_use_redis_transaction_buffer, str): + _use_redis_transaction_buffer = str_to_bool(_use_redis_transaction_buffer) + if _use_redis_transaction_buffer is None: + return False + return _use_redis_transaction_buffer + + async def store_in_memory_spend_updates_in_redis( + self, + prisma_client: PrismaClient, + ): + """ + Stores the in-memory spend updates to Redis + + Each transaction is a dict stored as following: + - key is the entity id + - value is the spend amount + + ``` + { + "0929880201": 10, + "0929880202": 20, + "0929880203": 30, + } + ``` + """ + IN_MEMORY_UPDATE_TRANSACTIONS: DBSpendUpdateTransactions = ( + DBSpendUpdateTransactions( + user_list_transactons=prisma_client.user_list_transactons, + end_user_list_transactons=prisma_client.end_user_list_transactons, + key_list_transactons=prisma_client.key_list_transactons, + team_list_transactons=prisma_client.team_list_transactons, + team_member_list_transactons=prisma_client.team_member_list_transactons, + org_list_transactons=prisma_client.org_list_transactons, + ) + ) + for key, _transactions in IN_MEMORY_UPDATE_TRANSACTIONS.items(): + await self.increment_all_transaction_objects_in_redis( + key=key, + transactions=cast(Dict, _transactions), + ) + + async def increment_all_transaction_objects_in_redis( + self, + key: str, + transactions: Dict, + ): + """ + Increments all transaction objects in Redis + """ + if self.redis_cache is None: + return + for transaction_id, transaction_amount in transactions.items(): + await self.redis_cache.async_increment( + key=f"{key}:{transaction_id}", + value=transaction_amount, + ) + + async def get_all_update_transactions_from_redis(self): + pass diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 435bf38c0e..c208f60529 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -265,6 +265,9 @@ class ProxyLogging: ) self.premium_user = premium_user self.service_logging_obj = ServiceLogging() + self.db_spend_update_writer = DBSpendUpdateWriter( + redis_cache=self.internal_usage_cache.dual_cache.redis_cache + ) def startup_event( self, @@ -2675,7 +2678,7 @@ async def update_spend( # noqa: PLR0915 spend_logs: list, """ n_retry_times = 3 - await DBSpendUpdateWriter._commit_spend_updates_to_db( + await proxy_logging_obj.db_spend_update_writer.db_update_spend_transaction_handler( prisma_client=prisma_client, n_retry_times=n_retry_times, proxy_logging_obj=proxy_logging_obj, From b9d0f460e83bf7e0be171026c4839fe97f0d89bf Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 19:14:26 -0700 Subject: [PATCH 125/244] Revert "Support max_completion_tokens on Mistral (#9589)" (#9604) This reverts commit fef5d23dd5935ae5d791f07fdaa07b655449f9f9. --- .../mistral/mistral_chat_transformation.py | 9 +--- .../mistral/test_mistral_transformation.py | 45 ------------------- 2 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 tests/litellm/llms/mistral/test_mistral_transformation.py diff --git a/litellm/llms/mistral/mistral_chat_transformation.py b/litellm/llms/mistral/mistral_chat_transformation.py index 7f93ddcc29..3e7a97c92f 100644 --- a/litellm/llms/mistral/mistral_chat_transformation.py +++ b/litellm/llms/mistral/mistral_chat_transformation.py @@ -28,9 +28,7 @@ class MistralConfig(OpenAIGPTConfig): - `top_p` (number or null): An alternative to sampling with temperature, used for nucleus sampling. API Default - 1. - - `max_tokens` [DEPRECATED - use max_completion_tokens] (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. - - - `max_completion_tokens` (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. + - `max_tokens` (integer or null): This optional parameter helps to set the maximum number of tokens to generate in the chat completion. API Default - null. - `tools` (list or null): A list of available tools for the model. Use this to specify functions for which the model can generate JSON inputs. @@ -48,7 +46,6 @@ class MistralConfig(OpenAIGPTConfig): temperature: Optional[int] = None top_p: Optional[int] = None max_tokens: Optional[int] = None - max_completion_tokens: Optional[int] = None tools: Optional[list] = None tool_choice: Optional[Literal["auto", "any", "none"]] = None random_seed: Optional[int] = None @@ -61,7 +58,6 @@ class MistralConfig(OpenAIGPTConfig): temperature: Optional[int] = None, top_p: Optional[int] = None, max_tokens: Optional[int] = None, - max_completion_tokens: Optional[int] = None, tools: Optional[list] = None, tool_choice: Optional[Literal["auto", "any", "none"]] = None, random_seed: Optional[int] = None, @@ -84,7 +80,6 @@ class MistralConfig(OpenAIGPTConfig): "temperature", "top_p", "max_tokens", - "max_completion_tokens" "tools", "tool_choice", "seed", @@ -110,8 +105,6 @@ class MistralConfig(OpenAIGPTConfig): for param, value in non_default_params.items(): if param == "max_tokens": optional_params["max_tokens"] = value - if param == "max_completion_tokens": # max_completion_tokens should take priority - optional_params["max_tokens"] = value if param == "tools": optional_params["tools"] = value if param == "stream" and value is True: diff --git a/tests/litellm/llms/mistral/test_mistral_transformation.py b/tests/litellm/llms/mistral/test_mistral_transformation.py deleted file mode 100644 index 4594e8ca2a..0000000000 --- a/tests/litellm/llms/mistral/test_mistral_transformation.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import sys -from unittest.mock import MagicMock - - -sys.path.insert( - 0, os.path.abspath("../../../../..") -) # Adds the parent directory to the system path - -from litellm.llms.mistral.mistral_chat_transformation import MistralConfig - - -class TestMistralTransform: - def setup_method(self): - self.config = MistralConfig() - self.model = "mistral-small-latest" - self.logging_obj = MagicMock() - - def test_map_mistral_params(self): - """Test that parameters are correctly mapped""" - test_params = {"temperature": 0.7, "max_tokens": 200, "max_completion_tokens": 256} - - result = self.config.map_openai_params( - non_default_params=test_params, - optional_params={}, - model=self.model, - drop_params=False, - ) - - # The function should properly map max_completion_tokens to max_tokens and override max_tokens - assert result == {"temperature": 0.7, "max_tokens": 256} - - def test_mistral_max_tokens_backward_compat(self): - """Test that parameters are correctly mapped""" - test_params = {"temperature": 0.7, "max_tokens": 200,} - - result = self.config.map_openai_params( - non_default_params=test_params, - optional_params={}, - model=self.model, - drop_params=False, - ) - - # The function should properly map max_tokens if max_completion_tokens is not provided - assert result == {"temperature": 0.7, "max_tokens": 200} From e65b708fa2d2ff425143da10970be96c8bfc277a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 19:15:30 -0700 Subject: [PATCH 126/244] get_all_update_transactions_from_redis --- litellm/proxy/db/redis_update_buffer.py | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 843ec445a3..d3591032ff 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -106,5 +106,30 @@ class RedisUpdateBuffer: value=transaction_amount, ) - async def get_all_update_transactions_from_redis(self): - pass + async def get_all_update_transactions_from_redis( + self, + ) -> Optional[DBSpendUpdateTransactions]: + """ + Gets all the update transactions from Redis + """ + if self.redis_cache is None: + return None + expected_keys = [ + "user_list_transactons", + "end_user_list_transactons", + "key_list_transactons", + "team_list_transactons", + "team_member_list_transactons", + "org_list_transactons", + ] + result = await self.redis_cache.async_batch_get_cache(expected_keys) + if result is None: + return None + return DBSpendUpdateTransactions( + user_list_transactons=result.get("user_list_transactons", {}), + end_user_list_transactons=result.get("end_user_list_transactons", {}), + key_list_transactons=result.get("key_list_transactons", {}), + team_list_transactons=result.get("team_list_transactons", {}), + team_member_list_transactons=result.get("team_member_list_transactons", {}), + org_list_transactons=result.get("org_list_transactons", {}), + ) From e2d4597588dde22101b8faea10964a4390d88fc7 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 20:10:45 -0700 Subject: [PATCH 127/244] test: mark flaky test --- tests/proxy_unit_tests/test_key_generate_prisma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index bbfa5b1c11..c940f98915 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -3883,7 +3883,7 @@ async def test_get_paginated_teams(prisma_client): @pytest.mark.asyncio -@pytest.mark.flaky(reruns=3) +@pytest.mark.flaky(retries=3, delay=1) @pytest.mark.parametrize("entity_type", ["key", "user", "team"]) async def test_reset_budget_job(prisma_client, entity_type): """ From b3c77852400779c1394d2277f8f8c81b80e1e671 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 20:34:26 -0700 Subject: [PATCH 128/244] test: skip flaky test - failing due to db timeouts - unrelated to test --- tests/proxy_unit_tests/test_key_generate_prisma.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index c940f98915..99fc415c1b 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -3885,6 +3885,9 @@ async def test_get_paginated_teams(prisma_client): @pytest.mark.asyncio @pytest.mark.flaky(retries=3, delay=1) @pytest.mark.parametrize("entity_type", ["key", "user", "team"]) +@pytest.mark.skip( + reason="Skipping reset budget job test. Fails on ci/cd due to db timeout errors. Need to replace with mock db." +) async def test_reset_budget_job(prisma_client, entity_type): """ Test that the ResetBudgetJob correctly resets budgets for keys, users, and teams. From ea93a09b7a4632f6b5fcb47afdc4aa66df3ab944 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 21:10:26 -0700 Subject: [PATCH 129/244] add model CronJob --- litellm/proxy/schema.prisma | 15 +++++++++++++++ schema.prisma | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 9269e89014..0df675fd84 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -336,3 +336,18 @@ model LiteLLM_DailyUserSpend { @@index([api_key]) @@index([model]) } + + +// Track the status of cron jobs running. Only allow one pod to run the job at a time +model CronJob { + id String @id @default(cuid()) // Unique ID for the record + podId String // Unique identifier for the pod acting as the leader + status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) + lastUpdated DateTime @default(now()) // Timestamp for the last update of the cron job record + ttl DateTime // Time when the leader's lease expires +} + +enum JobStatus { + ACTIVE + INACTIVE +} diff --git a/schema.prisma b/schema.prisma index 3312b26354..5d1535d2ff 100644 --- a/schema.prisma +++ b/schema.prisma @@ -335,3 +335,18 @@ model LiteLLM_DailyUserSpend { @@index([api_key]) @@index([model]) } + + +// Track the status of cron jobs running. Only allow one pod to run the job at a time +model CronJob { + id String @id @default(cuid()) // Unique ID for the record + podId String // Unique identifier for the pod acting as the leader + status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) + lastUpdated DateTime @default(now()) // Timestamp for the last update of the cron job record + ttl DateTime // Time when the leader's lease expires +} + +enum JobStatus { + ACTIVE + INACTIVE +} From dd00429adecf49aeeca5f8ba7466a4bbb85ffbef Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 21:36:22 -0700 Subject: [PATCH 130/244] update schema --- litellm/constants.py | 4 ++++ litellm/proxy/schema.prisma | 1 + 2 files changed, 5 insertions(+) diff --git a/litellm/constants.py b/litellm/constants.py index da66f897c9..0d5a9dcddf 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -441,3 +441,7 @@ HEALTH_CHECK_TIMEOUT_SECONDS = 60 # 60 seconds UI_SESSION_TOKEN_TEAM_ID = "litellm-dashboard" LITELLM_PROXY_ADMIN_NAME = "default_user_id" + +########################### DB CRON JOB NAMES ########################### +DB_SPEND_UPDATE_JOB_NAME = "db_spend_update_job" +DEFAULT_CRON_JOB_LOCK_TTL_SECONDS = 600 # 5 minutes diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 0df675fd84..6e4937390a 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -351,3 +351,4 @@ enum JobStatus { ACTIVE INACTIVE } + From aa6c34cb9757159e6e92a7155baf46157ddf99a5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 21:43:02 -0700 Subject: [PATCH 131/244] use lock manager for update spend job --- litellm/proxy/db/db_spend_update_writer.py | 37 +++---- litellm/proxy/db/pod_leader_manager.py | 111 +++++++++++++++++++++ 2 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 litellm/proxy/db/pod_leader_manager.py diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 00f60a76f2..21717b8cbd 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -15,12 +15,14 @@ from typing import TYPE_CHECKING, Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger from litellm.caching import DualCache, RedisCache, RedisClusterCache +from litellm.constants import DB_SPEND_UPDATE_JOB_NAME from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, Litellm_EntityType, LiteLLM_UserTable, SpendLogsPayload, ) +from litellm.proxy.db.pod_leader_manager import PodLockManager from litellm.proxy.db.redis_update_buffer import RedisUpdateBuffer if TYPE_CHECKING: @@ -41,8 +43,14 @@ class DBSpendUpdateWriter: def __init__( self, redis_cache: Optional[Union[RedisCache, RedisClusterCache]] = None ): + from litellm.proxy.proxy_server import prisma_client + self.redis_cache = redis_cache self.redis_update_buffer = RedisUpdateBuffer(redis_cache=redis_cache) + self.pod_leader_manager = PodLockManager( + cronjob_id=DB_SPEND_UPDATE_JOB_NAME, + prisma_client=prisma_client, + ) @staticmethod async def update_database( @@ -397,33 +405,20 @@ class DBSpendUpdateWriter: prisma_client=prisma_client, ) - if DBSpendUpdateWriter._should_commit_spend_updates_to_db(): + if await self.pod_leader_manager.acquire_lock(): + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + ) + await self.pod_leader_manager.release_lock() + else: await DBSpendUpdateWriter._commit_spend_updates_to_db( prisma_client=prisma_client, n_retry_times=n_retry_times, proxy_logging_obj=proxy_logging_obj, ) - pass - - @staticmethod - async def _commit_spend_updates_to_redis( - prisma_client: PrismaClient, - ): - """ - Commits all the spend updates to Redis for each entity type - - once committed, the transactions are cleared from the in-memory variables - """ - pass - - @staticmethod - def _should_commit_spend_updates_to_db() -> bool: - """ - Checks if the Pod should commit spend updates to the Database - """ - return False - @staticmethod async def _commit_spend_updates_to_db( # noqa: PLR0915 prisma_client: PrismaClient, diff --git a/litellm/proxy/db/pod_leader_manager.py b/litellm/proxy/db/pod_leader_manager.py new file mode 100644 index 0000000000..73ecd68b1b --- /dev/null +++ b/litellm/proxy/db/pod_leader_manager.py @@ -0,0 +1,111 @@ +import uuid +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Any, Optional + +from litellm._logging import verbose_proxy_logger +from litellm.constants import DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + +if TYPE_CHECKING: + from litellm.proxy.utils import PrismaClient, ProxyLogging +else: + PrismaClient = Any + ProxyLogging = Any + + +class PodLockManager: + """ + Manager for acquiring and releasing locks for cron jobs. + + Ensures that only one pod can run a cron job at a time. + """ + + def __init__(self, prisma_client: Optional[PrismaClient], cronjob_id: str): + self.pod_id = str(uuid.uuid4()) + self.prisma = prisma_client + self.cronjob_id = cronjob_id + + async def acquire_lock(self) -> bool: + """ + Attempt to acquire the lock for a specific cron job. + """ + if not self.prisma: + return False + try: + current_time = datetime.now(timezone.utc) + # Lease expiry time + ttl_expiry = current_time + timedelta( + seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + ) + + # Attempt to acquire the lock by upserting the record in the `cronjob_locks` table + cronjob_lock = await self.prisma.db.cronJob.upsert( + where={"cronjob_id": self.cronjob_id}, + create={ + "cronjob_id": self.cronjob_id, + "pod_id": self.pod_id, + "status": "ACTIVE", + "last_updated": current_time, + "ttl": ttl_expiry, + }, + update={ + "status": "ACTIVE", + "last_updated": current_time, + "ttl": ttl_expiry, + }, + ) + + if cronjob_lock.status == "ACTIVE" and cronjob_lock.pod_id == self.pod_id: + verbose_proxy_logger.debug( + f"Pod {self.pod_id} has acquired the lock for {self.cronjob_id}." + ) + return True # Lock successfully acquired + return False + except Exception as e: + verbose_proxy_logger.error( + f"Error acquiring the lock for {self.cronjob_id}: {e}" + ) + return False + + async def renew_lock(self): + """ + Renew the lock (update the TTL) for the pod holding the lock. + """ + if not self.prisma: + return False + try: + current_time = datetime.now(timezone.utc) + # Extend the TTL for another DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + ttl_expiry = current_time + timedelta( + seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + ) + + await self.prisma.db.cronJob.update( + where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, + data={"ttl": ttl_expiry, "last_updated": current_time}, + ) + verbose_proxy_logger.info( + f"Renewed the lock for Pod {self.pod_id} for {self.cronjob_id}" + ) + except Exception as e: + verbose_proxy_logger.error( + f"Error renewing the lock for {self.cronjob_id}: {e}" + ) + + async def release_lock(self): + """ + Release the lock and mark the pod as inactive. + """ + if not self.prisma: + return False + try: + await self.prisma.db.cronJob.update( + where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, + data={"status": "INACTIVE"}, + ) + verbose_proxy_logger.info( + f"Pod {self.pod_id} has released the lock for {self.cronjob_id}." + ) + except Exception as e: + verbose_proxy_logger.error( + f"Error releasing the lock for {self.cronjob_id}: {e}" + ) From 91392305316200a0075e8d2c5a538f0bc8842fc9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 21:54:12 -0700 Subject: [PATCH 132/244] update the correct set of txs --- litellm/proxy/db/db_spend_update_writer.py | 64 +++++++++++++++------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 21717b8cbd..c49d5aa989 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -23,7 +23,10 @@ from litellm.proxy._types import ( SpendLogsPayload, ) from litellm.proxy.db.pod_leader_manager import PodLockManager -from litellm.proxy.db.redis_update_buffer import RedisUpdateBuffer +from litellm.proxy.db.redis_update_buffer import ( + DBSpendUpdateTransactions, + RedisUpdateBuffer, +) if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient, ProxyLogging @@ -406,17 +409,31 @@ class DBSpendUpdateWriter: ) if await self.pod_leader_manager.acquire_lock(): - await DBSpendUpdateWriter._commit_spend_updates_to_db( - prisma_client=prisma_client, - n_retry_times=n_retry_times, - proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions = ( + await self.redis_update_buffer.get_all_update_transactions_from_redis() ) + if db_spend_update_transactions is not None: + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions=db_spend_update_transactions, + ) await self.pod_leader_manager.release_lock() else: + db_spend_update_transactions = DBSpendUpdateTransactions( + user_list_transactons=prisma_client.user_list_transactons, + end_user_list_transactons=prisma_client.end_user_list_transactons, + key_list_transactons=prisma_client.key_list_transactons, + team_list_transactons=prisma_client.team_list_transactons, + team_member_list_transactons=prisma_client.team_member_list_transactons, + org_list_transactons=prisma_client.org_list_transactons, + ) await DBSpendUpdateWriter._commit_spend_updates_to_db( prisma_client=prisma_client, n_retry_times=n_retry_times, proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions=db_spend_update_transactions, ) @staticmethod @@ -424,6 +441,7 @@ class DBSpendUpdateWriter: prisma_client: PrismaClient, n_retry_times: int, proxy_logging_obj: ProxyLogging, + db_spend_update_transactions: DBSpendUpdateTransactions, ): """ Commits all the spend `UPDATE` transactions to the Database @@ -435,7 +453,8 @@ class DBSpendUpdateWriter: ) ### UPDATE USER TABLE ### - if len(prisma_client.user_list_transactons.keys()) > 0: + user_list_transactons = db_spend_update_transactions["user_list_transactons"] + if len(user_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -446,7 +465,7 @@ class DBSpendUpdateWriter: for ( user_id, response_cost, - ) in prisma_client.user_list_transactons.items(): + ) in user_list_transactons.items(): batcher.litellm_usertable.update_many( where={"user_id": user_id}, data={"spend": {"increment": response_cost}}, @@ -477,19 +496,21 @@ class DBSpendUpdateWriter: len(prisma_client.end_user_list_transactons.keys()) ) ) - if len(prisma_client.end_user_list_transactons.keys()) > 0: + end_user_list_transactons = db_spend_update_transactions[ + "end_user_list_transactons" + ] + if len(end_user_list_transactons.keys()) > 0: await ProxyUpdateSpend.update_end_user_spend( n_retry_times=n_retry_times, prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj, ) ### UPDATE KEY TABLE ### + key_list_transactons = db_spend_update_transactions["key_list_transactons"] verbose_proxy_logger.debug( - "KEY Spend transactions: {}".format( - len(prisma_client.key_list_transactons.keys()) - ) + "KEY Spend transactions: {}".format(len(key_list_transactons.keys())) ) - if len(prisma_client.key_list_transactons.keys()) > 0: + if len(key_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -500,7 +521,7 @@ class DBSpendUpdateWriter: for ( token, response_cost, - ) in prisma_client.key_list_transactons.items(): + ) in key_list_transactons.items(): batcher.litellm_verificationtoken.update_many( # 'update_many' prevents error from being raised if no row exists where={"token": token}, data={"spend": {"increment": response_cost}}, @@ -531,7 +552,8 @@ class DBSpendUpdateWriter: len(prisma_client.team_list_transactons.keys()) ) ) - if len(prisma_client.team_list_transactons.keys()) > 0: + team_list_transactons = db_spend_update_transactions["team_list_transactons"] + if len(team_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -542,7 +564,7 @@ class DBSpendUpdateWriter: for ( team_id, response_cost, - ) in prisma_client.team_list_transactons.items(): + ) in team_list_transactons.items(): verbose_proxy_logger.debug( "Updating spend for team id={} by {}".format( team_id, response_cost @@ -573,7 +595,10 @@ class DBSpendUpdateWriter: ) ### UPDATE TEAM Membership TABLE with spend ### - if len(prisma_client.team_member_list_transactons.keys()) > 0: + team_member_list_transactons = db_spend_update_transactions[ + "team_member_list_transactons" + ] + if len(team_member_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -584,7 +609,7 @@ class DBSpendUpdateWriter: for ( key, response_cost, - ) in prisma_client.team_member_list_transactons.items(): + ) in team_member_list_transactons.items(): # key is "team_id::::user_id::" team_id = key.split("::")[1] user_id = key.split("::")[3] @@ -614,7 +639,8 @@ class DBSpendUpdateWriter: ) ### UPDATE ORG TABLE ### - if len(prisma_client.org_list_transactons.keys()) > 0: + org_list_transactons = db_spend_update_transactions["org_list_transactons"] + if len(org_list_transactons.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -625,7 +651,7 @@ class DBSpendUpdateWriter: for ( org_id, response_cost, - ) in prisma_client.org_list_transactons.items(): + ) in org_list_transactons.items(): batcher.litellm_organizationtable.update_many( # 'update_many' prevents error from being raised if no row exists where={"organization_id": org_id}, data={"spend": {"increment": response_cost}}, From 79175ddb538d480e4d29874334d9f207d22626a2 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 22:04:59 -0700 Subject: [PATCH 133/244] test: fix test --- tests/proxy_unit_tests/test_update_spend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/proxy_unit_tests/test_update_spend.py b/tests/proxy_unit_tests/test_update_spend.py index 6efc68a077..36965cafa7 100644 --- a/tests/proxy_unit_tests/test_update_spend.py +++ b/tests/proxy_unit_tests/test_update_spend.py @@ -28,6 +28,7 @@ class MockPrismaClient: self.team_list_transactons = {} self.team_member_list_transactons = {} self.org_list_transactons = {} + self.daily_user_spend_transactions = {} def jsonify_object(self, obj): return obj From 340a63bace76d4f833355cc3a5b5e4996f99740f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 22:16:21 -0700 Subject: [PATCH 134/244] fix(mistral_chat_transformation.py): add missing comma (#9606) --- litellm/llms/mistral/mistral_chat_transformation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/litellm/llms/mistral/mistral_chat_transformation.py b/litellm/llms/mistral/mistral_chat_transformation.py index 3e7a97c92f..67d88868d3 100644 --- a/litellm/llms/mistral/mistral_chat_transformation.py +++ b/litellm/llms/mistral/mistral_chat_transformation.py @@ -80,6 +80,7 @@ class MistralConfig(OpenAIGPTConfig): "temperature", "top_p", "max_tokens", + "max_completion_tokens", "tools", "tool_choice", "seed", @@ -105,6 +106,10 @@ class MistralConfig(OpenAIGPTConfig): for param, value in non_default_params.items(): if param == "max_tokens": optional_params["max_tokens"] = value + if ( + param == "max_completion_tokens" + ): # max_completion_tokens should take priority + optional_params["max_tokens"] = value if param == "tools": optional_params["tools"] = value if param == "stream" and value is True: From 1bfffadd052a87236ed4cd3cc3fdd4812c00c469 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 22:31:38 -0700 Subject: [PATCH 135/244] fix typo --- litellm/proxy/proxy_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index d7e62f98d0..91d700b843 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1532,7 +1532,7 @@ class ProxyConfig: cache_params = {} if "cache_params" in litellm_settings: cache_params_in_config = litellm_settings["cache_params"] - # overwrie cache_params with cache_params_in_config + # overwrite cache_params with cache_params_in_config cache_params.update(cache_params_in_config) cache_type = cache_params.get("type", "redis") From fc46f6b86126b76d292d3e512ae079a1ebe8f032 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 22:34:15 -0700 Subject: [PATCH 136/244] fix db spend update buffer --- litellm/proxy/db/db_spend_update_writer.py | 8 +++++--- litellm/proxy/db/redis_update_buffer.py | 9 +++++++-- litellm/proxy/proxy_config.yaml | 7 ++++++- litellm/proxy/utils.py | 5 ++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index c49d5aa989..0183fa433b 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any, Optional, Union import litellm from litellm._logging import verbose_proxy_logger -from litellm.caching import DualCache, RedisCache, RedisClusterCache +from litellm.caching import DualCache, RedisCache from litellm.constants import DB_SPEND_UPDATE_JOB_NAME from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, @@ -44,12 +44,13 @@ class DBSpendUpdateWriter: """ def __init__( - self, redis_cache: Optional[Union[RedisCache, RedisClusterCache]] = None + self, + redis_cache: Optional[RedisCache] = None, ): from litellm.proxy.proxy_server import prisma_client self.redis_cache = redis_cache - self.redis_update_buffer = RedisUpdateBuffer(redis_cache=redis_cache) + self.redis_update_buffer = RedisUpdateBuffer(redis_cache=self.redis_cache) self.pod_leader_manager = PodLockManager( cronjob_id=DB_SPEND_UPDATE_JOB_NAME, prisma_client=prisma_client, @@ -408,6 +409,7 @@ class DBSpendUpdateWriter: prisma_client=prisma_client, ) + # Only commit from redis to db if this pod is the leader if await self.pod_leader_manager.acquire_lock(): db_spend_update_transactions = ( await self.redis_update_buffer.get_all_update_transactions_from_redis() diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index d3591032ff..ea7f14d321 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -6,7 +6,8 @@ This is to prevent deadlocks and improve reliability from typing import TYPE_CHECKING, Any, Dict, Optional, TypedDict, Union, cast -from litellm.caching import RedisCache, RedisClusterCache +from litellm._logging import verbose_proxy_logger +from litellm.caching import RedisCache from litellm.secret_managers.main import str_to_bool if TYPE_CHECKING: @@ -32,7 +33,8 @@ class RedisUpdateBuffer: """ def __init__( - self, redis_cache: Optional[Union[RedisCache, RedisClusterCache]] = None + self, + redis_cache: Optional[RedisCache] = None, ): self.redis_cache = redis_cache @@ -99,6 +101,9 @@ class RedisUpdateBuffer: Increments all transaction objects in Redis """ if self.redis_cache is None: + verbose_proxy_logger.debug( + "redis_cache is None, skipping increment_all_transaction_objects_in_redis" + ) return for transaction_id, transaction_amount in transactions.items(): await self.redis_cache.async_increment( diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 4912a35f89..a1be54421b 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -6,4 +6,9 @@ model_list: api_base: https://exampleopenaiendpoint-production.up.railway.app/ general_settings: - allow_requests_on_db_unavailable: True \ No newline at end of file + use_redis_transaction_buffer: True + +litellm_settings: + cache: true + cache_params: + type: redis diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index c208f60529..040c6c14ef 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -265,9 +265,7 @@ class ProxyLogging: ) self.premium_user = premium_user self.service_logging_obj = ServiceLogging() - self.db_spend_update_writer = DBSpendUpdateWriter( - redis_cache=self.internal_usage_cache.dual_cache.redis_cache - ) + self.db_spend_update_writer = DBSpendUpdateWriter() def startup_event( self, @@ -340,6 +338,7 @@ class ProxyLogging: if redis_cache is not None: self.internal_usage_cache.dual_cache.redis_cache = redis_cache + self.db_spend_update_writer.redis_update_buffer.redis_cache = redis_cache def _init_litellm_callbacks(self, llm_router: Optional[Router] = None): litellm.logging_callback_manager.add_litellm_callback(self.max_parallel_request_limiter) # type: ignore From 758182fc7f66a96403013ffe0ba970bec635ea2d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 22:36:00 -0700 Subject: [PATCH 137/244] fix typo on codebase --- litellm/proxy/db/db_spend_update_writer.py | 78 ++++++++++----------- litellm/proxy/db/redis_update_buffer.py | 50 ++++++------- litellm/proxy/utils.py | 16 ++--- tests/local_testing/test_update_spend.py | 2 +- tests/proxy_unit_tests/test_proxy_utils.py | 4 +- tests/proxy_unit_tests/test_update_spend.py | 12 ++-- 6 files changed, 82 insertions(+), 80 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 0183fa433b..7184262ee5 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -201,7 +201,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=hashed_token, - transaction_list=prisma_client.key_list_transactons, + transaction_list=prisma_client.key_list_transactions, entity_type=Litellm_EntityType.KEY, debug_msg=f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}.", ) @@ -241,7 +241,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=_id, - transaction_list=prisma_client.user_list_transactons, + transaction_list=prisma_client.user_list_transactions, entity_type=Litellm_EntityType.USER, ) @@ -249,7 +249,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=end_user_id, - transaction_list=prisma_client.end_user_list_transactons, + transaction_list=prisma_client.end_user_list_transactions, entity_type=Litellm_EntityType.END_USER, ) except Exception as e: @@ -275,7 +275,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=team_id, - transaction_list=prisma_client.team_list_transactons, + transaction_list=prisma_client.team_list_transactions, entity_type=Litellm_EntityType.TEAM, ) @@ -287,7 +287,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=team_member_key, - transaction_list=prisma_client.team_member_list_transactons, + transaction_list=prisma_client.team_member_list_transactions, entity_type=Litellm_EntityType.TEAM_MEMBER, ) except Exception: @@ -314,7 +314,7 @@ class DBSpendUpdateWriter: await DBSpendUpdateWriter._update_transaction_list( response_cost=response_cost, entity_id=org_id, - transaction_list=prisma_client.org_list_transactons, + transaction_list=prisma_client.org_list_transactions, entity_type=Litellm_EntityType.ORGANIZATION, ) except Exception as e: @@ -424,12 +424,12 @@ class DBSpendUpdateWriter: await self.pod_leader_manager.release_lock() else: db_spend_update_transactions = DBSpendUpdateTransactions( - user_list_transactons=prisma_client.user_list_transactons, - end_user_list_transactons=prisma_client.end_user_list_transactons, - key_list_transactons=prisma_client.key_list_transactons, - team_list_transactons=prisma_client.team_list_transactons, - team_member_list_transactons=prisma_client.team_member_list_transactons, - org_list_transactons=prisma_client.org_list_transactons, + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, ) await DBSpendUpdateWriter._commit_spend_updates_to_db( prisma_client=prisma_client, @@ -455,8 +455,8 @@ class DBSpendUpdateWriter: ) ### UPDATE USER TABLE ### - user_list_transactons = db_spend_update_transactions["user_list_transactons"] - if len(user_list_transactons.keys()) > 0: + user_list_transactions = db_spend_update_transactions["user_list_transactions"] + if len(user_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -467,12 +467,12 @@ class DBSpendUpdateWriter: for ( user_id, response_cost, - ) in user_list_transactons.items(): + ) in user_list_transactions.items(): batcher.litellm_usertable.update_many( where={"user_id": user_id}, data={"spend": {"increment": response_cost}}, ) - prisma_client.user_list_transactons = ( + prisma_client.user_list_transactions = ( {} ) # Clear the remaining transactions after processing all batches in the loop. break @@ -495,24 +495,24 @@ class DBSpendUpdateWriter: ### UPDATE END-USER TABLE ### verbose_proxy_logger.debug( "End-User Spend transactions: {}".format( - len(prisma_client.end_user_list_transactons.keys()) + len(prisma_client.end_user_list_transactions.keys()) ) ) - end_user_list_transactons = db_spend_update_transactions[ - "end_user_list_transactons" + end_user_list_transactions = db_spend_update_transactions[ + "end_user_list_transactions" ] - if len(end_user_list_transactons.keys()) > 0: + if len(end_user_list_transactions.keys()) > 0: await ProxyUpdateSpend.update_end_user_spend( n_retry_times=n_retry_times, prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj, ) ### UPDATE KEY TABLE ### - key_list_transactons = db_spend_update_transactions["key_list_transactons"] + key_list_transactions = db_spend_update_transactions["key_list_transactions"] verbose_proxy_logger.debug( - "KEY Spend transactions: {}".format(len(key_list_transactons.keys())) + "KEY Spend transactions: {}".format(len(key_list_transactions.keys())) ) - if len(key_list_transactons.keys()) > 0: + if len(key_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -523,12 +523,12 @@ class DBSpendUpdateWriter: for ( token, response_cost, - ) in key_list_transactons.items(): + ) in key_list_transactions.items(): batcher.litellm_verificationtoken.update_many( # 'update_many' prevents error from being raised if no row exists where={"token": token}, data={"spend": {"increment": response_cost}}, ) - prisma_client.key_list_transactons = ( + prisma_client.key_list_transactions = ( {} ) # Clear the remaining transactions after processing all batches in the loop. break @@ -551,11 +551,11 @@ class DBSpendUpdateWriter: ### UPDATE TEAM TABLE ### verbose_proxy_logger.debug( "Team Spend transactions: {}".format( - len(prisma_client.team_list_transactons.keys()) + len(prisma_client.team_list_transactions.keys()) ) ) - team_list_transactons = db_spend_update_transactions["team_list_transactons"] - if len(team_list_transactons.keys()) > 0: + team_list_transactions = db_spend_update_transactions["team_list_transactions"] + if len(team_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -566,7 +566,7 @@ class DBSpendUpdateWriter: for ( team_id, response_cost, - ) in team_list_transactons.items(): + ) in team_list_transactions.items(): verbose_proxy_logger.debug( "Updating spend for team id={} by {}".format( team_id, response_cost @@ -576,7 +576,7 @@ class DBSpendUpdateWriter: where={"team_id": team_id}, data={"spend": {"increment": response_cost}}, ) - prisma_client.team_list_transactons = ( + prisma_client.team_list_transactions = ( {} ) # Clear the remaining transactions after processing all batches in the loop. break @@ -597,10 +597,10 @@ class DBSpendUpdateWriter: ) ### UPDATE TEAM Membership TABLE with spend ### - team_member_list_transactons = db_spend_update_transactions[ - "team_member_list_transactons" + team_member_list_transactions = db_spend_update_transactions[ + "team_member_list_transactions" ] - if len(team_member_list_transactons.keys()) > 0: + if len(team_member_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -611,7 +611,7 @@ class DBSpendUpdateWriter: for ( key, response_cost, - ) in team_member_list_transactons.items(): + ) in team_member_list_transactions.items(): # key is "team_id::::user_id::" team_id = key.split("::")[1] user_id = key.split("::")[3] @@ -620,7 +620,7 @@ class DBSpendUpdateWriter: where={"team_id": team_id, "user_id": user_id}, data={"spend": {"increment": response_cost}}, ) - prisma_client.team_member_list_transactons = ( + prisma_client.team_member_list_transactions = ( {} ) # Clear the remaining transactions after processing all batches in the loop. break @@ -641,8 +641,8 @@ class DBSpendUpdateWriter: ) ### UPDATE ORG TABLE ### - org_list_transactons = db_spend_update_transactions["org_list_transactons"] - if len(org_list_transactons.keys()) > 0: + org_list_transactions = db_spend_update_transactions["org_list_transactions"] + if len(org_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -653,12 +653,12 @@ class DBSpendUpdateWriter: for ( org_id, response_cost, - ) in org_list_transactons.items(): + ) in org_list_transactions.items(): batcher.litellm_organizationtable.update_many( # 'update_many' prevents error from being raised if no row exists where={"organization_id": org_id}, data={"spend": {"increment": response_cost}}, ) - prisma_client.org_list_transactons = ( + prisma_client.org_list_transactions = ( {} ) # Clear the remaining transactions after processing all batches in the loop. break diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index ea7f14d321..22afc56483 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -17,12 +17,12 @@ else: class DBSpendUpdateTransactions(TypedDict): - user_list_transactons: Dict[str, float] - end_user_list_transactons: Dict[str, float] - key_list_transactons: Dict[str, float] - team_list_transactons: Dict[str, float] - team_member_list_transactons: Dict[str, float] - org_list_transactons: Dict[str, float] + user_list_transactions: Dict[str, float] + end_user_list_transactions: Dict[str, float] + key_list_transactions: Dict[str, float] + team_list_transactions: Dict[str, float] + team_member_list_transactions: Dict[str, float] + org_list_transactions: Dict[str, float] class RedisUpdateBuffer: @@ -78,12 +78,12 @@ class RedisUpdateBuffer: """ IN_MEMORY_UPDATE_TRANSACTIONS: DBSpendUpdateTransactions = ( DBSpendUpdateTransactions( - user_list_transactons=prisma_client.user_list_transactons, - end_user_list_transactons=prisma_client.end_user_list_transactons, - key_list_transactons=prisma_client.key_list_transactons, - team_list_transactons=prisma_client.team_list_transactons, - team_member_list_transactons=prisma_client.team_member_list_transactons, - org_list_transactons=prisma_client.org_list_transactons, + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, ) ) for key, _transactions in IN_MEMORY_UPDATE_TRANSACTIONS.items(): @@ -120,21 +120,23 @@ class RedisUpdateBuffer: if self.redis_cache is None: return None expected_keys = [ - "user_list_transactons", - "end_user_list_transactons", - "key_list_transactons", - "team_list_transactons", - "team_member_list_transactons", - "org_list_transactons", + "user_list_transactions", + "end_user_list_transactions", + "key_list_transactions", + "team_list_transactions", + "team_member_list_transactions", + "org_list_transactions", ] result = await self.redis_cache.async_batch_get_cache(expected_keys) if result is None: return None return DBSpendUpdateTransactions( - user_list_transactons=result.get("user_list_transactons", {}), - end_user_list_transactons=result.get("end_user_list_transactons", {}), - key_list_transactons=result.get("key_list_transactons", {}), - team_list_transactons=result.get("team_list_transactons", {}), - team_member_list_transactons=result.get("team_member_list_transactons", {}), - org_list_transactons=result.get("org_list_transactons", {}), + user_list_transactions=result.get("user_list_transactions", {}), + end_user_list_transactions=result.get("end_user_list_transactions", {}), + key_list_transactions=result.get("key_list_transactions", {}), + team_list_transactions=result.get("team_list_transactions", {}), + team_member_list_transactions=result.get( + "team_member_list_transactions", {} + ), + org_list_transactions=result.get("org_list_transactions", {}), ) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 040c6c14ef..d0d4ea9b4e 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1101,12 +1101,12 @@ def jsonify_object(data: dict) -> dict: class PrismaClient: - user_list_transactons: dict = {} - end_user_list_transactons: dict = {} - key_list_transactons: dict = {} - team_list_transactons: dict = {} - team_member_list_transactons: dict = {} # key is ["team_id" + "user_id"] - org_list_transactons: dict = {} + user_list_transactions: dict = {} + end_user_list_transactions: dict = {} + key_list_transactions: dict = {} + team_list_transactions: dict = {} + team_member_list_transactions: dict = {} # key is ["team_id" + "user_id"] + org_list_transactions: dict = {} spend_log_transactions: List = [] daily_user_spend_transactions: Dict[str, DailyUserSpendTransaction] = {} @@ -2433,7 +2433,7 @@ class ProxyUpdateSpend: for ( end_user_id, response_cost, - ) in prisma_client.end_user_list_transactons.items(): + ) in prisma_client.end_user_list_transactions.items(): if litellm.max_end_user_budget is not None: pass batcher.litellm_endusertable.upsert( @@ -2461,7 +2461,7 @@ class ProxyUpdateSpend: e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj ) finally: - prisma_client.end_user_list_transactons = ( + prisma_client.end_user_list_transactions = ( {} ) # reset the end user list transactions - prevent bad data from causing issues diff --git a/tests/local_testing/test_update_spend.py b/tests/local_testing/test_update_spend.py index 6aeae851ab..fffa3062d7 100644 --- a/tests/local_testing/test_update_spend.py +++ b/tests/local_testing/test_update_spend.py @@ -93,7 +93,7 @@ def prisma_client(): @pytest.mark.asyncio async def test_batch_update_spend(prisma_client): - prisma_client.user_list_transactons["test-litellm-user-5"] = 23 + prisma_client.user_list_transactions["test-litellm-user-5"] = 23 setattr(litellm.proxy.proxy_server, "prisma_client", prisma_client) setattr(litellm.proxy.proxy_server, "master_key", "sk-1234") await litellm.proxy.proxy_server.prisma_client.connect() diff --git a/tests/proxy_unit_tests/test_proxy_utils.py b/tests/proxy_unit_tests/test_proxy_utils.py index dccf0d1842..d613118fc8 100644 --- a/tests/proxy_unit_tests/test_proxy_utils.py +++ b/tests/proxy_unit_tests/test_proxy_utils.py @@ -1509,7 +1509,7 @@ from litellm.proxy.utils import ProxyUpdateSpend async def test_end_user_transactions_reset(): # Setup mock_client = MagicMock() - mock_client.end_user_list_transactons = {"1": 10.0} # Bad log + mock_client.end_user_list_transactions = {"1": 10.0} # Bad log mock_client.db.tx = AsyncMock(side_effect=Exception("DB Error")) # Call function - should raise error @@ -1520,7 +1520,7 @@ async def test_end_user_transactions_reset(): # Verify cleanup happened assert ( - mock_client.end_user_list_transactons == {} + mock_client.end_user_list_transactions == {} ), "Transactions list should be empty after error" diff --git a/tests/proxy_unit_tests/test_update_spend.py b/tests/proxy_unit_tests/test_update_spend.py index 6efc68a077..355cb8a403 100644 --- a/tests/proxy_unit_tests/test_update_spend.py +++ b/tests/proxy_unit_tests/test_update_spend.py @@ -22,12 +22,12 @@ class MockPrismaClient: def __init__(self): self.db = MagicMock() self.spend_log_transactions = [] - self.user_list_transactons = {} - self.end_user_list_transactons = {} - self.key_list_transactons = {} - self.team_list_transactons = {} - self.team_member_list_transactons = {} - self.org_list_transactons = {} + self.user_list_transactions = {} + self.end_user_list_transactions = {} + self.key_list_transactions = {} + self.team_list_transactions = {} + self.team_member_list_transactions = {} + self.org_list_transactions = {} def jsonify_object(self, obj): return obj From ccbac691e531d0bffb5be58eae811eb261a2c4b1 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 22:50:48 -0700 Subject: [PATCH 138/244] Support discovering gemini, anthropic, xai models by calling their `/v1/model` endpoint (#9530) * fix: initial commit for adding provider model discovery to gemini * feat(gemini/): add model discovery for gemini/ route * docs(set_keys.md): update docs to show you can check available gemini models as well * feat(anthropic/): add model discovery for anthropic api key * feat(xai/): add model discovery for XAI enables checking what models an xai key can call * ci: bump ci config yml * fix(topaz/common_utils.py): fix linting error * fix: fix linting error for python38 --- docs/my-website/docs/set_keys.md | 14 +++++++ litellm/__init__.py | 3 ++ litellm/llms/anthropic/common_utils.py | 51 ++++++++++++++++++++++++ litellm/llms/base_llm/base_utils.py | 12 +++++- litellm/llms/gemini/common_utils.py | 52 +++++++++++++++++++++++++ litellm/llms/topaz/common_utils.py | 4 +- litellm/llms/xai/common_utils.py | 51 ++++++++++++++++++++++++ litellm/utils.py | 24 ++++++++++-- tests/litellm_utils_tests/test_utils.py | 18 +++++++++ 9 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 litellm/llms/gemini/common_utils.py create mode 100644 litellm/llms/xai/common_utils.py diff --git a/docs/my-website/docs/set_keys.md b/docs/my-website/docs/set_keys.md index 3a5ff08d63..693cf5f7f4 100644 --- a/docs/my-website/docs/set_keys.md +++ b/docs/my-website/docs/set_keys.md @@ -188,7 +188,13 @@ Currently implemented for: - OpenAI (if OPENAI_API_KEY is set) - Fireworks AI (if FIREWORKS_AI_API_KEY is set) - LiteLLM Proxy (if LITELLM_PROXY_API_KEY is set) +- Gemini (if GEMINI_API_KEY is set) +- XAI (if XAI_API_KEY is set) +- Anthropic (if ANTHROPIC_API_KEY is set) +You can also specify a custom provider to check: + +**All providers**: ```python from litellm import get_valid_models @@ -196,6 +202,14 @@ valid_models = get_valid_models(check_provider_endpoint=True) print(valid_models) ``` +**Specific provider**: +```python +from litellm import get_valid_models + +valid_models = get_valid_models(check_provider_endpoint=True, custom_llm_provider="openai") +print(valid_models) +``` + ### `validate_environment(model: str)` This helper tells you if you have all the required environment variables for a model, and if not - what's missing. diff --git a/litellm/__init__.py b/litellm/__init__.py index 8cdde24a6a..a4903f828c 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -813,6 +813,7 @@ from .llms.oobabooga.chat.transformation import OobaboogaConfig from .llms.maritalk import MaritalkConfig from .llms.openrouter.chat.transformation import OpenrouterConfig from .llms.anthropic.chat.transformation import AnthropicConfig +from .llms.anthropic.common_utils import AnthropicModelInfo from .llms.groq.stt.transformation import GroqSTTConfig from .llms.anthropic.completion.transformation import AnthropicTextConfig from .llms.triton.completion.transformation import TritonConfig @@ -848,6 +849,7 @@ from .llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( VertexGeminiConfig, VertexGeminiConfig as VertexAIConfig, ) +from .llms.gemini.common_utils import GeminiModelInfo from .llms.gemini.chat.transformation import ( GoogleAIStudioGeminiConfig, GoogleAIStudioGeminiConfig as GeminiConfig, # aliased to maintain backwards compatibility @@ -984,6 +986,7 @@ from .llms.fireworks_ai.embed.fireworks_ai_transformation import ( from .llms.friendliai.chat.transformation import FriendliaiChatConfig from .llms.jina_ai.embedding.transformation import JinaAIEmbeddingConfig from .llms.xai.chat.transformation import XAIChatConfig +from .llms.xai.common_utils import XAIModelInfo from .llms.volcengine import VolcEngineConfig from .llms.codestral.completion.transformation import CodestralTextCompletionConfig from .llms.azure.azure import ( diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 409bbe2d82..52a96f5a30 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -6,7 +6,10 @@ from typing import Optional, Union import httpx +import litellm +from litellm.llms.base_llm.base_utils import BaseLLMModelInfo from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.secret_managers.main import get_secret_str class AnthropicError(BaseLLMException): @@ -19,6 +22,54 @@ class AnthropicError(BaseLLMException): super().__init__(status_code=status_code, message=message, headers=headers) +class AnthropicModelInfo(BaseLLMModelInfo): + @staticmethod + def get_api_base(api_base: Optional[str] = None) -> str | None: + return ( + api_base + or get_secret_str("ANTHROPIC_API_BASE") + or "https://api.anthropic.com" + ) + + @staticmethod + def get_api_key(api_key: str | None = None) -> str | None: + return api_key or get_secret_str("ANTHROPIC_API_KEY") + + @staticmethod + def get_base_model(model: str) -> str | None: + return model.replace("anthropic/", "") + + def get_models( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> list[str]: + api_base = AnthropicModelInfo.get_api_base(api_base) + api_key = AnthropicModelInfo.get_api_key(api_key) + if api_base is None or api_key is None: + raise ValueError( + "ANTHROPIC_API_BASE or ANTHROPIC_API_KEY is not set. Please set the environment variable, to query Anthropic's `/models` endpoint." + ) + response = litellm.module_level_client.get( + url=f"{api_base}/v1/models", + headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"}, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError: + raise Exception( + f"Failed to fetch models from Anthropic. Status code: {response.status_code}, Response: {response.text}" + ) + + models = response.json()["data"] + + litellm_model_names = [] + for model in models: + stripped_model_name = model["id"] + litellm_model_name = "anthropic/" + stripped_model_name + litellm_model_names.append(litellm_model_name) + return litellm_model_names + + def process_anthropic_headers(headers: Union[httpx.Headers, dict]) -> dict: openai_headers = {} if "anthropic-ratelimit-requests-limit" in headers: diff --git a/litellm/llms/base_llm/base_utils.py b/litellm/llms/base_llm/base_utils.py index 919cdbfd02..cef64d01e3 100644 --- a/litellm/llms/base_llm/base_utils.py +++ b/litellm/llms/base_llm/base_utils.py @@ -19,11 +19,19 @@ class BaseLLMModelInfo(ABC): self, model: str, ) -> Optional[ProviderSpecificModelInfo]: + """ + Default values all models of this provider support. + """ return None @abstractmethod - def get_models(self) -> List[str]: - pass + def get_models( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> List[str]: + """ + Returns a list of models supported by this provider. + """ + return [] @staticmethod @abstractmethod diff --git a/litellm/llms/gemini/common_utils.py b/litellm/llms/gemini/common_utils.py new file mode 100644 index 0000000000..7f266c0536 --- /dev/null +++ b/litellm/llms/gemini/common_utils.py @@ -0,0 +1,52 @@ +from typing import List, Optional + +import litellm +from litellm.llms.base_llm.base_utils import BaseLLMModelInfo +from litellm.secret_managers.main import get_secret_str + + +class GeminiModelInfo(BaseLLMModelInfo): + @staticmethod + def get_api_base(api_base: Optional[str] = None) -> Optional[str]: + return ( + api_base + or get_secret_str("GEMINI_API_BASE") + or "https://generativelanguage.googleapis.com/v1beta" + ) + + @staticmethod + def get_api_key(api_key: Optional[str] = None) -> Optional[str]: + return api_key or (get_secret_str("GEMINI_API_KEY")) + + @staticmethod + def get_base_model(model: str) -> Optional[str]: + return model.replace("gemini/", "") + + def get_models( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> List[str]: + + api_base = GeminiModelInfo.get_api_base(api_base) + api_key = GeminiModelInfo.get_api_key(api_key) + if api_base is None or api_key is None: + raise ValueError( + "GEMINI_API_BASE or GEMINI_API_KEY is not set. Please set the environment variable, to query Gemini's `/models` endpoint." + ) + + response = litellm.module_level_client.get( + url=f"{api_base}/models?key={api_key}", + ) + + if response.status_code != 200: + raise ValueError( + f"Failed to fetch models from Gemini. Status code: {response.status_code}, Response: {response.json()}" + ) + + models = response.json()["models"] + + litellm_model_names = [] + for model in models: + stripped_model_name = model["name"].strip("models/") + litellm_model_name = "gemini/" + stripped_model_name + litellm_model_names.append(litellm_model_name) + return litellm_model_names diff --git a/litellm/llms/topaz/common_utils.py b/litellm/llms/topaz/common_utils.py index 4ef2315db4..0252585922 100644 --- a/litellm/llms/topaz/common_utils.py +++ b/litellm/llms/topaz/common_utils.py @@ -11,7 +11,9 @@ class TopazException(BaseLLMException): class TopazModelInfo(BaseLLMModelInfo): - def get_models(self) -> List[str]: + def get_models( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> List[str]: return [ "topaz/Standard V2", "topaz/Low Resolution V2", diff --git a/litellm/llms/xai/common_utils.py b/litellm/llms/xai/common_utils.py new file mode 100644 index 0000000000..fdf2edbfa3 --- /dev/null +++ b/litellm/llms/xai/common_utils.py @@ -0,0 +1,51 @@ +from typing import Optional + +import httpx + +import litellm +from litellm.llms.base_llm.base_utils import BaseLLMModelInfo +from litellm.secret_managers.main import get_secret_str + + +class XAIModelInfo(BaseLLMModelInfo): + @staticmethod + def get_api_base(api_base: Optional[str] = None) -> Optional[str]: + return api_base or get_secret_str("XAI_API_BASE") or "https://api.x.ai" + + @staticmethod + def get_api_key(api_key: Optional[str] = None) -> Optional[str]: + return api_key or get_secret_str("XAI_API_KEY") + + @staticmethod + def get_base_model(model: str) -> Optional[str]: + return model.replace("xai/", "") + + def get_models( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> list[str]: + api_base = self.get_api_base(api_base) + api_key = self.get_api_key(api_key) + if api_base is None or api_key is None: + raise ValueError( + "XAI_API_BASE or XAI_API_KEY is not set. Please set the environment variable, to query XAI's `/models` endpoint." + ) + response = litellm.module_level_client.get( + url=f"{api_base}/v1/models", + headers={"Authorization": f"Bearer {api_key}"}, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError: + raise Exception( + f"Failed to fetch models from XAI. Status code: {response.status_code}, Response: {response.text}" + ) + + models = response.json()["data"] + + litellm_model_names = [] + for model in models: + stripped_model_name = model["id"] + litellm_model_name = "xai/" + stripped_model_name + litellm_model_names.append(litellm_model_name) + return litellm_model_names diff --git a/litellm/utils.py b/litellm/utils.py index 3fcb4a803a..3c8b6667f9 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5744,13 +5744,15 @@ def trim_messages( return messages -def get_valid_models(check_provider_endpoint: bool = False) -> List[str]: +def get_valid_models( + check_provider_endpoint: bool = False, custom_llm_provider: Optional[str] = None +) -> List[str]: """ Returns a list of valid LLMs based on the set environment variables Args: check_provider_endpoint: If True, will check the provider's endpoint for valid models. - + custom_llm_provider: If provided, will only check the provider's endpoint for valid models. Returns: A list of valid LLMs """ @@ -5762,6 +5764,9 @@ def get_valid_models(check_provider_endpoint: bool = False) -> List[str]: valid_models = [] for provider in litellm.provider_list: + if custom_llm_provider and provider != custom_llm_provider: + continue + # edge case litellm has together_ai as a provider, it should be togetherai env_provider_1 = provider.replace("_", "") env_provider_2 = provider @@ -5783,10 +5788,17 @@ def get_valid_models(check_provider_endpoint: bool = False) -> List[str]: provider=LlmProviders(provider), ) + if custom_llm_provider and provider != custom_llm_provider: + continue + if provider == "azure": valid_models.append("Azure-LLM") elif provider_config is not None and check_provider_endpoint: - valid_models.extend(provider_config.get_models()) + try: + models = provider_config.get_models() + valid_models.extend(models) + except Exception as e: + verbose_logger.debug(f"Error getting valid models: {e}") else: models_for_provider = litellm.models_by_provider.get(provider, []) valid_models.extend(models_for_provider) @@ -6400,10 +6412,16 @@ class ProviderConfigManager: return litellm.FireworksAIConfig() elif LlmProviders.OPENAI == provider: return litellm.OpenAIGPTConfig() + elif LlmProviders.GEMINI == provider: + return litellm.GeminiModelInfo() elif LlmProviders.LITELLM_PROXY == provider: return litellm.LiteLLMProxyChatConfig() elif LlmProviders.TOPAZ == provider: return litellm.TopazModelInfo() + elif LlmProviders.ANTHROPIC == provider: + return litellm.AnthropicModelInfo() + elif LlmProviders.XAI == provider: + return litellm.XAIModelInfo() return None diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index 535861ce1a..3088fa250f 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -303,6 +303,24 @@ def test_aget_valid_models(): os.environ = old_environ +@pytest.mark.parametrize("custom_llm_provider", ["gemini", "anthropic", "xai"]) +def test_get_valid_models_with_custom_llm_provider(custom_llm_provider): + from litellm.utils import ProviderConfigManager + from litellm.types.utils import LlmProviders + + provider_config = ProviderConfigManager.get_provider_model_info( + model=None, + provider=LlmProviders(custom_llm_provider), + ) + assert provider_config is not None + valid_models = get_valid_models( + check_provider_endpoint=True, custom_llm_provider=custom_llm_provider + ) + print(valid_models) + assert len(valid_models) > 0 + assert provider_config.get_models() == valid_models + + # test_get_valid_models() From 0cb6cb6c6656b53d943644d21c0ee24acf832aec Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 22:54:46 -0700 Subject: [PATCH 139/244] fix schema to work with lock acquisition --- litellm/proxy/db/pod_leader_manager.py | 54 +++++++++++++++++--------- litellm/proxy/schema.prisma | 7 ++-- schema.prisma | 6 +-- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/litellm/proxy/db/pod_leader_manager.py b/litellm/proxy/db/pod_leader_manager.py index 73ecd68b1b..c762eabf66 100644 --- a/litellm/proxy/db/pod_leader_manager.py +++ b/litellm/proxy/db/pod_leader_manager.py @@ -19,16 +19,19 @@ class PodLockManager: Ensures that only one pod can run a cron job at a time. """ - def __init__(self, prisma_client: Optional[PrismaClient], cronjob_id: str): + def __init__(self, cronjob_id: str): self.pod_id = str(uuid.uuid4()) - self.prisma = prisma_client self.cronjob_id = cronjob_id async def acquire_lock(self) -> bool: """ Attempt to acquire the lock for a specific cron job. """ - if not self.prisma: + from litellm.proxy.proxy_server import prisma_client + + verbose_proxy_logger.debug("acquiring lock for cronjob_id=%s", self.cronjob_id) + if not prisma_client: + verbose_proxy_logger.debug("prisma is None, returning False") return False try: current_time = datetime.now(timezone.utc) @@ -38,21 +41,24 @@ class PodLockManager: ) # Attempt to acquire the lock by upserting the record in the `cronjob_locks` table - cronjob_lock = await self.prisma.db.cronJob.upsert( + cronjob_lock = await prisma_client.db.cronjob.upsert( where={"cronjob_id": self.cronjob_id}, - create={ - "cronjob_id": self.cronjob_id, - "pod_id": self.pod_id, - "status": "ACTIVE", - "last_updated": current_time, - "ttl": ttl_expiry, - }, - update={ - "status": "ACTIVE", - "last_updated": current_time, - "ttl": ttl_expiry, + data={ + "create": { + "cronjob_id": self.cronjob_id, + "pod_id": self.pod_id, + "status": "ACTIVE", + "last_updated": current_time, + "ttl": ttl_expiry, + }, + "update": { + "status": "ACTIVE", + "last_updated": current_time, + "ttl": ttl_expiry, + }, }, ) + verbose_proxy_logger.debug("cronjob_lock=%s", cronjob_lock) if cronjob_lock.status == "ACTIVE" and cronjob_lock.pod_id == self.pod_id: verbose_proxy_logger.debug( @@ -70,16 +76,21 @@ class PodLockManager: """ Renew the lock (update the TTL) for the pod holding the lock. """ - if not self.prisma: + from litellm.proxy.proxy_server import prisma_client + + if not prisma_client: return False try: + verbose_proxy_logger.debug( + "renewing lock for cronjob_id=%s", self.cronjob_id + ) current_time = datetime.now(timezone.utc) # Extend the TTL for another DEFAULT_CRON_JOB_LOCK_TTL_SECONDS ttl_expiry = current_time + timedelta( seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS ) - await self.prisma.db.cronJob.update( + await prisma_client.db.cronjob.update( where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, data={"ttl": ttl_expiry, "last_updated": current_time}, ) @@ -95,10 +106,15 @@ class PodLockManager: """ Release the lock and mark the pod as inactive. """ - if not self.prisma: + from litellm.proxy.proxy_server import prisma_client + + if not prisma_client: return False try: - await self.prisma.db.cronJob.update( + verbose_proxy_logger.debug( + "releasing lock for cronjob_id=%s", self.cronjob_id + ) + await prisma_client.db.cronjob.update( where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, data={"status": "INACTIVE"}, ) diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 6e4937390a..ccc22f8af1 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -340,10 +340,10 @@ model LiteLLM_DailyUserSpend { // Track the status of cron jobs running. Only allow one pod to run the job at a time model CronJob { - id String @id @default(cuid()) // Unique ID for the record - podId String // Unique identifier for the pod acting as the leader + cronjob_id String @id @default(cuid()) // Unique ID for the record + pod_id String // Unique identifier for the pod acting as the leader status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) - lastUpdated DateTime @default(now()) // Timestamp for the last update of the cron job record + last_updated DateTime @default(now()) // Timestamp for the last update of the cron job record ttl DateTime // Time when the leader's lease expires } @@ -352,3 +352,4 @@ enum JobStatus { INACTIVE } + diff --git a/schema.prisma b/schema.prisma index 5d1535d2ff..d0de6e2e27 100644 --- a/schema.prisma +++ b/schema.prisma @@ -339,10 +339,10 @@ model LiteLLM_DailyUserSpend { // Track the status of cron jobs running. Only allow one pod to run the job at a time model CronJob { - id String @id @default(cuid()) // Unique ID for the record - podId String // Unique identifier for the pod acting as the leader + cronjob_id String @id @default(cuid()) // Unique ID for the record + pod_id String // Unique identifier for the pod acting as the leader status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) - lastUpdated DateTime @default(now()) // Timestamp for the last update of the cron job record + last_updated DateTime @default(now()) // Timestamp for the last update of the cron job record ttl DateTime // Time when the leader's lease expires } From 666cd8d4e336d4a625bcb4fd68e0c6ebe7f07bd5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 22:55:36 -0700 Subject: [PATCH 140/244] handle commit updates to the DB --- litellm/proxy/db/db_spend_update_writer.py | 59 +++++++++++++--------- litellm/proxy/db/redis_update_buffer.py | 12 ++--- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 7184262ee5..eee598f79a 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -47,14 +47,9 @@ class DBSpendUpdateWriter: self, redis_cache: Optional[RedisCache] = None, ): - from litellm.proxy.proxy_server import prisma_client - self.redis_cache = redis_cache self.redis_update_buffer = RedisUpdateBuffer(redis_cache=self.redis_cache) - self.pod_leader_manager = PodLockManager( - cronjob_id=DB_SPEND_UPDATE_JOB_NAME, - prisma_client=prisma_client, - ) + self.pod_leader_manager = PodLockManager(cronjob_id=DB_SPEND_UPDATE_JOB_NAME) @staticmethod async def update_database( @@ -411,17 +406,23 @@ class DBSpendUpdateWriter: # Only commit from redis to db if this pod is the leader if await self.pod_leader_manager.acquire_lock(): - db_spend_update_transactions = ( - await self.redis_update_buffer.get_all_update_transactions_from_redis() - ) - if db_spend_update_transactions is not None: - await DBSpendUpdateWriter._commit_spend_updates_to_db( - prisma_client=prisma_client, - n_retry_times=n_retry_times, - proxy_logging_obj=proxy_logging_obj, - db_spend_update_transactions=db_spend_update_transactions, + verbose_proxy_logger.debug("acquired lock for spend updates") + + try: + db_spend_update_transactions = ( + await self.redis_update_buffer.get_all_update_transactions_from_redis() ) - await self.pod_leader_manager.release_lock() + if db_spend_update_transactions is not None: + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions=db_spend_update_transactions, + ) + except Exception as e: + verbose_proxy_logger.error(f"Error committing spend updates: {e}") + finally: + await self.pod_leader_manager.release_lock() else: db_spend_update_transactions = DBSpendUpdateTransactions( user_list_transactions=prisma_client.user_list_transactions, @@ -456,7 +457,10 @@ class DBSpendUpdateWriter: ### UPDATE USER TABLE ### user_list_transactions = db_spend_update_transactions["user_list_transactions"] - if len(user_list_transactions.keys()) > 0: + if ( + user_list_transactions is not None + and len(user_list_transactions.keys()) > 0 + ): for i in range(n_retry_times + 1): start_time = time.time() try: @@ -501,7 +505,10 @@ class DBSpendUpdateWriter: end_user_list_transactions = db_spend_update_transactions[ "end_user_list_transactions" ] - if len(end_user_list_transactions.keys()) > 0: + if ( + end_user_list_transactions is not None + and len(end_user_list_transactions.keys()) > 0 + ): await ProxyUpdateSpend.update_end_user_spend( n_retry_times=n_retry_times, prisma_client=prisma_client, @@ -510,9 +517,9 @@ class DBSpendUpdateWriter: ### UPDATE KEY TABLE ### key_list_transactions = db_spend_update_transactions["key_list_transactions"] verbose_proxy_logger.debug( - "KEY Spend transactions: {}".format(len(key_list_transactions.keys())) + "KEY Spend transactions: {}".format(key_list_transactions) ) - if len(key_list_transactions.keys()) > 0: + if key_list_transactions is not None and len(key_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: @@ -555,7 +562,10 @@ class DBSpendUpdateWriter: ) ) team_list_transactions = db_spend_update_transactions["team_list_transactions"] - if len(team_list_transactions.keys()) > 0: + if ( + team_list_transactions is not None + and len(team_list_transactions.keys()) > 0 + ): for i in range(n_retry_times + 1): start_time = time.time() try: @@ -600,7 +610,10 @@ class DBSpendUpdateWriter: team_member_list_transactions = db_spend_update_transactions[ "team_member_list_transactions" ] - if len(team_member_list_transactions.keys()) > 0: + if ( + team_member_list_transactions is not None + and len(team_member_list_transactions.keys()) > 0 + ): for i in range(n_retry_times + 1): start_time = time.time() try: @@ -642,7 +655,7 @@ class DBSpendUpdateWriter: ### UPDATE ORG TABLE ### org_list_transactions = db_spend_update_transactions["org_list_transactions"] - if len(org_list_transactions.keys()) > 0: + if org_list_transactions is not None and len(org_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() try: diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 22afc56483..94cd1e47c7 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -17,12 +17,12 @@ else: class DBSpendUpdateTransactions(TypedDict): - user_list_transactions: Dict[str, float] - end_user_list_transactions: Dict[str, float] - key_list_transactions: Dict[str, float] - team_list_transactions: Dict[str, float] - team_member_list_transactions: Dict[str, float] - org_list_transactions: Dict[str, float] + user_list_transactions: Optional[Dict[str, float]] + end_user_list_transactions: Optional[Dict[str, float]] + key_list_transactions: Optional[Dict[str, float]] + team_list_transactions: Optional[Dict[str, float]] + team_member_list_transactions: Optional[Dict[str, float]] + org_list_transactions: Optional[Dict[str, float]] class RedisUpdateBuffer: From 5203382702d2a4e9ca552d2f5acc19145404a451 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 23:08:52 -0700 Subject: [PATCH 141/244] test: run test earlier to catch error --- tests/test_end_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_end_users.py b/tests/test_end_users.py index fdff3e15bf..0ce1694147 100644 --- a/tests/test_end_users.py +++ b/tests/test_end_users.py @@ -160,7 +160,7 @@ async def test_end_user_new(): @pytest.mark.asyncio -async def test_end_user_specific_region(): +async def test_aaaend_user_specific_region(): """ - Specify region user can make calls in - Make a generic call From 1ba73f491a2b07eb0a92be6d97848c1c2f723bca Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 23:13:01 -0700 Subject: [PATCH 142/244] working cron job updates --- litellm/constants.py | 2 +- litellm/proxy/db/pod_leader_manager.py | 6 +- litellm/proxy/db/redis_update_buffer.py | 90 +++++++++++++++++++------ litellm/proxy/schema.prisma | 2 +- schema.prisma | 2 +- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 0d5a9dcddf..1276c08fae 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -444,4 +444,4 @@ LITELLM_PROXY_ADMIN_NAME = "default_user_id" ########################### DB CRON JOB NAMES ########################### DB_SPEND_UPDATE_JOB_NAME = "db_spend_update_job" -DEFAULT_CRON_JOB_LOCK_TTL_SECONDS = 600 # 5 minutes +DEFAULT_CRON_JOB_LOCK_TTL_SECONDS = 60 # 1 minute diff --git a/litellm/proxy/db/pod_leader_manager.py b/litellm/proxy/db/pod_leader_manager.py index c762eabf66..9c055e96f6 100644 --- a/litellm/proxy/db/pod_leader_manager.py +++ b/litellm/proxy/db/pod_leader_manager.py @@ -41,7 +41,7 @@ class PodLockManager: ) # Attempt to acquire the lock by upserting the record in the `cronjob_locks` table - cronjob_lock = await prisma_client.db.cronjob.upsert( + cronjob_lock = await prisma_client.db.litellm_cronjob.upsert( where={"cronjob_id": self.cronjob_id}, data={ "create": { @@ -90,7 +90,7 @@ class PodLockManager: seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS ) - await prisma_client.db.cronjob.update( + await prisma_client.db.litellm_cronjob.update( where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, data={"ttl": ttl_expiry, "last_updated": current_time}, ) @@ -114,7 +114,7 @@ class PodLockManager: verbose_proxy_logger.debug( "releasing lock for cronjob_id=%s", self.cronjob_id ) - await prisma_client.db.cronjob.update( + await prisma_client.db.litellm_cronjob.update( where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, data={"status": "INACTIVE"}, ) diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 94cd1e47c7..2207911024 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -111,6 +111,13 @@ class RedisUpdateBuffer: value=transaction_amount, ) + @staticmethod + def _remove_prefix_from_keys(data: Dict[str, Any], prefix: str) -> Dict[str, Any]: + """ + Removes the specified prefix from the keys of a dictionary. + """ + return {key.replace(prefix, "", 1): value for key, value in data.items()} + async def get_all_update_transactions_from_redis( self, ) -> Optional[DBSpendUpdateTransactions]: @@ -119,24 +126,69 @@ class RedisUpdateBuffer: """ if self.redis_cache is None: return None - expected_keys = [ - "user_list_transactions", - "end_user_list_transactions", - "key_list_transactions", - "team_list_transactions", - "team_member_list_transactions", - "org_list_transactions", - ] - result = await self.redis_cache.async_batch_get_cache(expected_keys) - if result is None: - return None + user_transaction_keys = await self.redis_cache.async_scan_iter( + "user_list_transactions:*" + ) + end_user_transaction_keys = await self.redis_cache.async_scan_iter( + "end_user_list_transactions:*" + ) + key_transaction_keys = await self.redis_cache.async_scan_iter( + "key_list_transactions:*" + ) + team_transaction_keys = await self.redis_cache.async_scan_iter( + "team_list_transactions:*" + ) + team_member_transaction_keys = await self.redis_cache.async_scan_iter( + "team_member_list_transactions:*" + ) + org_transaction_keys = await self.redis_cache.async_scan_iter( + "org_list_transactions:*" + ) + + user_list_transactions = await self.redis_cache.async_batch_get_cache( + user_transaction_keys + ) + end_user_list_transactions = await self.redis_cache.async_batch_get_cache( + end_user_transaction_keys + ) + key_list_transactions = await self.redis_cache.async_batch_get_cache( + key_transaction_keys + ) + team_list_transactions = await self.redis_cache.async_batch_get_cache( + team_transaction_keys + ) + team_member_list_transactions = await self.redis_cache.async_batch_get_cache( + team_member_transaction_keys + ) + org_list_transactions = await self.redis_cache.async_batch_get_cache( + org_transaction_keys + ) + + # filter out the "prefix" from the keys using the helper method + user_list_transactions = self._remove_prefix_from_keys( + user_list_transactions, "user_list_transactions:" + ) + end_user_list_transactions = self._remove_prefix_from_keys( + end_user_list_transactions, "end_user_list_transactions:" + ) + key_list_transactions = self._remove_prefix_from_keys( + key_list_transactions, "key_list_transactions:" + ) + team_list_transactions = self._remove_prefix_from_keys( + team_list_transactions, "team_list_transactions:" + ) + team_member_list_transactions = self._remove_prefix_from_keys( + team_member_list_transactions, "team_member_list_transactions:" + ) + org_list_transactions = self._remove_prefix_from_keys( + org_list_transactions, "org_list_transactions:" + ) + return DBSpendUpdateTransactions( - user_list_transactions=result.get("user_list_transactions", {}), - end_user_list_transactions=result.get("end_user_list_transactions", {}), - key_list_transactions=result.get("key_list_transactions", {}), - team_list_transactions=result.get("team_list_transactions", {}), - team_member_list_transactions=result.get( - "team_member_list_transactions", {} - ), - org_list_transactions=result.get("org_list_transactions", {}), + user_list_transactions=user_list_transactions, + end_user_list_transactions=end_user_list_transactions, + key_list_transactions=key_list_transactions, + team_list_transactions=team_list_transactions, + team_member_list_transactions=team_member_list_transactions, + org_list_transactions=org_list_transactions, ) diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index ccc22f8af1..ae7560ead3 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -339,7 +339,7 @@ model LiteLLM_DailyUserSpend { // Track the status of cron jobs running. Only allow one pod to run the job at a time -model CronJob { +model LiteLLM_CronJob { cronjob_id String @id @default(cuid()) // Unique ID for the record pod_id String // Unique identifier for the pod acting as the leader status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) diff --git a/schema.prisma b/schema.prisma index d0de6e2e27..ad46f96f6d 100644 --- a/schema.prisma +++ b/schema.prisma @@ -338,7 +338,7 @@ model LiteLLM_DailyUserSpend { // Track the status of cron jobs running. Only allow one pod to run the job at a time -model CronJob { +model LiteLLM_CronJob { cronjob_id String @id @default(cuid()) // Unique ID for the record pod_id String // Unique identifier for the pod acting as the leader status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive) From b155f4f4a6c1c2579cb4f38d3d84e311dabee487 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 23:25:46 -0700 Subject: [PATCH 143/244] fix(proxy_server.py): Fix https://github.com/BerriAI/litellm/issues/9576 --- litellm/proxy/proxy_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 87f34a8984..832ba00b3e 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -511,7 +511,7 @@ async def proxy_startup_event(app: FastAPI): ### LOAD MASTER KEY ### # check if master key set in environment - load from there - master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore + # master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore # check if DATABASE_URL in environment - load from there if prisma_client is None: _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore @@ -1974,6 +1974,7 @@ class ProxyConfig: if master_key and master_key.startswith("os.environ/"): master_key = get_secret(master_key) # type: ignore + if not isinstance(master_key, str): raise Exception( "Master key must be a string. Current type - {}".format( From cdcc8ea9b7ef5d7dcaa1b7c1e1da4cb8b08d5723 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Thu, 27 Mar 2025 23:29:15 -0700 Subject: [PATCH 144/244] Connect UI to "LiteLLM_DailyUserSpend" spend table - enables usage tab to work at 1m+ spend logs (#9603) * feat(spend_management_endpoints.py): expose new endpoint for querying user's usage at 1m+ spend logs Allows user to view their spend at 1m+ spend logs * build(schema.prisma): add api_requests to dailyuserspend table * build(migration.sql): add migration file for new column to daily user spend table * build(prisma_client.py): add logic for copying over migration folder, if deploy/migrations present in expected location enables easier testing of prisma migration flow * build(ui/): initial commit successfully using the dailyuserspend table on the UI * refactor(internal_user_endpoints.py): refactor `/user/daily/activity` to give breakdowns by provider/model/key * feat: feature parity (cost page) with existing 'usage' page * build(ui/): add activity tab to new_usage.tsx gets to feature parity on 'All Up' page of 'usage.tsx' * fix(proxy/utils.py): count number of api requests in daily user spend table allows us to see activity by model on new usage tab * style(new_usage.tsx): fix y-axis to be in ascending order of date * fix: fix linting errors * fix: fix ruff check errors --- ci_cd/baseline_db.py | 1 - .../migration.sql | 3 + litellm/proxy/_types.py | 1 + litellm/proxy/db/prisma_client.py | 94 +++- .../internal_user_endpoints.py | 293 ++++++++++- litellm/proxy/schema.prisma | 10 +- .../spend_management_endpoints.py | 2 +- litellm/proxy/utils.py | 6 + schema.prisma | 1 + ui/litellm-dashboard/src/app/page.tsx | 10 +- .../src/components/leftnav.tsx | 1 + .../src/components/networking.tsx | 36 ++ .../src/components/new_usage.tsx | 471 ++++++++++++++++++ ui/litellm-dashboard/src/components/usage.tsx | 2 +- 14 files changed, 909 insertions(+), 22 deletions(-) create mode 100644 deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql create mode 100644 ui/litellm-dashboard/src/components/new_usage.tsx diff --git a/ci_cd/baseline_db.py b/ci_cd/baseline_db.py index 52aa5430f5..ecc080abed 100644 --- a/ci_cd/baseline_db.py +++ b/ci_cd/baseline_db.py @@ -1,4 +1,3 @@ -import os import subprocess from pathlib import Path from datetime import datetime diff --git a/deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql b/deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql new file mode 100644 index 0000000000..e7c5ab566a --- /dev/null +++ b/deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "LiteLLM_DailyUserSpend" ADD COLUMN "api_requests" INTEGER NOT NULL DEFAULT 0; + diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 6e242ddacb..3c22aaa601 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2718,3 +2718,4 @@ class DailyUserSpendTransaction(TypedDict): prompt_tokens: int completion_tokens: int spend: float + api_requests: int diff --git a/litellm/proxy/db/prisma_client.py b/litellm/proxy/db/prisma_client.py index 85a3a57adc..4e38321e91 100644 --- a/litellm/proxy/db/prisma_client.py +++ b/litellm/proxy/db/prisma_client.py @@ -3,6 +3,7 @@ This file contains the PrismaWrapper class, which is used to wrap the Prisma cli """ import asyncio +import glob import os import random import subprocess @@ -178,6 +179,69 @@ class PrismaManager: verbose_proxy_logger.warning(f"Error creating baseline migration: {e}") return False + @staticmethod + def _copy_spend_tracking_migrations(prisma_dir: str) -> bool: + import shutil + from pathlib import Path + + """ + Check for and copy over spend tracking migrations if they exist in the deploy directory. + Returns True if migrations were found and copied, False otherwise. + """ + try: + # Get the current file's directory + current_dir = Path(__file__).parent + + # Check for migrations in the deploy directory (../../deploy/migrations) + deploy_migrations_dir = ( + current_dir.parent.parent.parent / "deploy" / "migrations" + ) + + # Local migrations directory + local_migrations_dir = Path(prisma_dir + "/migrations") + + if deploy_migrations_dir.exists(): + # Create local migrations directory if it doesn't exist + local_migrations_dir.mkdir(parents=True, exist_ok=True) + + # Copy all migration files + # Copy entire migrations folder recursively + shutil.copytree( + deploy_migrations_dir, local_migrations_dir, dirs_exist_ok=True + ) + + return True + return False + except Exception: + return False + + @staticmethod + def _get_migration_names(migrations_dir: str) -> list: + """Get all migration directory names from the migrations folder""" + migration_paths = glob.glob(f"{migrations_dir}/*/migration.sql") + return [Path(p).parent.name for p in migration_paths] + + @staticmethod + def _resolve_all_migrations(migrations_dir: str): + """Mark all existing migrations as applied""" + migration_names = PrismaManager._get_migration_names(migrations_dir) + for migration_name in migration_names: + try: + verbose_proxy_logger.info(f"Resolving migration: {migration_name}") + subprocess.run( + ["prisma", "migrate", "resolve", "--applied", migration_name], + timeout=60, + check=True, + capture_output=True, + text=True, + ) + verbose_proxy_logger.debug(f"Resolved migration: {migration_name}") + except subprocess.CalledProcessError as e: + if "is already recorded as applied in the database." not in e.stderr: + verbose_proxy_logger.warning( + f"Failed to resolve migration {migration_name}: {e.stderr}" + ) + @staticmethod def setup_database(use_migrate: bool = False) -> bool: """ @@ -194,8 +258,10 @@ class PrismaManager: os.chdir(prisma_dir) try: if use_migrate: + PrismaManager._copy_spend_tracking_migrations( + prisma_dir + ) # place a migration in the migrations directory verbose_proxy_logger.info("Running prisma migrate deploy") - # First try to run migrate deploy directly try: subprocess.run( ["prisma", "migrate", "deploy"], @@ -205,25 +271,31 @@ class PrismaManager: text=True, ) verbose_proxy_logger.info("prisma migrate deploy completed") + + # Resolve all migrations in the migrations directory + migrations_dir = os.path.join(prisma_dir, "migrations") + PrismaManager._resolve_all_migrations(migrations_dir) + return True except subprocess.CalledProcessError as e: - # Check if this is the non-empty schema error + verbose_proxy_logger.warning( + f"prisma db error: {e.stderr}, e: {e.stdout}" + ) if ( "P3005" in e.stderr and "database schema is not empty" in e.stderr ): - # Create baseline migration + verbose_proxy_logger.info("Creating baseline migration") if PrismaManager._create_baseline_migration(schema_path): - # Try migrate deploy again after baseline - subprocess.run( - ["prisma", "migrate", "deploy"], - timeout=60, - check=True, + verbose_proxy_logger.info( + "Resolving all migrations after baseline" ) + + # Resolve all migrations after baseline + migrations_dir = os.path.join(prisma_dir, "migrations") + PrismaManager._resolve_all_migrations(migrations_dir) + return True - else: - # If it's a different error, raise it - raise e else: # Use prisma db push with increased timeout subprocess.run( diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index e9be169cdc..79de6da1fd 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -14,8 +14,9 @@ These are members of a Team on LiteLLM import asyncio import traceback import uuid -from datetime import datetime, timedelta, timezone -from typing import Any, List, Optional, Union, cast +from datetime import date, datetime, timedelta, timezone +from enum import Enum +from typing import Any, Dict, List, Optional, TypedDict, Union, cast import fastapi from fastapi import APIRouter, Depends, Header, HTTPException, Request, status @@ -1242,3 +1243,291 @@ async def ui_view_users( except Exception as e: verbose_proxy_logger.exception(f"Error searching users: {str(e)}") raise HTTPException(status_code=500, detail=f"Error searching users: {str(e)}") + + +class GroupByDimension(str, Enum): + DATE = "date" + MODEL = "model" + API_KEY = "api_key" + TEAM = "team" + ORGANIZATION = "organization" + MODEL_GROUP = "model_group" + PROVIDER = "custom_llm_provider" + + +class SpendMetrics(BaseModel): + spend: float = Field(default=0.0) + prompt_tokens: int = Field(default=0) + completion_tokens: int = Field(default=0) + total_tokens: int = Field(default=0) + api_requests: int = Field(default=0) + + +class BreakdownMetrics(BaseModel): + """Breakdown of spend by different dimensions""" + + models: Dict[str, SpendMetrics] = Field(default_factory=dict) # model -> metrics + providers: Dict[str, SpendMetrics] = Field( + default_factory=dict + ) # provider -> metrics + api_keys: Dict[str, SpendMetrics] = Field( + default_factory=dict + ) # api_key -> metrics + + +class DailySpendData(BaseModel): + date: date + metrics: SpendMetrics + breakdown: BreakdownMetrics = Field(default_factory=BreakdownMetrics) + + +class DailySpendMetadata(BaseModel): + total_spend: float = Field(default=0.0) + total_prompt_tokens: int = Field(default=0) + total_completion_tokens: int = Field(default=0) + total_api_requests: int = Field(default=0) + page: int = Field(default=1) + total_pages: int = Field(default=1) + has_more: bool = Field(default=False) + + +class SpendAnalyticsPaginatedResponse(BaseModel): + results: List[DailySpendData] + metadata: DailySpendMetadata = Field(default_factory=DailySpendMetadata) + + +class LiteLLM_DailyUserSpend(BaseModel): + id: str + user_id: str + date: str + api_key: str + model: str + model_group: Optional[str] = None + custom_llm_provider: Optional[str] = None + prompt_tokens: int = 0 + completion_tokens: int = 0 + spend: float = 0.0 + api_requests: int = 0 + + +class GroupedData(TypedDict): + metrics: SpendMetrics + breakdown: BreakdownMetrics + + +def update_metrics( + group_metrics: SpendMetrics, record: LiteLLM_DailyUserSpend +) -> SpendMetrics: + group_metrics.spend += record.spend + group_metrics.prompt_tokens += record.prompt_tokens + group_metrics.completion_tokens += record.completion_tokens + group_metrics.total_tokens += record.prompt_tokens + record.completion_tokens + group_metrics.api_requests += record.api_requests + return group_metrics + + +def update_breakdown_metrics( + breakdown: BreakdownMetrics, record: LiteLLM_DailyUserSpend +) -> BreakdownMetrics: + """Updates breakdown metrics for a single record using the existing update_metrics function""" + + # Update model breakdown + if record.model not in breakdown.models: + breakdown.models[record.model] = SpendMetrics() + breakdown.models[record.model] = update_metrics( + breakdown.models[record.model], record + ) + + # Update provider breakdown + provider = record.custom_llm_provider or "unknown" + if provider not in breakdown.providers: + breakdown.providers[provider] = SpendMetrics() + breakdown.providers[provider] = update_metrics( + breakdown.providers[provider], record + ) + + # Update api key breakdown + if record.api_key not in breakdown.api_keys: + breakdown.api_keys[record.api_key] = SpendMetrics() + breakdown.api_keys[record.api_key] = update_metrics( + breakdown.api_keys[record.api_key], record + ) + + return breakdown + + +@router.get( + "/user/daily/activity", + tags=["Budget & Spend Tracking", "Internal User management"], + dependencies=[Depends(user_api_key_auth)], + response_model=SpendAnalyticsPaginatedResponse, +) +async def get_user_daily_activity( + start_date: Optional[str] = fastapi.Query( + default=None, + description="Start date in YYYY-MM-DD format", + ), + end_date: Optional[str] = fastapi.Query( + default=None, + description="End date in YYYY-MM-DD format", + ), + group_by: List[GroupByDimension] = fastapi.Query( + default=[GroupByDimension.DATE], + description="Dimensions to group by. Can combine multiple (e.g. date,team)", + ), + view_by: Literal["team", "organization", "user"] = fastapi.Query( + default="user", + description="View spend at team/org/user level", + ), + team_id: Optional[str] = fastapi.Query( + default=None, + description="Filter by specific team", + ), + org_id: Optional[str] = fastapi.Query( + default=None, + description="Filter by specific organization", + ), + model: Optional[str] = fastapi.Query( + default=None, + description="Filter by specific model", + ), + api_key: Optional[str] = fastapi.Query( + default=None, + description="Filter by specific API key", + ), + page: int = fastapi.Query( + default=1, description="Page number for pagination", ge=1 + ), + page_size: int = fastapi.Query( + default=50, description="Items per page", ge=1, le=100 + ), + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +) -> SpendAnalyticsPaginatedResponse: + """ + [BETA] This is a beta endpoint. It will change. + + Meant to optimize querying spend data for analytics for a user. + + Returns: + (by date/team/org/user/model/api_key/model_group/provider) + - spend + - prompt_tokens + - completion_tokens + - total_tokens + - api_requests + - breakdown by team, organization, user, model, api_key, model_group, provider + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, + ) + + if start_date is None or end_date is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"error": "Please provide start_date and end_date"}, + ) + + try: + # Build filter conditions + where_conditions: Dict[str, Any] = { + "date": { + "gte": start_date, + "lte": end_date, + } + } + + if team_id: + where_conditions["team_id"] = team_id + if org_id: + where_conditions["organization_id"] = org_id + if model: + where_conditions["model"] = model + if api_key: + where_conditions["api_key"] = api_key + + # Get total count for pagination + total_count = await prisma_client.db.litellm_dailyuserspend.count( + where=where_conditions + ) + + # Fetch paginated results + daily_spend_data = await prisma_client.db.litellm_dailyuserspend.find_many( + where=where_conditions, + order=[ + {"date": "desc"}, + ], + skip=(page - 1) * page_size, + take=page_size, + ) + + # Process results + results = [] + total_metrics = SpendMetrics() + + # Group data by date and other dimensions + + grouped_data: Dict[str, Dict[str, Any]] = {} + for record in daily_spend_data: + date_str = record.date + if date_str not in grouped_data: + grouped_data[date_str] = { + "metrics": SpendMetrics(), + "breakdown": BreakdownMetrics(), + } + + # Update metrics + grouped_data[date_str]["metrics"] = update_metrics( + grouped_data[date_str]["metrics"], record + ) + # Update breakdowns + grouped_data[date_str]["breakdown"] = update_breakdown_metrics( + grouped_data[date_str]["breakdown"], record + ) + + # Update total metrics + total_metrics.spend += record.spend + total_metrics.prompt_tokens += record.prompt_tokens + total_metrics.completion_tokens += record.completion_tokens + total_metrics.total_tokens += ( + record.prompt_tokens + record.completion_tokens + ) + total_metrics.api_requests += 1 + + # Convert grouped data to response format + for date_str, data in grouped_data.items(): + results.append( + DailySpendData( + date=datetime.strptime(date_str, "%Y-%m-%d").date(), + metrics=data["metrics"], + breakdown=data["breakdown"], + ) + ) + + # Sort results by date + results.sort(key=lambda x: x.date, reverse=True) + + return SpendAnalyticsPaginatedResponse( + results=results, + metadata=DailySpendMetadata( + total_spend=total_metrics.spend, + total_prompt_tokens=total_metrics.prompt_tokens, + total_completion_tokens=total_metrics.completion_tokens, + total_api_requests=total_metrics.api_requests, + page=page, + total_pages=-(-total_count // page_size), # Ceiling division + has_more=(page * page_size) < total_count, + ), + ) + + except Exception as e: + verbose_proxy_logger.exception( + "/spend/daily/analytics: Exception occured - {}".format(str(e)) + ) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail={"error": f"Failed to fetch analytics: {str(e)}"}, + ) diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 9269e89014..7acb5e2615 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -314,19 +314,19 @@ model LiteLLM_AuditLog { updated_values Json? // value of the row after change } - // Track daily user spend metrics per model and key model LiteLLM_DailyUserSpend { id String @id @default(uuid()) user_id String date String - api_key String // Hashed API Token - model String // The specific model used - model_group String? // public model_name / model_group - custom_llm_provider String? // The LLM provider (e.g., "openai", "anthropic") + api_key String + model String + model_group String? + custom_llm_provider String? prompt_tokens Int @default(0) completion_tokens Int @default(0) spend Float @default(0.0) + api_requests Int @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 9789e2a0ec..4c0e22aef7 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -3,7 +3,7 @@ import collections import os from datetime import datetime, timedelta, timezone from functools import lru_cache -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional import fastapi from fastapi import APIRouter, Depends, HTTPException, status diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 7f1ac814a8..1c4e625781 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1179,6 +1179,7 @@ class PrismaClient: daily_transaction["spend"] += payload["spend"] daily_transaction["prompt_tokens"] += payload["prompt_tokens"] daily_transaction["completion_tokens"] += payload["completion_tokens"] + daily_transaction["api_requests"] += 1 else: daily_transaction = DailyUserSpendTransaction( user_id=payload["user"], @@ -1190,6 +1191,7 @@ class PrismaClient: prompt_tokens=payload["prompt_tokens"], completion_tokens=payload["completion_tokens"], spend=payload["spend"], + api_requests=1, ) self.daily_user_spend_transactions[daily_transaction_key] = ( @@ -2598,6 +2600,7 @@ class ProxyUpdateSpend: "completion_tokens" ], "spend": transaction["spend"], + "api_requests": transaction["api_requests"], }, "update": { "prompt_tokens": { @@ -2609,6 +2612,9 @@ class ProxyUpdateSpend: ] }, "spend": {"increment": transaction["spend"]}, + "api_requests": { + "increment": transaction["api_requests"] + }, }, }, ) diff --git a/schema.prisma b/schema.prisma index 3312b26354..7acb5e2615 100644 --- a/schema.prisma +++ b/schema.prisma @@ -326,6 +326,7 @@ model LiteLLM_DailyUserSpend { prompt_tokens Int @default(0) completion_tokens Int @default(0) spend Float @default(0.0) + api_requests Int @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 9b9ec0c9c0..f480501b58 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -20,6 +20,7 @@ import PassThroughSettings from "@/components/pass_through_settings"; import BudgetPanel from "@/components/budgets/budget_panel"; import SpendLogsTable from "@/components/view_logs"; import ModelHub from "@/components/model_hub"; +import NewUsagePage from "@/components/new_usage"; import APIRef from "@/components/api_ref"; import ChatUI from "@/components/chat_ui"; import Sidebar from "@/components/leftnav"; @@ -346,7 +347,14 @@ export default function CreateKeyPage() { accessToken={accessToken} allTeams={teams as Team[] ?? []} /> - ) : ( + ) : page == "new_usage" ? ( + + ) : + ( , roles: all_admin_roles }, { key: "10", page: "budgets", label: "Budgets", icon: , roles: all_admin_roles }, { key: "11", page: "guardrails", label: "Guardrails", icon: , roles: all_admin_roles }, + { key: "12", page: "new_usage", label: "New Usage", icon: , roles: all_admin_roles }, ] }, { diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index fbe0374e34..83d258e532 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -1070,6 +1070,42 @@ export const organizationDeleteCall = async ( } }; + +export const userDailyActivityCall = async (accessToken: String, startTime: Date, endTime: Date) => { + /** + * Get daily user activity on proxy + */ + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/user/daily/activity` : `/user/daily/activity`; + const queryParams = new URLSearchParams(); + queryParams.append('start_date', startTime.toISOString()); + queryParams.append('end_date', endTime.toISOString()); + const queryString = queryParams.toString(); + if (queryString) { + url += `?${queryString}`; + } + + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; export const getTotalSpendCall = async (accessToken: String) => { /** * Get all models on proxy diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx new file mode 100644 index 0000000000..e472fc69ef --- /dev/null +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -0,0 +1,471 @@ +/** + * New Usage Page + * + * Uses the new `/user/daily/activity` endpoint to get daily activity data for a user. + * + * Works at 1m+ spend logs, by querying an aggregate table instead. + */ + +import React, { useState, useEffect } from "react"; +import { + BarChart, Card, Title, Text, + Grid, Col, TabGroup, TabList, Tab, + TabPanel, TabPanels, DonutChart, + Table, TableHead, TableRow, + TableHeaderCell, TableBody, TableCell, + Subtitle +} from "@tremor/react"; +import { AreaChart } from "@tremor/react"; + +import { userDailyActivityCall } from "./networking"; +import ViewUserSpend from "./view_user_spend"; +import TopKeyView from "./top_key_view"; + +interface NewUsagePageProps { + accessToken: string | null; + userRole: string | null; + userID: string | null; +} + +interface SpendMetrics { + spend: number; + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + api_requests: number; +} + +interface BreakdownMetrics { + models: { [key: string]: SpendMetrics }; + providers: { [key: string]: SpendMetrics }; + api_keys: { [key: string]: SpendMetrics }; +} + +interface DailyData { + date: string; + metrics: SpendMetrics; + breakdown: BreakdownMetrics; +} + +const NewUsagePage: React.FC = ({ + accessToken, + userRole, + userID, +}) => { + const [userSpendData, setUserSpendData] = useState<{ + results: DailyData[]; + metadata: any; + }>({ results: [], metadata: {} }); + + // Derived states from userSpendData + const totalSpend = userSpendData.metadata?.total_spend || 0; + + // Calculate top models from the breakdown data + const getTopModels = () => { + const modelSpend: { [key: string]: SpendMetrics } = {}; + userSpendData.results.forEach(day => { + Object.entries(day.breakdown.models || {}).forEach(([model, metrics]) => { + if (!modelSpend[model]) { + modelSpend[model] = { + spend: 0, + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + api_requests: 0 + }; + } + modelSpend[model].spend += metrics.spend; + modelSpend[model].prompt_tokens += metrics.prompt_tokens; + modelSpend[model].completion_tokens += metrics.completion_tokens; + modelSpend[model].total_tokens += metrics.total_tokens; + modelSpend[model].api_requests += metrics.api_requests; + }); + }); + + return Object.entries(modelSpend) + .map(([model, metrics]) => ({ + key: model, + spend: metrics.spend, + requests: metrics.api_requests, + tokens: metrics.total_tokens + })) + .sort((a, b) => b.spend - a.spend) + .slice(0, 5); + }; + + // Calculate provider spend from the breakdown data + const getProviderSpend = () => { + const providerSpend: { [key: string]: SpendMetrics } = {}; + userSpendData.results.forEach(day => { + Object.entries(day.breakdown.providers || {}).forEach(([provider, metrics]) => { + if (!providerSpend[provider]) { + providerSpend[provider] = { + spend: 0, + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + api_requests: 0 + }; + } + providerSpend[provider].spend += metrics.spend; + providerSpend[provider].prompt_tokens += metrics.prompt_tokens; + providerSpend[provider].completion_tokens += metrics.completion_tokens; + providerSpend[provider].total_tokens += metrics.total_tokens; + providerSpend[provider].api_requests += metrics.api_requests; + }); + }); + + return Object.entries(providerSpend) + .map(([provider, metrics]) => ({ + provider, + spend: metrics.spend, + requests: metrics.api_requests, + tokens: metrics.total_tokens + })); + }; + + // Calculate top API keys from the breakdown data + const getTopKeys = () => { + const keySpend: { [key: string]: SpendMetrics } = {}; + userSpendData.results.forEach(day => { + Object.entries(day.breakdown.api_keys || {}).forEach(([key, metrics]) => { + if (!keySpend[key]) { + keySpend[key] = { + spend: 0, + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + api_requests: 0 + }; + } + keySpend[key].spend += metrics.spend; + keySpend[key].prompt_tokens += metrics.prompt_tokens; + keySpend[key].completion_tokens += metrics.completion_tokens; + keySpend[key].total_tokens += metrics.total_tokens; + keySpend[key].api_requests += metrics.api_requests; + }); + }); + + return Object.entries(keySpend) + .map(([api_key, metrics]) => ({ + api_key, + key_alias: api_key.substring(0, 10), // Using truncated key as alias + spend: metrics.spend, + })) + .sort((a, b) => b.spend - a.spend) + .slice(0, 5); + }; + + const fetchUserSpendData = async () => { + if (!accessToken) return; + const startTime = new Date(Date.now() - 28 * 24 * 60 * 60 * 1000); + const endTime = new Date(); + const data = await userDailyActivityCall(accessToken, startTime, endTime); + setUserSpendData(data); + }; + + useEffect(() => { + fetchUserSpendData(); + }, [accessToken]); + + return ( +
+ Experimental Usage page, using new `/user/daily/activity` endpoint. + + + Cost + Activity + + + {/* Cost Panel */} + + + {/* Total Spend Card */} + + + Project Spend {new Date().toLocaleString('default', { month: 'long' })} 1 - {new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()} + + + + + {/* Daily Spend Chart */} + + + Daily Spend + `$${value.toFixed(2)}`} + yAxisWidth={100} + showLegend={false} + customTooltip={({ payload, active }) => { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.date}

+

Spend: ${data.metrics.spend.toFixed(2)}

+

Requests: {data.metrics.api_requests}

+

Tokens: {data.metrics.total_tokens}

+
+ ); + }} + /> +
+ + + {/* Top API Keys */} + + + Top API Keys + + + + + {/* Top Models */} + + + Top Models + `$${value.toFixed(2)}`} + layout="vertical" + yAxisWidth={200} + showLegend={false} + customTooltip={({ payload, active }) => { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.key}

+

Spend: ${data.spend.toFixed(2)}

+

Requests: {data.requests.toLocaleString()}

+

Tokens: {data.tokens.toLocaleString()}

+
+ ); + }} + /> +
+ + + {/* Spend by Provider */} + + + Spend by Provider + + + `$${value.toFixed(2)}`} + colors={["cyan"]} + /> + + + + + + Provider + Spend + Requests + Tokens + + + + {getProviderSpend().map((provider) => ( + + {provider.provider} + + ${provider.spend < 0.00001 + ? "less than 0.00" + : provider.spend.toFixed(2)} + + {provider.requests.toLocaleString()} + {provider.tokens.toLocaleString()} + + ))} + +
+ +
+
+ + + {/* Usage Metrics */} + + + Usage Metrics + + + Total Requests + + {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} + + + + Total Tokens + + {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} + + + + Average Cost per Request + + ${((totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1)).toFixed(4)} + + + + + +
+
+ + {/* Activity Panel */} + + + + All Up + + + + API Requests {valueFormatterNumbers(userSpendData.metadata?.total_api_requests || 0)} + + + + + + Tokens {valueFormatterNumbers(userSpendData.metadata?.total_tokens || 0)} + + + + + + + {/* Per Model Activity */} + {Object.entries(getModelActivityData(userSpendData)).map(([model, data], index) => ( + + {model} + + + + API Requests {valueFormatterNumbers(data.total_requests)} + + + + + + Tokens {valueFormatterNumbers(data.total_tokens)} + + + + + + ))} + + +
+
+
+ ); +}; + +// Add this helper function to process model-specific activity data +const getModelActivityData = (userSpendData: { + results: DailyData[]; + metadata: any; +}) => { + const modelData: { + [key: string]: { + total_requests: number; + total_tokens: number; + daily_data: Array<{ + date: string; + api_requests: number; + total_tokens: number; + }>; + }; + } = {}; + + userSpendData.results.forEach((day: DailyData) => { + Object.entries(day.breakdown.models || {}).forEach(([model, metrics]) => { + if (!modelData[model]) { + modelData[model] = { + total_requests: 0, + total_tokens: 0, + daily_data: [] + }; + } + + modelData[model].total_requests += metrics.api_requests; + modelData[model].total_tokens += metrics.total_tokens; + modelData[model].daily_data.push({ + date: day.date, + api_requests: metrics.api_requests, + total_tokens: metrics.total_tokens + }); + }); + }); + + return modelData; +}; + +// Add this helper function for number formatting +function valueFormatterNumbers(number: number) { + const formatter = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 0, + notation: 'compact', + compactDisplay: 'short', + }); + return formatter.format(number); +} + +export default NewUsagePage; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 43099c21ff..0f4b46195a 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -1035,4 +1035,4 @@ const UsagePage: React.FC = ({ ); }; -export default UsagePage; +export default UsagePage; \ No newline at end of file From 0edd4aa8a7f471a6e57e134932b4db717e3a42f8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Mar 2025 23:29:48 -0700 Subject: [PATCH 145/244] fix PodLockManager --- litellm/proxy/db/pod_leader_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/db/pod_leader_manager.py b/litellm/proxy/db/pod_leader_manager.py index 9c055e96f6..9b739b8695 100644 --- a/litellm/proxy/db/pod_leader_manager.py +++ b/litellm/proxy/db/pod_leader_manager.py @@ -1,6 +1,6 @@ import uuid from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from litellm._logging import verbose_proxy_logger from litellm.constants import DEFAULT_CRON_JOB_LOCK_TTL_SECONDS From 3226477464a2dc06a922dad69347e5aff1905522 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 23:30:52 -0700 Subject: [PATCH 146/244] fix: cleanup --- litellm/proxy/proxy_server.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 832ba00b3e..df295c3697 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -509,9 +509,6 @@ async def proxy_startup_event(app: FastAPI): if isinstance(worker_config, dict): await initialize(**worker_config) - ### LOAD MASTER KEY ### - # check if master key set in environment - load from there - # master_key = get_secret("LITELLM_MASTER_KEY", None) # type: ignore # check if DATABASE_URL in environment - load from there if prisma_client is None: _db_url: Optional[str] = get_secret("DATABASE_URL", None) # type: ignore From 34a1a48af14f4e98c5327887c3e13bb28db1243f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Mar 2025 23:31:58 -0700 Subject: [PATCH 147/244] fix(common_utils.py): fix linting error --- litellm/llms/anthropic/common_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 52a96f5a30..967d29f454 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -24,7 +24,7 @@ class AnthropicError(BaseLLMException): class AnthropicModelInfo(BaseLLMModelInfo): @staticmethod - def get_api_base(api_base: Optional[str] = None) -> str | None: + def get_api_base(api_base: Optional[str] = None) -> Optional[str]: return ( api_base or get_secret_str("ANTHROPIC_API_BASE") @@ -32,12 +32,12 @@ class AnthropicModelInfo(BaseLLMModelInfo): ) @staticmethod - def get_api_key(api_key: str | None = None) -> str | None: + def get_api_key(api_key: Optional[str] = None) -> Optional[str]: return api_key or get_secret_str("ANTHROPIC_API_KEY") @staticmethod - def get_base_model(model: str) -> str | None: - return model.replace("anthropic/", "") + def get_base_model(model: Optional[str] = None) -> Optional[str]: + return model.replace("anthropic/", "") if model else None def get_models( self, api_key: Optional[str] = None, api_base: Optional[str] = None From d60e485c0347da2c3eeb41c3df242fa9749737ec Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 09:28:09 -0700 Subject: [PATCH 148/244] Update README.md (#9616) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 2d2f71e4d1..1c4e148443 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,6 @@
PyPI Version - - CircleCI - Y Combinator W23 From 69e28b92c6d683e4dd34611c362d38626716fe11 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Mar 2025 09:38:32 -0700 Subject: [PATCH 149/244] fix: fix python38 linting error --- litellm/llms/anthropic/common_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 967d29f454..9eae6734ff 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -2,7 +2,7 @@ This file contains common utils for anthropic calls. """ -from typing import Optional, Union +from typing import List, Optional, Union import httpx @@ -41,7 +41,7 @@ class AnthropicModelInfo(BaseLLMModelInfo): def get_models( self, api_key: Optional[str] = None, api_base: Optional[str] = None - ) -> list[str]: + ) -> List[str]: api_base = AnthropicModelInfo.get_api_base(api_base) api_key = AnthropicModelInfo.get_api_key(api_key) if api_base is None or api_key is None: From 8c79e1902e89767ec2014b132e3e61550df6bbfa Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Fri, 28 Mar 2025 09:57:53 -0700 Subject: [PATCH 150/244] Unit test fixing and poetry update --- poetry.lock | 374 +++++++++++------- pyproject.toml | 5 +- tests/litellm/rerank_api/test_rerank_main.py | 2 +- .../test_base_routing_strategy.py | 2 +- 4 files changed, 246 insertions(+), 137 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5834dd2e70..2f20dfcd10 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -123,7 +123,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -152,9 +152,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "anyio" version = "4.5.2" @@ -175,7 +172,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -192,7 +189,6 @@ files = [ ] [package.dependencies] -"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} tzlocal = ">=3.0" [package.extras] @@ -203,7 +199,7 @@ mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "anyio (>=4.5.2)", "gevent ; python_version < \"3.14\"", "pytest", "pytz", "twisted ; python_version < \"3.14\""] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -212,7 +208,7 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "5.0.1" description = "Timeout context manager for asyncio programs" -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] markers = "python_full_version < \"3.11.3\" and extra == \"proxy\" or python_version < \"3.11\"" @@ -234,12 +230,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "azure-core" @@ -313,36 +309,6 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] -[[package]] -name = "backports-zoneinfo" -version = "0.2.1" -description = "Backport of the standard library zoneinfo module" -optional = true -python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\" and python_version < \"3.9\"" -files = [ - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, - {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, -] - -[package.extras] -tzdata = ["tzdata"] - [[package]] name = "black" version = "23.12.1" @@ -386,7 +352,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -427,10 +393,7 @@ files = [ [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, -] +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.21.2)"] @@ -467,7 +430,7 @@ description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" +markers = "(extra == \"proxy\" or extra == \"extra-proxy\") and platform_python_implementation != \"PyPy\" or extra == \"proxy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -829,7 +792,6 @@ fastapi = ">=0.80" httpx = ">=0.23.0" oauthlib = ">=3.1.0" pydantic = {version = ">=1.8.0", extras = ["email"]} -typing-extensions = {version = ">=4.12.2,<5.0.0", markers = "python_version < \"3.10\""} [[package]] name = "filelock" @@ -846,7 +808,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "flake8" @@ -1024,23 +986,23 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, ] proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, + {version = ">=1.22.3,<2.0.0"}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1250,6 +1212,34 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "h2" +version = "4.2.0" +description = "Pure-Python HTTP/2 protocol implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"}, + {file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"}, +] + +[package.dependencies] +hpack = ">=4.1,<5" +hyperframe = ">=6.1,<7" + +[[package]] +name = "hpack" +version = "4.1.0" +description = "Pure-Python HPACK header encoding" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, + {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, +] + [[package]] name = "httpcore" version = "1.0.7" @@ -1291,12 +1281,24 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "httpx-sse" +version = "0.4.0" +description = "Consume Server-Sent Event (SSE) messages with HTTPX." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, + {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, +] + [[package]] name = "huggingface-hub" version = "0.29.3" @@ -1332,6 +1334,46 @@ testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gr torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] +[[package]] +name = "hypercorn" +version = "0.17.3" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "hypercorn-0.17.3-py3-none-any.whl", hash = "sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547"}, + {file = "hypercorn-0.17.3.tar.gz", hash = "sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +taskgroup = {version = "*", markers = "python_version < \"3.11\""} +tomli = {version = "*", markers = "python_version < \"3.11\""} +typing_extensions = {version = "*", markers = "python_version < \"3.11\""} +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["trio (>=0.22.0)"] +uvloop = ["uvloop (>=0.18) ; platform_system != \"Windows\""] + +[[package]] +name = "hyperframe" +version = "6.1.0" +description = "Pure-Python HTTP/2 framing" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, + {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, +] + [[package]] name = "idna" version = "3.10" @@ -1363,36 +1405,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - -[[package]] -name = "importlib-resources" -version = "6.4.5" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.9\"" -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -1551,9 +1569,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} jsonschema-specifications = ">=2023.03.6" -pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -1574,7 +1590,6 @@ files = [ ] [package.dependencies] -importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} referencing = ">=0.31.0" [[package]] @@ -1659,6 +1674,33 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mcp" +version = "1.5.0" +description = "Model Context Protocol SDK" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527"}, + {file = "mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9"}, +] + +[package.dependencies] +anyio = ">=4.5" +httpx = ">=0.27" +httpx-sse = ">=0.4" +pydantic = ">=2.7.2,<3.0.0" +pydantic-settings = ">=2.5.2" +sse-starlette = ">=1.6.1" +starlette = ">=0.27" +uvicorn = ">=0.23.1" + +[package.extras] +cli = ["python-dotenv (>=1.0.0)", "typer (>=0.12.4)"] +rich = ["rich (>=13.9.4)"] +ws = ["websockets (>=15.0.1)"] + [[package]] name = "msal" version = "1.32.0" @@ -1678,7 +1720,7 @@ PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" [package.extras] -broker = ["pymsalruntime (>=0.14,<0.18)", "pymsalruntime (>=0.17,<0.18)"] +broker = ["pymsalruntime (>=0.14,<0.18) ; python_version >= \"3.6\" and platform_system == \"Windows\"", "pymsalruntime (>=0.17,<0.18) ; python_version >= \"3.8\" and platform_system == \"Darwin\""] [[package]] name = "msal-extensions" @@ -2047,19 +2089,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pkgutil-resolve-name" -version = "1.3.10" -description = "Resolve a name to an object." -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version < \"3.9\"" -files = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] - [[package]] name = "platformdirs" version = "4.3.6" @@ -2093,6 +2122,18 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +optional = false +python-versions = ">=3.6.1" +groups = ["main"] +files = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] + [[package]] name = "prisma" version = "0.11.0" @@ -2317,7 +2358,7 @@ description = "C parser in Python" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" +markers = "(extra == \"proxy\" or extra == \"extra-proxy\") and platform_python_implementation != \"PyPy\" or extra == \"proxy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2343,7 +2384,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -2458,6 +2499,27 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-settings" +version = "2.8.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, + {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + [[package]] name = "pyflakes" version = "3.1.0" @@ -2846,6 +2908,21 @@ files = [ [package.dependencies] requests = "2.31.0" +[[package]] +name = "respx" +version = "0.22.0" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, + {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, +] + +[package.dependencies] +httpx = ">=0.25.0" + [[package]] name = "rpds-py" version = "0.20.1" @@ -3036,14 +3113,33 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sse-starlette" +version = "2.1.3" +description = "SSE plugin for Starlette" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, + {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, +] + +[package.dependencies] +anyio = "*" +starlette = "*" +uvicorn = "*" + +[package.extras] +examples = ["fastapi"] + [[package]] name = "starlette" version = "0.44.0" description = "The little ASGI library that shines." -optional = true +optional = false python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, @@ -3051,11 +3147,27 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] +[[package]] +name = "taskgroup" +version = "0.2.2" +description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"}, + {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"}, +] + +[package.dependencies] +exceptiongroup = "*" +typing_extensions = ">=4.12.2,<5" + [[package]] name = "tiktoken" version = "0.7.0" @@ -3148,7 +3260,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, @@ -3259,30 +3371,11 @@ files = [ ] [package.dependencies] -"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] -[[package]] -name = "urllib3" -version = "1.26.20" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - [[package]] name = "urllib3" version = "2.2.3" @@ -3290,14 +3383,13 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3306,10 +3398,9 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.29.0" description = "The lightning-fast ASGI server." -optional = true +optional = false python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -3321,7 +3412,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -3473,6 +3564,21 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "yarl" version = "1.15.2" @@ -3599,11 +3705,11 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] @@ -3612,5 +3718,5 @@ proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", [metadata] lock-version = "2.1" -python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "55078af47c1af79bd3ebadacb7ba92844d550a577bb0c49f5096693701ea4322" +python-versions = ">=3.10,<4.0" +content-hash = "56260c4dea3b139d8674043a1df5e4102cbf6d021bcc41f1fb39368cbc53fcc8" diff --git a/pyproject.toml b/pyproject.toml index 58fd5a7ded..4b31ce80a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ documentation = "https://docs.litellm.ai" Documentation = "https://docs.litellm.ai" [tool.poetry.dependencies] -python = ">=3.8.1,<4.0, !=3.9.7" +python = ">=3.10,<4.0" httpx = ">=0.23.0" openai = ">=1.66.1" python-dotenv = ">=0.2.0" @@ -53,6 +53,9 @@ resend = {version = "^0.8.0", optional = true} pynacl = {version = "^1.5.0", optional = true} websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.34.34", optional = true} +respx = "^0.22.0" +mcp = "^1.5.0" +hypercorn = "^0.17.3" [tool.poetry.extras] proxy = [ diff --git a/tests/litellm/rerank_api/test_rerank_main.py b/tests/litellm/rerank_api/test_rerank_main.py index d3051b81f9..a908293826 100644 --- a/tests/litellm/rerank_api/test_rerank_main.py +++ b/tests/litellm/rerank_api/test_rerank_main.py @@ -13,7 +13,7 @@ from unittest.mock import MagicMock, patch from litellm import rerank from litellm.llms.custom_httpx.http_handler import HTTPHandler - +@pytest.mark.skip(reason="This test requires a valid rerank API key") def test_rerank_infer_region_from_model_arn(monkeypatch): mock_response = MagicMock() diff --git a/tests/litellm/router_strategy/test_base_routing_strategy.py b/tests/litellm/router_strategy/test_base_routing_strategy.py index b47a2f1c90..faa6c40c57 100644 --- a/tests/litellm/router_strategy/test_base_routing_strategy.py +++ b/tests/litellm/router_strategy/test_base_routing_strategy.py @@ -19,7 +19,7 @@ from litellm.router_strategy.base_routing_strategy import BaseRoutingStrategy @pytest.fixture -def mock_dual_cache(): +async def mock_dual_cache(): dual_cache = MagicMock(spec=DualCache) dual_cache.in_memory_cache = MagicMock() dual_cache.redis_cache = MagicMock() From 9437ee5e1fc68f18836462584c2c16f9fa30e335 Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Fri, 28 Mar 2025 10:22:32 -0700 Subject: [PATCH 151/244] Revert "Unit test fixing and poetry update" This reverts commit 8c79e1902e89767ec2014b132e3e61550df6bbfa. --- poetry.lock | 374 +++++++----------- pyproject.toml | 5 +- tests/litellm/rerank_api/test_rerank_main.py | 2 +- .../test_base_routing_strategy.py | 2 +- 4 files changed, 137 insertions(+), 246 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2f20dfcd10..5834dd2e70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -123,7 +123,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -152,6 +152,9 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" version = "4.5.2" @@ -172,7 +175,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -189,6 +192,7 @@ files = [ ] [package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} tzlocal = ">=3.0" [package.extras] @@ -199,7 +203,7 @@ mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "anyio (>=4.5.2)", "gevent ; python_version < \"3.14\"", "pytest", "pytz", "twisted ; python_version < \"3.14\""] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -208,7 +212,7 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "5.0.1" description = "Timeout context manager for asyncio programs" -optional = true +optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_full_version < \"3.11.3\" and extra == \"proxy\" or python_version < \"3.11\"" @@ -230,12 +234,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "azure-core" @@ -309,6 +313,36 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +optional = true +python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"proxy\" and python_version < \"3.9\"" +files = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[package.extras] +tzdata = ["tzdata"] + [[package]] name = "black" version = "23.12.1" @@ -352,7 +386,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -393,7 +427,10 @@ files = [ [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} +urllib3 = [ + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, +] [package.extras] crt = ["awscrt (==0.21.2)"] @@ -430,7 +467,7 @@ description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"proxy\" or extra == \"extra-proxy\") and platform_python_implementation != \"PyPy\" or extra == \"proxy\"" +markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -792,6 +829,7 @@ fastapi = ">=0.80" httpx = ">=0.23.0" oauthlib = ">=3.1.0" pydantic = {version = ">=1.8.0", extras = ["email"]} +typing-extensions = {version = ">=4.12.2,<5.0.0", markers = "python_version < \"3.10\""} [[package]] name = "filelock" @@ -808,7 +846,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flake8" @@ -986,23 +1024,23 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, ] proto-plus = [ + {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0"}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1212,34 +1250,6 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] -[[package]] -name = "h2" -version = "4.2.0" -description = "Pure-Python HTTP/2 protocol implementation" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"}, - {file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"}, -] - -[package.dependencies] -hpack = ">=4.1,<5" -hyperframe = ">=6.1,<7" - -[[package]] -name = "hpack" -version = "4.1.0" -description = "Pure-Python HPACK header encoding" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, - {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, -] - [[package]] name = "httpcore" version = "1.0.7" @@ -1281,24 +1291,12 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "httpx-sse" -version = "0.4.0" -description = "Consume Server-Sent Event (SSE) messages with HTTPX." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, - {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, -] - [[package]] name = "huggingface-hub" version = "0.29.3" @@ -1334,46 +1332,6 @@ testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gr torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] -[[package]] -name = "hypercorn" -version = "0.17.3" -description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "hypercorn-0.17.3-py3-none-any.whl", hash = "sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547"}, - {file = "hypercorn-0.17.3.tar.gz", hash = "sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -h11 = "*" -h2 = ">=3.1.0" -priority = "*" -taskgroup = {version = "*", markers = "python_version < \"3.11\""} -tomli = {version = "*", markers = "python_version < \"3.11\""} -typing_extensions = {version = "*", markers = "python_version < \"3.11\""} -wsproto = ">=0.14.0" - -[package.extras] -docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] -h3 = ["aioquic (>=0.9.0,<1.0)"] -trio = ["trio (>=0.22.0)"] -uvloop = ["uvloop (>=0.18) ; platform_system != \"Windows\""] - -[[package]] -name = "hyperframe" -version = "6.1.0" -description = "Pure-Python HTTP/2 framing" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, - {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, -] - [[package]] name = "idna" version = "3.10" @@ -1405,12 +1363,36 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\"" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] type = ["pytest-mypy"] [[package]] @@ -1569,7 +1551,9 @@ files = [ [package.dependencies] attrs = ">=22.2.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} jsonschema-specifications = ">=2023.03.6" +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -1590,6 +1574,7 @@ files = [ ] [package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} referencing = ">=0.31.0" [[package]] @@ -1674,33 +1659,6 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -[[package]] -name = "mcp" -version = "1.5.0" -description = "Model Context Protocol SDK" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527"}, - {file = "mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9"}, -] - -[package.dependencies] -anyio = ">=4.5" -httpx = ">=0.27" -httpx-sse = ">=0.4" -pydantic = ">=2.7.2,<3.0.0" -pydantic-settings = ">=2.5.2" -sse-starlette = ">=1.6.1" -starlette = ">=0.27" -uvicorn = ">=0.23.1" - -[package.extras] -cli = ["python-dotenv (>=1.0.0)", "typer (>=0.12.4)"] -rich = ["rich (>=13.9.4)"] -ws = ["websockets (>=15.0.1)"] - [[package]] name = "msal" version = "1.32.0" @@ -1720,7 +1678,7 @@ PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" [package.extras] -broker = ["pymsalruntime (>=0.14,<0.18) ; python_version >= \"3.6\" and platform_system == \"Windows\"", "pymsalruntime (>=0.17,<0.18) ; python_version >= \"3.8\" and platform_system == \"Darwin\""] +broker = ["pymsalruntime (>=0.14,<0.18)", "pymsalruntime (>=0.17,<0.18)"] [[package]] name = "msal-extensions" @@ -2089,6 +2047,19 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "python_version < \"3.9\"" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -2122,18 +2093,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "priority" -version = "2.0.0" -description = "A pure-Python implementation of the HTTP/2 priority tree" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, - {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, -] - [[package]] name = "prisma" version = "0.11.0" @@ -2358,7 +2317,7 @@ description = "C parser in Python" optional = true python-versions = ">=3.8" groups = ["main"] -markers = "(extra == \"proxy\" or extra == \"extra-proxy\") and platform_python_implementation != \"PyPy\" or extra == \"proxy\"" +markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2384,7 +2343,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" @@ -2499,27 +2458,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pydantic-settings" -version = "2.8.1" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, - {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" - -[package.extras] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - [[package]] name = "pyflakes" version = "3.1.0" @@ -2908,21 +2846,6 @@ files = [ [package.dependencies] requests = "2.31.0" -[[package]] -name = "respx" -version = "0.22.0" -description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, - {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, -] - -[package.dependencies] -httpx = ">=0.25.0" - [[package]] name = "rpds-py" version = "0.20.1" @@ -3113,33 +3036,14 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "sse-starlette" -version = "2.1.3" -description = "SSE plugin for Starlette" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, - {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, -] - -[package.dependencies] -anyio = "*" -starlette = "*" -uvicorn = "*" - -[package.extras] -examples = ["fastapi"] - [[package]] name = "starlette" version = "0.44.0" description = "The little ASGI library that shines." -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, @@ -3147,27 +3051,11 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] -[[package]] -name = "taskgroup" -version = "0.2.2" -description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout" -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version < \"3.11\"" -files = [ - {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"}, - {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"}, -] - -[package.dependencies] -exceptiongroup = "*" -typing_extensions = ">=4.12.2,<5" - [[package]] name = "tiktoken" version = "0.7.0" @@ -3260,7 +3148,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["dev"] markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, @@ -3371,11 +3259,30 @@ files = [ ] [package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +[[package]] +name = "urllib3" +version = "1.26.20" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] +markers = "python_version < \"3.10\"" +files = [ + {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, + {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "urllib3" version = "2.2.3" @@ -3383,13 +3290,14 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.8" groups = ["main"] +markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3398,9 +3306,10 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.29.0" description = "The lightning-fast ASGI server." -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -3412,7 +3321,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -3564,21 +3473,6 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] -[[package]] -name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, -] - -[package.dependencies] -h11 = ">=0.9.0,<1" - [[package]] name = "yarl" version = "1.15.2" @@ -3705,11 +3599,11 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] @@ -3718,5 +3612,5 @@ proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", [metadata] lock-version = "2.1" -python-versions = ">=3.10,<4.0" -content-hash = "56260c4dea3b139d8674043a1df5e4102cbf6d021bcc41f1fb39368cbc53fcc8" +python-versions = ">=3.8.1,<4.0, !=3.9.7" +content-hash = "55078af47c1af79bd3ebadacb7ba92844d550a577bb0c49f5096693701ea4322" diff --git a/pyproject.toml b/pyproject.toml index 4b31ce80a1..58fd5a7ded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ documentation = "https://docs.litellm.ai" Documentation = "https://docs.litellm.ai" [tool.poetry.dependencies] -python = ">=3.10,<4.0" +python = ">=3.8.1,<4.0, !=3.9.7" httpx = ">=0.23.0" openai = ">=1.66.1" python-dotenv = ">=0.2.0" @@ -53,9 +53,6 @@ resend = {version = "^0.8.0", optional = true} pynacl = {version = "^1.5.0", optional = true} websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.34.34", optional = true} -respx = "^0.22.0" -mcp = "^1.5.0" -hypercorn = "^0.17.3" [tool.poetry.extras] proxy = [ diff --git a/tests/litellm/rerank_api/test_rerank_main.py b/tests/litellm/rerank_api/test_rerank_main.py index a908293826..d3051b81f9 100644 --- a/tests/litellm/rerank_api/test_rerank_main.py +++ b/tests/litellm/rerank_api/test_rerank_main.py @@ -13,7 +13,7 @@ from unittest.mock import MagicMock, patch from litellm import rerank from litellm.llms.custom_httpx.http_handler import HTTPHandler -@pytest.mark.skip(reason="This test requires a valid rerank API key") + def test_rerank_infer_region_from_model_arn(monkeypatch): mock_response = MagicMock() diff --git a/tests/litellm/router_strategy/test_base_routing_strategy.py b/tests/litellm/router_strategy/test_base_routing_strategy.py index faa6c40c57..b47a2f1c90 100644 --- a/tests/litellm/router_strategy/test_base_routing_strategy.py +++ b/tests/litellm/router_strategy/test_base_routing_strategy.py @@ -19,7 +19,7 @@ from litellm.router_strategy.base_routing_strategy import BaseRoutingStrategy @pytest.fixture -async def mock_dual_cache(): +def mock_dual_cache(): dual_cache = MagicMock(spec=DualCache) dual_cache.in_memory_cache = MagicMock() dual_cache.redis_cache = MagicMock() From e7181395ffb5c705ce444b7269d2992875aaec9e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 10:32:15 -0700 Subject: [PATCH 152/244] fix code quality check --- litellm/llms/xai/common_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/xai/common_utils.py b/litellm/llms/xai/common_utils.py index fdf2edbfa3..d7ceeadd95 100644 --- a/litellm/llms/xai/common_utils.py +++ b/litellm/llms/xai/common_utils.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional import httpx @@ -22,7 +22,7 @@ class XAIModelInfo(BaseLLMModelInfo): def get_models( self, api_key: Optional[str] = None, api_base: Optional[str] = None - ) -> list[str]: + ) -> List[str]: api_base = self.get_api_base(api_base) api_key = self.get_api_key(api_key) if api_base is None or api_key is None: From 0865e52db356c2d37655a2bb1f9278fb509e7c27 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 12:32:04 -0700 Subject: [PATCH 153/244] =?UTF-8?q?fix(proxy=5Fserver.py):=20get=20master?= =?UTF-8?q?=20key=20from=20environment,=20if=20not=20set=20in=20=E2=80=A6?= =?UTF-8?q?=20(#9617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(proxy_server.py): get master key from environment, if not set in general settings or general settings not set at all * test: mark flaky test * test(test_proxy_server.py): mock prisma client * ci: add new github workflow for testing just the mock tests * fix: fix linting error * ci(conftest.py): add conftest.py to isolate proxy tests * build(pyproject.toml): add respx to dev dependencies * build(pyproject.toml): add prisma to dev dependencies * test: fix mock prompt management tests to use a mock anthropic key * ci(test-litellm.yml): parallelize mock testing make it run faster * build(pyproject.toml): add hypercorn as dev dep * build(pyproject.toml): separate proxy vs. core dev dependencies make it easier for non-proxy contributors to run tests locally - e.g. no need to install hypercorn * ci(test-litellm.yml): pin python version * test(test_rerank.py): move test - cannot be mocked, requires aws credentials for e2e testing * ci: add thank you message to ci * test: add mock env var to test * test: add autouse to tests * test: test mock env vars for e2e tests --- .github/workflows/test-litellm.yml | 35 ++ Makefile | 3 + litellm/llms/xai/common_utils.py | 4 +- litellm/proxy/proxy_server.py | 2 + poetry.lock | 444 +++++++++--------- pyproject.toml | 5 + tests/litellm/conftest.py | 63 +++ .../test_custom_prompt_management.py | 7 +- .../test_spend_management_endpoints.py | 7 +- tests/litellm/proxy/test_proxy_server.py | 90 ++++ tests/litellm/rerank_api/test_rerank_main.py | 51 -- tests/litellm/test_main.py | 12 +- tests/llm_translation/test_cohere.py | 1 + tests/llm_translation/test_rerank.py | 39 ++ 14 files changed, 479 insertions(+), 284 deletions(-) create mode 100644 .github/workflows/test-litellm.yml create mode 100644 tests/litellm/conftest.py delete mode 100644 tests/litellm/rerank_api/test_rerank_main.py diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml new file mode 100644 index 0000000000..09de0ff110 --- /dev/null +++ b/.github/workflows/test-litellm.yml @@ -0,0 +1,35 @@ +name: LiteLLM Tests + +on: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - name: Thank You Message + run: | + echo "### 🙏 Thank you for contributing to LiteLLM!" >> $GITHUB_STEP_SUMMARY + echo "Your PR is being tested now. We appreciate your help in making LiteLLM better!" >> $GITHUB_STEP_SUMMARY + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: | + poetry install --with dev,proxy-dev --extras proxy + poetry run pip install pytest-xdist + + - name: Run tests + run: | + poetry run pytest tests/litellm -x -vv -n 4 \ No newline at end of file diff --git a/Makefile b/Makefile index 6c231d3cc2..a06509312d 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ help: install-dev: poetry install --with dev +install-proxy-dev: + poetry install --with dev,proxy-dev + lint: install-dev poetry run pip install types-requests types-setuptools types-redis types-PyYAML cd litellm && poetry run mypy . --ignore-missing-imports diff --git a/litellm/llms/xai/common_utils.py b/litellm/llms/xai/common_utils.py index fdf2edbfa3..d7ceeadd95 100644 --- a/litellm/llms/xai/common_utils.py +++ b/litellm/llms/xai/common_utils.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional import httpx @@ -22,7 +22,7 @@ class XAIModelInfo(BaseLLMModelInfo): def get_models( self, api_key: Optional[str] = None, api_base: Optional[str] = None - ) -> list[str]: + ) -> List[str]: api_base = self.get_api_base(api_base) api_key = self.get_api_key(api_key) if api_base is None or api_key is None: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index df295c3697..c60cc1378e 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -462,6 +462,8 @@ async def proxy_startup_event(app: FastAPI): if premium_user is False: premium_user = _license_check.is_premium() + ## CHECK MASTER KEY IN ENVIRONMENT ## + master_key = get_secret_str("LITELLM_MASTER_KEY") ### LOAD CONFIG ### worker_config: Optional[Union[str, dict]] = get_secret("WORKER_CONFIG") # type: ignore env_config_yaml: Optional[str] = get_secret_str("CONFIG_FILE_PATH") diff --git a/poetry.lock b/poetry.lock index 9273f91acc..64e8eaa8ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,7 +6,6 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -18,7 +17,6 @@ version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, @@ -131,7 +129,6 @@ version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, @@ -146,7 +143,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -161,7 +157,6 @@ version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, @@ -184,8 +179,6 @@ version = "3.11.0" description = "In-process task scheduler with Cron-like capabilities" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"}, {file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"}, @@ -214,8 +207,6 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_full_version < \"3.11.3\" and (extra == \"extra-proxy\" or extra == \"proxy\" or python_version < \"3.11\")" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -227,7 +218,6 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, @@ -247,8 +237,6 @@ version = "1.32.0" description = "Microsoft Azure Core Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4"}, {file = "azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5"}, @@ -268,8 +256,6 @@ version = "1.21.0" description = "Microsoft Azure Identity Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9"}, {file = "azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6"}, @@ -288,8 +274,6 @@ version = "4.9.0" description = "Microsoft Azure Key Vault Secrets Client Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_keyvault_secrets-4.9.0-py3-none-any.whl", hash = "sha256:33c7e2aca2cc2092cebc8c6e96eca36a5cc30c767e16ea429c5fa21270e9fba6"}, {file = "azure_keyvault_secrets-4.9.0.tar.gz", hash = "sha256:2a03bb2ffd9a0d6c8ad1c330d9d0310113985a9de06607ece378fd72a5889fe1"}, @@ -306,8 +290,6 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = true python-versions = ">=3.7,<4.0" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -319,8 +301,6 @@ version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\" and python_version < \"3.9\"" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, @@ -349,7 +329,6 @@ version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, @@ -396,8 +375,6 @@ version = "1.34.34" description = "The AWS SDK for Python" optional = true python-versions = ">= 3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "boto3-1.34.34-py3-none-any.whl", hash = "sha256:33a8b6d9136fa7427160edb92d2e50f2035f04e9d63a2d1027349053e12626aa"}, {file = "boto3-1.34.34.tar.gz", hash = "sha256:b2f321e20966f021ec800b7f2c01287a3dd04fc5965acdfbaa9c505a24ca45d1"}, @@ -417,8 +394,6 @@ version = "1.34.162" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "botocore-1.34.162-py3-none-any.whl", hash = "sha256:2d918b02db88d27a75b48275e6fb2506e9adaaddbec1ffa6a8a0898b34e769be"}, {file = "botocore-1.34.162.tar.gz", hash = "sha256:adc23be4fb99ad31961236342b7cbf3c0bfc62532cd02852196032e8c0d682f3"}, @@ -428,8 +403,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -441,8 +416,6 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -454,7 +427,6 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -466,8 +438,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -547,7 +517,6 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -649,7 +618,6 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -664,12 +632,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "coloredlogs" @@ -677,8 +643,6 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -696,8 +660,6 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -747,7 +709,6 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -759,8 +720,6 @@ version = "2.6.1" description = "DNS toolkit" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -781,8 +740,6 @@ version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -798,8 +755,6 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -814,8 +769,6 @@ version = "0.115.12" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, @@ -836,8 +789,6 @@ version = "0.16.0" description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" optional = true python-versions = "<4.0,>=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "fastapi_sso-0.16.0-py3-none-any.whl", hash = "sha256:3a66a942474ef9756d3a9d8b945d55bd9faf99781facdb9b87a40b73d6d6b0c3"}, {file = "fastapi_sso-0.16.0.tar.gz", hash = "sha256:f3941f986347566b7d3747c710cf474a907f581bfb6697ff3bb3e44eb76b438c"}, @@ -856,7 +807,6 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -873,7 +823,6 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -890,7 +839,6 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -992,7 +940,6 @@ version = "2025.3.0" description = "File-system specification" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"}, {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"}, @@ -1032,8 +979,6 @@ version = "2.24.2" description = "Google API client core library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, @@ -1043,12 +988,12 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] proto-plus = [ {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, @@ -1069,8 +1014,6 @@ version = "2.38.0" description = "Google Authentication Library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, @@ -1095,8 +1038,6 @@ version = "2.24.2" description = "Google Cloud Kms API client library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_cloud_kms-2.24.2-py2.py3-none-any.whl", hash = "sha256:368209b035dfac691a467c1cf50986d8b1b26cac1166bdfbaa25d738df91ff7b"}, {file = "google_cloud_kms-2.24.2.tar.gz", hash = "sha256:e9e18bbfafd1a4035c76c03fb5ff03f4f57f596d08e1a9ede7e69ec0151b27a1"}, @@ -1115,8 +1056,6 @@ version = "1.69.2" description = "Common protobufs used in Google APIs" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, @@ -1135,8 +1074,6 @@ version = "0.14.2" description = "IAM API client library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351"}, {file = "grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20"}, @@ -1153,8 +1090,6 @@ version = "1.70.0" description = "HTTP/2-based RPC framework" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, @@ -1216,14 +1151,75 @@ files = [ [package.extras] protobuf = ["grpcio-tools (>=1.70.0)"] +[[package]] +name = "grpcio" +version = "1.71.0" +description = "HTTP/2-based RPC framework" +optional = true +python-versions = ">=3.9" +files = [ + {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, + {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"}, + {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"}, + {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"}, + {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"}, + {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"}, + {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"}, + {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"}, + {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"}, + {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"}, + {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"}, + {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"}, + {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"}, + {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"}, + {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"}, + {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"}, + {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"}, + {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"}, + {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"}, + {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, + {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.71.0)"] + [[package]] name = "grpcio-status" version = "1.70.0" description = "Status proto mapping for gRPC" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85"}, {file = "grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101"}, @@ -1234,14 +1230,28 @@ googleapis-common-protos = ">=1.5.5" grpcio = ">=1.70.0" protobuf = ">=5.26.1,<6.0dev" +[[package]] +name = "grpcio-status" +version = "1.71.0" +description = "Status proto mapping for gRPC" +optional = true +python-versions = ">=3.9" +files = [ + {file = "grpcio_status-1.71.0-py3-none-any.whl", hash = "sha256:843934ef8c09e3e858952887467f8256aac3910c55f077a359a65b2b3cde3e68"}, + {file = "grpcio_status-1.71.0.tar.gz", hash = "sha256:11405fed67b68f406b3f3c7c5ae5104a79d2d309666d10d61b152e91d28fb968"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.71.0" +protobuf = ">=5.26.1,<6.0dev" + [[package]] name = "gunicorn" version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -1263,19 +1273,43 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + [[package]] name = "httpcore" version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -1297,7 +1331,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1322,8 +1355,6 @@ version = "0.4.0" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, @@ -1335,7 +1366,6 @@ version = "0.29.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" -groups = ["main"] files = [ {file = "huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa"}, {file = "huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5"}, @@ -1370,8 +1400,6 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -1380,13 +1408,48 @@ files = [ [package.dependencies] pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} +[[package]] +name = "hypercorn" +version = "0.15.0" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hypercorn-0.15.0-py3-none-any.whl", hash = "sha256:5008944999612fd188d7a1ca02e89d20065642b89503020ac392dfed11840730"}, + {file = "hypercorn-0.15.0.tar.gz", hash = "sha256:d517f68d5dc7afa9a9d50ecefb0f769f466ebe8c1c18d2c2f447a24e763c9a63"}, +] + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +taskgroup = {version = "*", markers = "python_version < \"3.11\""} +tomli = {version = "*", markers = "python_version < \"3.11\""} +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] +uvloop = ["uvloop"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1401,7 +1464,6 @@ version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -1425,8 +1487,6 @@ version = "6.4.5" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.9\"" files = [ {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, @@ -1449,7 +1509,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1461,8 +1520,6 @@ version = "0.7.2" description = "An ISO 8601 date/time/duration parser and formatter" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -1474,7 +1531,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1492,7 +1548,6 @@ version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, @@ -1578,8 +1633,6 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1591,7 +1644,6 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -1615,7 +1667,6 @@ version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, @@ -1631,7 +1682,6 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -1701,7 +1751,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1713,8 +1762,6 @@ version = "1.5.0" description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527"}, {file = "mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9"}, @@ -1741,8 +1788,6 @@ version = "0.4.1" description = "" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5"}, {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24"}, @@ -1765,10 +1810,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">1.20", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] [package.extras] @@ -1780,8 +1825,6 @@ version = "1.32.0" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7"}, {file = "msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2"}, @@ -1801,8 +1844,6 @@ version = "1.3.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "msal_extensions-1.3.0-py3-none-any.whl", hash = "sha256:105328ddcbdd342016c9949d8f89e3917554740c8ab26669c0fa0e069e730a0e"}, {file = "msal_extensions-1.3.0.tar.gz", hash = "sha256:96918996642b38c78cd59b55efa0f06fd1373c90e0949be8615697c048fba62c"}, @@ -1820,7 +1861,6 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1925,7 +1965,6 @@ version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -1985,7 +2024,6 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1995,10 +2033,8 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" -optional = true +optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -2010,8 +2046,6 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.12\"" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2057,8 +2091,6 @@ version = "2.2.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.12\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"}, {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"}, @@ -2123,8 +2155,6 @@ version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, @@ -2137,14 +2167,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "openai" -version = "1.68.2" +version = "1.69.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ - {file = "openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36"}, - {file = "openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19"}, + {file = "openai-1.69.0-py3-none-any.whl", hash = "sha256:73c4b2ddfd050060f8d93c70367189bd891e70a5adb6d69c04c3571f4fea5627"}, + {file = "openai-1.69.0.tar.gz", hash = "sha256:7b8a10a8ff77e1ae827e5e4c8480410af2070fb68bc973d6c994cf8218f1f98d"}, ] [package.dependencies] @@ -2168,8 +2197,6 @@ version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, @@ -2258,7 +2285,6 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -2270,7 +2296,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2282,8 +2307,6 @@ version = "1.3.10" description = "Resolve a name to an object." optional = false python-versions = ">=3.6" -groups = ["main"] -markers = "python_version < \"3.9\"" files = [ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, @@ -2295,7 +2318,6 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -2312,7 +2334,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2322,14 +2343,23 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] + [[package]] name = "prisma" version = "0.11.0" description = "Prisma Client Python is an auto-generated and fully type-safe database client" -optional = true +optional = false python-versions = ">=3.7.0" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "prisma-0.11.0-py3-none-any.whl", hash = "sha256:22bb869e59a2968b99f3483bb417717273ffbc569fd1e9ceed95e5614cbaf53a"}, {file = "prisma-0.11.0.tar.gz", hash = "sha256:3f2f2fd2361e1ec5ff655f2a04c7860c2f2a5bc4c91f78ca9c5c6349735bf693"}, @@ -2355,7 +2385,6 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -2463,8 +2492,6 @@ version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -2482,8 +2509,6 @@ version = "5.29.4" description = "" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"}, {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"}, @@ -2504,8 +2529,6 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -2513,19 +2536,17 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.4.1" +version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" +pyasn1 = ">=0.6.1,<0.7.0" [[package]] name = "pycodestyle" @@ -2533,7 +2554,6 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -2545,8 +2565,6 @@ version = "2.22" description = "C parser in Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2558,7 +2576,6 @@ version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -2580,7 +2597,6 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -2693,8 +2709,6 @@ version = "2.8.1" description = "Settings management using Pydantic" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, @@ -2715,7 +2729,6 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -2727,8 +2740,6 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\"" files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -2749,8 +2760,6 @@ version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, @@ -2777,8 +2786,6 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and sys_platform == \"win32\" and python_version < \"3.14\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -2793,7 +2800,6 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -2816,7 +2822,6 @@ version = "0.21.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, @@ -2835,7 +2840,6 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2853,8 +2857,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2869,7 +2871,6 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -2884,8 +2885,6 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -2897,8 +2896,6 @@ version = "3.0.0" description = "Universally unique lexicographically sortable identifier" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, @@ -2913,7 +2910,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2976,8 +2972,6 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\" or extra == \"proxy\"" files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2996,8 +2990,6 @@ version = "0.4.1" description = "Python client library and CLI for using Redis as a vector database" optional = true python-versions = "<3.14,>=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "redisvl-0.4.1-py3-none-any.whl", hash = "sha256:6db5d5bc95b1fe8032a1cdae74ce1c65bc7fe9054e5429b5d34d5a91d28bae5f"}, {file = "redisvl-0.4.1.tar.gz", hash = "sha256:fd6a36426ba94792c0efca20915c31232d4ee3cc58eb23794a62c142696401e6"}, @@ -3032,7 +3024,6 @@ version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -3048,7 +3039,6 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -3152,7 +3142,6 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -3174,8 +3163,6 @@ version = "0.8.0" description = "Resend Python SDK" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "resend-0.8.0-py2.py3-none-any.whl", hash = "sha256:adc1515dadf4f4fc6b90db55a237f0f37fc56fd74287a986519a8a187fdb661d"}, {file = "resend-0.8.0.tar.gz", hash = "sha256:94142394701724dbcfcd8f760f675c662a1025013e741dd7cc773ca885526257"}, @@ -3184,13 +3171,26 @@ files = [ [package.dependencies] requests = "2.31.0" +[[package]] +name = "respx" +version = "0.20.2" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +optional = false +python-versions = ">=3.7" +files = [ + {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, + {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, +] + +[package.dependencies] +httpx = ">=0.21.0" + [[package]] name = "rpds-py" version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, @@ -3303,8 +3303,6 @@ version = "2.2.0" description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "rq-2.2.0-py3-none-any.whl", hash = "sha256:dacbfe1ccb79a45c8cd95dec7951620679fa0195570b63da3f9347622d33accc"}, {file = "rq-2.2.0.tar.gz", hash = "sha256:b636760f1e4c183022031c142faa0483e687885824e9732ba2953f994104e203"}, @@ -3320,8 +3318,6 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = true python-versions = ">=3.6,<4" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -3336,8 +3332,6 @@ version = "0.10.4" description = "An Amazon S3 Transfer Manager" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, @@ -3355,8 +3349,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "extra == \"extra-proxy\" or extra == \"proxy\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3368,7 +3360,6 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3380,8 +3371,6 @@ version = "2.1.3" description = "SSE plugin for Starlette" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, @@ -3401,8 +3390,6 @@ version = "0.44.0" description = "The little ASGI library that shines." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, @@ -3421,8 +3408,6 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -3431,14 +3416,27 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "taskgroup" +version = "0.2.2" +description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout" +optional = false +python-versions = "*" +files = [ + {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"}, + {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"}, +] + +[package.dependencies] +exceptiongroup = "*" +typing_extensions = ">=4.12.2,<5" + [[package]] name = "tenacity" version = "9.0.0" description = "Retry code until it succeeds" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -3454,7 +3452,6 @@ version = "0.7.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, @@ -3507,7 +3504,6 @@ version = "0.21.0" description = "" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, @@ -3540,8 +3536,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3581,10 +3575,8 @@ files = [ name = "tomlkit" version = "0.13.2" description = "Style preserving TOML library" -optional = true +optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -3596,7 +3588,6 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -3614,14 +3605,13 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, + {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, ] [[package]] @@ -3630,8 +3620,6 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = true python-versions = ">=2" -groups = ["main"] -markers = "extra == \"proxy\" and platform_system == \"Windows\"" files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -3643,8 +3631,6 @@ version = "5.2" description = "tzinfo object for the local timezone" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, @@ -3663,8 +3649,6 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main"] -markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, @@ -3681,8 +3665,6 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -3700,8 +3682,6 @@ version = "0.29.0" description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -3721,8 +3701,6 @@ version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = true python-versions = ">=3.8.0" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, @@ -3774,8 +3752,6 @@ version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, @@ -3865,13 +3841,26 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "yarl" version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, @@ -3984,7 +3973,6 @@ version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, @@ -4003,6 +3991,6 @@ extra-proxy = ["azure-identity", "azure-keyvault-secrets", "google-cloud-kms", " proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", "fastapi-sso", "gunicorn", "mcp", "orjson", "pynacl", "python-multipart", "pyyaml", "rq", "uvicorn", "uvloop", "websockets"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "9c863b11189227a035a9130c8872de44fe7c5e1e32b47569a56af86e3f6570c5" +content-hash = "0f195796116a7c7a4a04d9958a7662d74baccb5266f531bb58a4403fd4db4e0e" diff --git a/pyproject.toml b/pyproject.toml index c3b0fd3224..325bf8b4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,11 @@ mypy = "^1.0" pytest = "^7.4.3" pytest-mock = "^3.12.0" pytest-asyncio = "^0.21.1" +respx = "^0.20.2" + +[tool.poetry.group.proxy-dev.dependencies] +prisma = "0.11.0" +hypercorn = "^0.15.0" [build-system] requires = ["poetry-core", "wheel"] diff --git a/tests/litellm/conftest.py b/tests/litellm/conftest.py new file mode 100644 index 0000000000..b3561d8a62 --- /dev/null +++ b/tests/litellm/conftest.py @@ -0,0 +1,63 @@ +# conftest.py + +import importlib +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +import litellm + + +@pytest.fixture(scope="function", autouse=True) +def setup_and_teardown(): + """ + This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained. + """ + curr_dir = os.getcwd() # Get the current working directory + sys.path.insert( + 0, os.path.abspath("../..") + ) # Adds the project directory to the system path + + import litellm + from litellm import Router + + importlib.reload(litellm) + + try: + if hasattr(litellm, "proxy") and hasattr(litellm.proxy, "proxy_server"): + import litellm.proxy.proxy_server + + importlib.reload(litellm.proxy.proxy_server) + except Exception as e: + print(f"Error reloading litellm.proxy.proxy_server: {e}") + + import asyncio + + loop = asyncio.get_event_loop_policy().new_event_loop() + asyncio.set_event_loop(loop) + print(litellm) + # from litellm import Router, completion, aembedding, acompletion, embedding + yield + + # Teardown code (executes after the yield point) + loop.close() # Close the loop created earlier + asyncio.set_event_loop(None) # Remove the reference to the loop + + +def pytest_collection_modifyitems(config, items): + # Separate tests in 'test_amazing_proxy_custom_logger.py' and other tests + custom_logger_tests = [ + item for item in items if "custom_logger" in item.parent.name + ] + other_tests = [item for item in items if "custom_logger" not in item.parent.name] + + # Sort tests based on their names + custom_logger_tests.sort(key=lambda x: x.name) + other_tests.sort(key=lambda x: x.name) + + # Reorder the items list + items[:] = custom_logger_tests + other_tests diff --git a/tests/litellm/integrations/test_custom_prompt_management.py b/tests/litellm/integrations/test_custom_prompt_management.py index 00dc6fe96f..646b222e72 100644 --- a/tests/litellm/integrations/test_custom_prompt_management.py +++ b/tests/litellm/integrations/test_custom_prompt_management.py @@ -19,6 +19,11 @@ from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import StandardCallbackDynamicParams +@pytest.fixture(autouse=True) +def setup_anthropic_api_key(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-some-key") + + class TestCustomPromptManagement(CustomPromptManagement): def get_chat_completion_prompt( self, @@ -50,7 +55,7 @@ class TestCustomPromptManagement(CustomPromptManagement): @pytest.mark.asyncio -async def test_custom_prompt_management_with_prompt_id(): +async def test_custom_prompt_management_with_prompt_id(monkeypatch): custom_prompt_management = TestCustomPromptManagement() litellm.callbacks = [custom_prompt_management] diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index a5ee9ddf70..609fbbcc52 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -26,6 +26,11 @@ def client(): return TestClient(app) +@pytest.fixture(autouse=True) +def add_anthropic_api_key_to_env(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") + + @pytest.mark.asyncio async def test_ui_view_spend_logs_with_user_id(client, monkeypatch): # Mock data for the test @@ -500,7 +505,7 @@ class TestSpendLogsPayload: return mock_response @pytest.mark.asyncio - async def test_spend_logs_payload_success_log_with_api_base(self): + async def test_spend_logs_payload_success_log_with_api_base(self, monkeypatch): from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler litellm.callbacks = [_ProxyDBLogger(message_logging=False)] diff --git a/tests/litellm/proxy/test_proxy_server.py b/tests/litellm/proxy/test_proxy_server.py index 77b521e836..c1e935addd 100644 --- a/tests/litellm/proxy/test_proxy_server.py +++ b/tests/litellm/proxy/test_proxy_server.py @@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, mock_open, patch import click import httpx import pytest +import yaml from fastapi import FastAPI from fastapi.testclient import TestClient @@ -74,3 +75,92 @@ async def test_initialize_scheduled_jobs_credentials(monkeypatch): call[0] for call in mock_proxy_config.get_credentials.mock_calls ] assert len(mock_scheduler_calls) > 0 + + +# Mock Prisma +class MockPrisma: + def __init__(self, database_url=None, proxy_logging_obj=None, http_client=None): + self.database_url = database_url + self.proxy_logging_obj = proxy_logging_obj + self.http_client = http_client + + async def connect(self): + pass + + async def disconnect(self): + pass + + +mock_prisma = MockPrisma() + + +@patch( + "litellm.proxy.proxy_server.ProxyStartupEvent._setup_prisma_client", + return_value=mock_prisma, +) +@pytest.mark.asyncio +async def test_aaaproxy_startup_master_key(mock_prisma, monkeypatch, tmp_path): + """ + Test that master_key is correctly loaded from either config.yaml or environment variables + """ + import yaml + from fastapi import FastAPI + + # Import happens here - this is when the module probably reads the config path + from litellm.proxy.proxy_server import proxy_startup_event + + # Mock the Prisma import + monkeypatch.setattr("litellm.proxy.proxy_server.PrismaClient", MockPrisma) + + # Create test app + app = FastAPI() + + # Test Case 1: Master key from config.yaml + test_master_key = "sk-12345" + test_config = {"general_settings": {"master_key": test_master_key}} + + # Create a temporary config file + config_path = tmp_path / "config.yaml" + with open(config_path, "w") as f: + yaml.dump(test_config, f) + + print(f"SET ENV VARIABLE - CONFIG_FILE_PATH, str(config_path): {str(config_path)}") + # Second setting of CONFIG_FILE_PATH to a different value + monkeypatch.setenv("CONFIG_FILE_PATH", str(config_path)) + print(f"config_path: {config_path}") + print(f"os.getenv('CONFIG_FILE_PATH'): {os.getenv('CONFIG_FILE_PATH')}") + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_master_key + + # Test Case 2: Master key from environment variable + test_env_master_key = "sk-67890" + + # Create empty config + empty_config = {"general_settings": {}} + with open(config_path, "w") as f: + yaml.dump(empty_config, f) + + monkeypatch.setenv("LITELLM_MASTER_KEY", test_env_master_key) + print("test_env_master_key: {}".format(test_env_master_key)) + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_env_master_key + + # Test Case 3: Master key with os.environ prefix + test_resolved_key = "sk-resolved-key" + test_config_with_prefix = { + "general_settings": {"master_key": "os.environ/CUSTOM_MASTER_KEY"} + } + + # Create config with os.environ prefix + with open(config_path, "w") as f: + yaml.dump(test_config_with_prefix, f) + + monkeypatch.setenv("CUSTOM_MASTER_KEY", test_resolved_key) + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_resolved_key diff --git a/tests/litellm/rerank_api/test_rerank_main.py b/tests/litellm/rerank_api/test_rerank_main.py deleted file mode 100644 index d3051b81f9..0000000000 --- a/tests/litellm/rerank_api/test_rerank_main.py +++ /dev/null @@ -1,51 +0,0 @@ -import json -import os -import sys - -import pytest -from fastapi.testclient import TestClient - -sys.path.insert( - 0, os.path.abspath("../../..") -) # Adds the parent directory to the system path -from unittest.mock import MagicMock, patch - -from litellm import rerank -from litellm.llms.custom_httpx.http_handler import HTTPHandler - - -def test_rerank_infer_region_from_model_arn(monkeypatch): - mock_response = MagicMock() - - monkeypatch.setenv("AWS_REGION_NAME", "us-east-1") - args = { - "model": "bedrock/arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0", - "query": "hello", - "documents": ["hello", "world"], - } - - def return_val(): - return { - "results": [ - {"index": 0, "relevanceScore": 0.6716859340667725}, - {"index": 1, "relevanceScore": 0.0004994205664843321}, - ] - } - - mock_response.json = return_val - mock_response.headers = {"key": "value"} - mock_response.status_code = 200 - - client = HTTPHandler() - - with patch.object(client, "post", return_value=mock_response) as mock_post: - rerank( - model=args["model"], - query=args["query"], - documents=args["documents"], - client=client, - ) - mock_post.assert_called_once() - print(f"mock_post.call_args: {mock_post.call_args.kwargs}") - assert "us-west-2" in mock_post.call_args.kwargs["url"] - assert "us-east-1" not in mock_post.call_args.kwargs["url"] diff --git a/tests/litellm/test_main.py b/tests/litellm/test_main.py index 4892300eb9..99ac8514f6 100644 --- a/tests/litellm/test_main.py +++ b/tests/litellm/test_main.py @@ -14,6 +14,15 @@ from unittest.mock import MagicMock, patch import litellm +@pytest.fixture(autouse=True) +def add_api_keys_to_env(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") + monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-api03-1234567890") + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "my-fake-aws-access-key-id") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "my-fake-aws-secret-access-key") + monkeypatch.setenv("AWS_REGION", "us-east-1") + + @pytest.fixture def openai_api_response(): mock_response_data = { @@ -130,7 +139,8 @@ def test_completion_missing_role(openai_api_response): ) @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio -async def test_url_with_format_param(model, sync_mode): +async def test_url_with_format_param(model, sync_mode, monkeypatch): + from litellm import acompletion, completion from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler diff --git a/tests/llm_translation/test_cohere.py b/tests/llm_translation/test_cohere.py index f34098477d..72df3ad436 100644 --- a/tests/llm_translation/test_cohere.py +++ b/tests/llm_translation/test_cohere.py @@ -22,6 +22,7 @@ litellm.num_retries = 3 @pytest.mark.parametrize("stream", [True, False]) +@pytest.mark.flaky(retries=3, delay=1) @pytest.mark.asyncio async def test_chat_completion_cohere_citations(stream): try: diff --git a/tests/llm_translation/test_rerank.py b/tests/llm_translation/test_rerank.py index d2cb2b6fea..5de6c1a8ec 100644 --- a/tests/llm_translation/test_rerank.py +++ b/tests/llm_translation/test_rerank.py @@ -481,3 +481,42 @@ def test_rerank_cohere_api(): assert response.results[0]["document"]["text"] is not None assert response.results[0]["document"]["text"] == "hello" assert response.results[1]["document"]["text"] == "world" + + +def test_rerank_infer_region_from_model_arn(monkeypatch): + + mock_response = MagicMock() + + monkeypatch.setenv("AWS_REGION_NAME", "us-east-1") + args = { + "model": "bedrock/arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0", + "query": "hello", + "documents": ["hello", "world"], + } + + def return_val(): + return { + "results": [ + {"index": 0, "relevanceScore": 0.6716859340667725}, + {"index": 1, "relevanceScore": 0.0004994205664843321}, + ] + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + client = HTTPHandler() + + with patch.object(client, "post", return_value=mock_response) as mock_post: + litellm.rerank( + model=args["model"], + query=args["query"], + documents=args["documents"], + client=client, + ) + + mock_post.assert_called_once() + print(f"mock_post.call_args: {mock_post.call_args.kwargs}") + assert "us-west-2" in mock_post.call_args.kwargs["url"] + assert "us-east-1" not in mock_post.call_args.kwargs["url"] From f3ff7e2779c42e5dbea11af783560d7c8575d98f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 12:47:26 -0700 Subject: [PATCH 154/244] fix(logging): add json formatting for uncaught exceptions (#9615) (#9619) * fix(logging): add json formatting for uncaught exceptions (#9615) * fix(_logging.py): cleanup logging to catch unhandled exceptions * fix(_logging.py): avoid using 'print' ' --------- Co-authored-by: Henrique Cavarsan --- litellm/_logging.py | 60 +++++++++++++++++++++++++-- litellm/proxy/_new_secret_config.yaml | 1 + 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/litellm/_logging.py b/litellm/_logging.py index 151ae6003d..d7e2c9e778 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -1,6 +1,7 @@ import json import logging import os +import sys from datetime import datetime from logging import Formatter @@ -40,9 +41,56 @@ class JsonFormatter(Formatter): return json.dumps(json_record) +# Function to set up exception handlers for JSON logging +def _setup_json_exception_handlers(formatter): + # Create a handler with JSON formatting for exceptions + error_handler = logging.StreamHandler() + error_handler.setFormatter(formatter) + + # Setup excepthook for uncaught exceptions + def json_excepthook(exc_type, exc_value, exc_traceback): + record = logging.LogRecord( + name="LiteLLM", + level=logging.ERROR, + pathname="", + lineno=0, + msg=str(exc_value), + args=(), + exc_info=(exc_type, exc_value, exc_traceback), + ) + error_handler.handle(record) + + sys.excepthook = json_excepthook + + # Configure asyncio exception handler if possible + try: + import asyncio + + def async_json_exception_handler(loop, context): + exception = context.get("exception") + if exception: + record = logging.LogRecord( + name="LiteLLM", + level=logging.ERROR, + pathname="", + lineno=0, + msg=str(exception), + args=(), + exc_info=None, + ) + error_handler.handle(record) + else: + loop.default_exception_handler(context) + + asyncio.get_event_loop().set_exception_handler(async_json_exception_handler) + except Exception: + pass + + # Create a formatter and set it for the handler if json_logs: handler.setFormatter(JsonFormatter()) + _setup_json_exception_handlers(JsonFormatter()) else: formatter = logging.Formatter( "\033[92m%(asctime)s - %(name)s:%(levelname)s\033[0m: %(filename)s:%(lineno)s - %(message)s", @@ -65,18 +113,24 @@ def _turn_on_json(): handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) - # Define a list of the loggers to update - loggers = [verbose_router_logger, verbose_proxy_logger, verbose_logger] + # Define all loggers to update, including root logger + loggers = [logging.getLogger()] + [ + verbose_router_logger, + verbose_proxy_logger, + verbose_logger, + ] # Iterate through each logger and update its handlers for logger in loggers: # Remove all existing handlers for h in logger.handlers[:]: logger.removeHandler(h) - # Add the new handler logger.addHandler(handler) + # Set up exception handlers + _setup_json_exception_handlers(JsonFormatter()) + def _turn_on_debug(): verbose_logger.setLevel(level=logging.DEBUG) # set package log to debug diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index e57c5f3980..72edeb55ca 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -20,6 +20,7 @@ model_list: litellm_settings: num_retries: 0 callbacks: ["prometheus"] + json_logs: true router_settings: routing_strategy: usage-based-routing-v2 # 👈 KEY CHANGE From c53d172b062bc1cbbb5b4097b167d925fb73a19a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 12:57:00 -0700 Subject: [PATCH 155/244] rename pod lock manager --- litellm/proxy/db/db_spend_update_writer.py | 8 +- ..._leader_manager.py => pod_lock_manager.py} | 0 .../litellm/proxy/db/test_pod_lock_manager.py | 127 ++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) rename litellm/proxy/db/{pod_leader_manager.py => pod_lock_manager.py} (100%) create mode 100644 tests/litellm/proxy/db/test_pod_lock_manager.py diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index eee598f79a..9047bd4c38 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -22,7 +22,7 @@ from litellm.proxy._types import ( LiteLLM_UserTable, SpendLogsPayload, ) -from litellm.proxy.db.pod_leader_manager import PodLockManager +from litellm.proxy.db.pod_lock_manager import PodLockManager from litellm.proxy.db.redis_update_buffer import ( DBSpendUpdateTransactions, RedisUpdateBuffer, @@ -49,7 +49,7 @@ class DBSpendUpdateWriter: ): self.redis_cache = redis_cache self.redis_update_buffer = RedisUpdateBuffer(redis_cache=self.redis_cache) - self.pod_leader_manager = PodLockManager(cronjob_id=DB_SPEND_UPDATE_JOB_NAME) + self.pod_lock_manager = PodLockManager(cronjob_id=DB_SPEND_UPDATE_JOB_NAME) @staticmethod async def update_database( @@ -405,7 +405,7 @@ class DBSpendUpdateWriter: ) # Only commit from redis to db if this pod is the leader - if await self.pod_leader_manager.acquire_lock(): + if await self.pod_lock_manager.acquire_lock(): verbose_proxy_logger.debug("acquired lock for spend updates") try: @@ -422,7 +422,7 @@ class DBSpendUpdateWriter: except Exception as e: verbose_proxy_logger.error(f"Error committing spend updates: {e}") finally: - await self.pod_leader_manager.release_lock() + await self.pod_lock_manager.release_lock() else: db_spend_update_transactions = DBSpendUpdateTransactions( user_list_transactions=prisma_client.user_list_transactions, diff --git a/litellm/proxy/db/pod_leader_manager.py b/litellm/proxy/db/pod_lock_manager.py similarity index 100% rename from litellm/proxy/db/pod_leader_manager.py rename to litellm/proxy/db/pod_lock_manager.py diff --git a/tests/litellm/proxy/db/test_pod_lock_manager.py b/tests/litellm/proxy/db/test_pod_lock_manager.py new file mode 100644 index 0000000000..8894ea6716 --- /dev/null +++ b/tests/litellm/proxy/db/test_pod_lock_manager.py @@ -0,0 +1,127 @@ +import json +import os +import sys +from datetime import datetime, timedelta, timezone +from unittest.mock import AsyncMock, MagicMock + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm.constants import DEFAULT_CRON_JOB_LOCK_TTL_SECONDS +from litellm.proxy.db.pod_lock_manager import PodLockManager + + +# Mock Prisma client class +class MockPrismaClient: + def __init__(self): + self.db = MagicMock() + self.db.litellm_cronjob = AsyncMock() + + +@pytest.fixture +def mock_prisma(monkeypatch): + mock_client = MockPrismaClient() + + # Mock the prisma_client import in proxy_server + def mock_get_prisma(): + return mock_client + + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_client) + return mock_client + + +@pytest.fixture +def pod_lock_manager(): + return PodLockManager(cronjob_id="test_job") + + +@pytest.mark.asyncio +async def test_acquire_lock_success(pod_lock_manager, mock_prisma): + # Mock successful lock acquisition + mock_response = AsyncMock() + mock_response.status = "ACTIVE" + mock_response.pod_id = pod_lock_manager.pod_id + mock_prisma.db.litellm_cronjob.upsert.return_value = mock_response + + result = await pod_lock_manager.acquire_lock() + assert result == True + + # Verify upsert was called with correct parameters + mock_prisma.db.litellm_cronjob.upsert.assert_called_once() + call_args = mock_prisma.db.litellm_cronjob.upsert.call_args[1] + assert call_args["where"]["cronjob_id"] == "test_job" + assert "create" in call_args["data"] + assert "update" in call_args["data"] + + +@pytest.mark.asyncio +async def test_acquire_lock_failure(pod_lock_manager, mock_prisma): + """ + Test that the lock is not acquired if the lock is held by a different pod + """ + # Mock failed lock acquisition (different pod holds the lock) + mock_response = AsyncMock() + mock_response.status = "ACTIVE" + mock_response.pod_id = "different_pod_id" + mock_prisma.db.litellm_cronjob.upsert.return_value = mock_response + + result = await pod_lock_manager.acquire_lock() + assert result == False + + +@pytest.mark.asyncio +async def test_renew_lock(pod_lock_manager, mock_prisma): + # Mock successful lock renewal + mock_prisma.db.litellm_cronjob.update.return_value = AsyncMock() + + await pod_lock_manager.renew_lock() + + # Verify update was called with correct parameters + mock_prisma.db.litellm_cronjob.update.assert_called_once() + call_args = mock_prisma.db.litellm_cronjob.update.call_args[1] + assert call_args["where"]["cronjob_id"] == "test_job" + assert call_args["where"]["pod_id"] == pod_lock_manager.pod_id + assert "ttl" in call_args["data"] + assert "last_updated" in call_args["data"] + + +@pytest.mark.asyncio +async def test_release_lock(pod_lock_manager, mock_prisma): + # Mock successful lock release + mock_prisma.db.litellm_cronjob.update.return_value = AsyncMock() + + await pod_lock_manager.release_lock() + + # Verify update was called with correct parameters + mock_prisma.db.litellm_cronjob.update.assert_called_once() + call_args = mock_prisma.db.litellm_cronjob.update.call_args[1] + assert call_args["where"]["cronjob_id"] == "test_job" + assert call_args["where"]["pod_id"] == pod_lock_manager.pod_id + assert call_args["data"]["status"] == "INACTIVE" + + +@pytest.mark.asyncio +async def test_prisma_client_none(pod_lock_manager, monkeypatch): + # Mock prisma_client as None + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) + + # Test all methods with None client + assert await pod_lock_manager.acquire_lock() == False + assert await pod_lock_manager.renew_lock() == False + assert await pod_lock_manager.release_lock() == False + + +@pytest.mark.asyncio +async def test_database_error_handling(pod_lock_manager, mock_prisma): + # Mock database errors + mock_prisma.db.litellm_cronjob.upsert.side_effect = Exception("Database error") + mock_prisma.db.litellm_cronjob.update.side_effect = Exception("Database error") + + # Test error handling in all methods + assert await pod_lock_manager.acquire_lock() == False + await pod_lock_manager.renew_lock() # Should not raise exception + await pod_lock_manager.release_lock() # Should not raise exception From 021eedaf693ff34bfe8d824bce48f4f76bbbdee6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 12:59:16 -0700 Subject: [PATCH 156/244] test pod lock manager --- tests/litellm/proxy/db/test_pod_lock_manager.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/litellm/proxy/db/test_pod_lock_manager.py b/tests/litellm/proxy/db/test_pod_lock_manager.py index 8894ea6716..e685172c1e 100644 --- a/tests/litellm/proxy/db/test_pod_lock_manager.py +++ b/tests/litellm/proxy/db/test_pod_lock_manager.py @@ -41,7 +41,9 @@ def pod_lock_manager(): @pytest.mark.asyncio async def test_acquire_lock_success(pod_lock_manager, mock_prisma): - # Mock successful lock acquisition + """ + Test that the lock is acquired successfully if the DB response is successful + """ mock_response = AsyncMock() mock_response.status = "ACTIVE" mock_response.pod_id = pod_lock_manager.pod_id @@ -75,7 +77,9 @@ async def test_acquire_lock_failure(pod_lock_manager, mock_prisma): @pytest.mark.asyncio async def test_renew_lock(pod_lock_manager, mock_prisma): - # Mock successful lock renewal + """ + Test that the renew lock calls the DB update method with the correct parameters + """ mock_prisma.db.litellm_cronjob.update.return_value = AsyncMock() await pod_lock_manager.renew_lock() @@ -91,7 +95,11 @@ async def test_renew_lock(pod_lock_manager, mock_prisma): @pytest.mark.asyncio async def test_release_lock(pod_lock_manager, mock_prisma): - # Mock successful lock release + """ + Test that the release lock calls the DB update method with the correct parameters + + specifically, the status should be set to INACTIVE + """ mock_prisma.db.litellm_cronjob.update.return_value = AsyncMock() await pod_lock_manager.release_lock() From 1f2bbda11d5a664879921c047bc718e11d92503d Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Fri, 28 Mar 2025 13:11:19 -0700 Subject: [PATCH 157/244] Add recursion depth to convert_anyof_null_to_nullable, constants.py. Fix recursive_detector.py raise error state --- litellm/constants.py | 1 + litellm/litellm_core_utils/safe_json_dumps.py | 3 +- .../sensitive_data_masker.py | 3 +- litellm/llms/vertex_ai/common_utils.py | 11 +++++-- litellm/proxy/proxy_server.py | 3 +- .../code_coverage_tests/recursive_detector.py | 33 ++++++++++++------- .../vertex_ai/test_vertex_ai_common_utils.py | 25 ++++++++++++++ 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index da66f897c9..d45994f400 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -4,6 +4,7 @@ ROUTER_MAX_FALLBACKS = 5 DEFAULT_BATCH_SIZE = 512 DEFAULT_FLUSH_INTERVAL_SECONDS = 5 DEFAULT_MAX_RETRIES = 2 +DEFAULT_MAX_RECURSE_DEPTH = 10 DEFAULT_FAILURE_THRESHOLD_PERCENT = ( 0.5 # default cooldown a deployment if 50% of requests fail in a given minute ) diff --git a/litellm/litellm_core_utils/safe_json_dumps.py b/litellm/litellm_core_utils/safe_json_dumps.py index 990c0ed561..7ad0038ecb 100644 --- a/litellm/litellm_core_utils/safe_json_dumps.py +++ b/litellm/litellm_core_utils/safe_json_dumps.py @@ -1,8 +1,9 @@ import json from typing import Any, Union +from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH -def safe_dumps(data: Any, max_depth: int = 10) -> str: +def safe_dumps(data: Any, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH) -> str: """ Recursively serialize data while detecting circular references. If a circular reference is detected then a marker string is returned. diff --git a/litellm/litellm_core_utils/sensitive_data_masker.py b/litellm/litellm_core_utils/sensitive_data_masker.py index a1df115ff0..7800e5304f 100644 --- a/litellm/litellm_core_utils/sensitive_data_masker.py +++ b/litellm/litellm_core_utils/sensitive_data_masker.py @@ -1,4 +1,5 @@ from typing import Any, Dict, Optional, Set +from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH class SensitiveDataMasker: @@ -39,7 +40,7 @@ class SensitiveDataMasker: return result def mask_dict( - self, data: Dict[str, Any], depth: int = 0, max_depth: int = 10 + self, data: Dict[str, Any], depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH ) -> Dict[str, Any]: if depth >= max_depth: return data diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index db2960617c..337445777a 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -5,6 +5,7 @@ import httpx import litellm from litellm import supports_response_schema, supports_system_messages, verbose_logger +from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.types.llms.vertex_ai import PartType @@ -228,7 +229,11 @@ def unpack_defs(schema, defs): continue -def convert_anyof_null_to_nullable(schema): +def convert_anyof_null_to_nullable(schema, depth=0): + if depth > DEFAULT_MAX_RECURSE_DEPTH: + raise ValueError( + f"Max depth of {DEFAULT_MAX_RECURSE_DEPTH} exceeded while processing schema. Please check the schema for excessive nesting." + ) """ Converts null objects within anyOf by removing them and adding nullable to all remaining objects """ anyof = schema.get("anyOf", None) if anyof is not None: @@ -256,11 +261,11 @@ def convert_anyof_null_to_nullable(schema): properties = schema.get("properties", None) if properties is not None: for name, value in properties.items(): - convert_anyof_null_to_nullable(value) + convert_anyof_null_to_nullable(value, depth=depth + 1) items = schema.get("items", None) if items is not None: - convert_anyof_null_to_nullable(items) + convert_anyof_null_to_nullable(items, depth=depth + 1) def add_object_type(schema): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index df295c3697..16f28e5bdc 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -29,6 +29,7 @@ from litellm.types.utils import ( ModelResponseStream, TextCompletionResponse, ) +from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH if TYPE_CHECKING: from opentelemetry.trace import Span as _Span @@ -1522,7 +1523,7 @@ class ProxyConfig: yaml.dump(new_config, config_file, default_flow_style=False) def _check_for_os_environ_vars( - self, config: dict, depth: int = 0, max_depth: int = 10 + self, config: dict, depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH ) -> dict: """ Check for os.environ/ variables in the config and replace them with the actual values. diff --git a/tests/code_coverage_tests/recursive_detector.py b/tests/code_coverage_tests/recursive_detector.py index 3e9c597b94..b748d1a517 100644 --- a/tests/code_coverage_tests/recursive_detector.py +++ b/tests/code_coverage_tests/recursive_detector.py @@ -9,7 +9,7 @@ IGNORE_FUNCTIONS = [ "_check_for_os_environ_vars", "clean_message", "unpack_defs", - "convert_to_nullable", + "convert_anyof_null_to_nullable", # has a set max depth "add_object_type", "strip_field", "_transform_prompt", @@ -76,15 +76,26 @@ def find_recursive_functions_in_directory(directory): ignored_recursive_functions[file_path] = ignored return recursive_functions, ignored_recursive_functions +if __name__ == "__main__": + # Example usage + # raise exception if any recursive functions are found, except for the ignored ones + # this is used in the CI/CD pipeline to prevent recursive functions from being merged -# Example usage -directory_path = "./litellm" -recursive_functions, ignored_recursive_functions = ( - find_recursive_functions_in_directory(directory_path) -) -print("ALL RECURSIVE FUNCTIONS: ", recursive_functions) -print("IGNORED RECURSIVE FUNCTIONS: ", ignored_recursive_functions) -if len(recursive_functions) > 0: - raise Exception( - f"🚨 Recursive functions found in {file}: {functions}. THIS IS REALLY BAD, it has caused CPU Usage spikes in the past. Only keep this if it's ABSOLUTELY necessary." + directory_path = "./litellm" + recursive_functions, ignored_recursive_functions = ( + find_recursive_functions_in_directory(directory_path) ) + print("UNIGNORED RECURSIVE FUNCTIONS: ", recursive_functions) + print("IGNORED RECURSIVE FUNCTIONS: ", ignored_recursive_functions) + + if len(recursive_functions) > 0: + # raise exception if any recursive functions are found + for file, functions in recursive_functions.items(): + print( + f"🚨 Unignored recursive functions found in {file}: {functions}. THIS IS REALLY BAD, it has caused CPU Usage spikes in the past. Only keep this if it's ABSOLUTELY necessary." + ) + file, functions = list(recursive_functions.items())[0] + raise Exception( + f"🚨 Unignored recursive functions found include {file}: {functions}. THIS IS REALLY BAD, it has caused CPU Usage spikes in the past. Only keep this if it's ABSOLUTELY necessary." + ) + diff --git a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index 0dc4a533fe..9412cc6d81 100644 --- a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -1,6 +1,7 @@ import os import sys from unittest.mock import MagicMock, call, patch +from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH import pytest @@ -112,6 +113,30 @@ def test_nested_anyof_conversion(): } assert schema == expected +def test_anyof_with_excessive_nesting(): + """Test conversion with excessive nesting > max levels +1 deep.""" + # generate a schema with excessive nesting + schema = {"type": "object", "properties": {}} + current = schema + for _ in range(DEFAULT_MAX_RECURSE_DEPTH + 1): + current["properties"] = { + "nested": { + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ], + "properties": {} + } + } + current = current["properties"]["nested"] + + + # running the conversion will raise an error + with pytest.raises(ValueError, match=f"Max depth of {DEFAULT_MAX_RECURSE_DEPTH} exceeded while processing schema. Please check the schema for excessive nesting."): + convert_anyof_null_to_nullable(schema) + + + @pytest.mark.asyncio async def test_get_supports_system_message(): """Test get_supports_system_message with different models""" From 7c85054bade2b2a7d9ef348444ab2c9e32f9e1d5 Mon Sep 17 00:00:00 2001 From: Dbzman Date: Fri, 28 Mar 2025 21:20:12 +0100 Subject: [PATCH 158/244] fix: wrong indentation for ttlSecondsAfterFinished (#9611) --- deploy/charts/litellm-helm/templates/migrations-job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/deploy/charts/litellm-helm/templates/migrations-job.yaml index e994c45548..1c4b6817fa 100644 --- a/deploy/charts/litellm-helm/templates/migrations-job.yaml +++ b/deploy/charts/litellm-helm/templates/migrations-job.yaml @@ -65,6 +65,6 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} - ttlSecondsAfterFinished: {{ .Values.migrationJob.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.migrationJob.ttlSecondsAfterFinished }} backoffLimit: {{ .Values.migrationJob.backoffLimit }} {{- end }} From 1eaf847f8a7801fe93a0b2fef3b5f03b5c0c5326 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 13:31:45 -0700 Subject: [PATCH 159/244] test pod lock manager --- litellm/proxy/db/pod_lock_manager.py | 50 ++- .../test_e2e_pod_lock_manager.py | 354 ++++++++++++++++++ 2 files changed, 384 insertions(+), 20 deletions(-) create mode 100644 tests/proxy_unit_tests/test_e2e_pod_lock_manager.py diff --git a/litellm/proxy/db/pod_lock_manager.py b/litellm/proxy/db/pod_lock_manager.py index 9b739b8695..84c92c9daa 100644 --- a/litellm/proxy/db/pod_lock_manager.py +++ b/litellm/proxy/db/pod_lock_manager.py @@ -25,47 +25,57 @@ class PodLockManager: async def acquire_lock(self) -> bool: """ - Attempt to acquire the lock for a specific cron job. + Attempt to acquire the lock for a specific cron job using database locking. """ from litellm.proxy.proxy_server import prisma_client - verbose_proxy_logger.debug("acquiring lock for cronjob_id=%s", self.cronjob_id) + verbose_proxy_logger.debug( + "Pod %s acquiring lock for cronjob_id=%s", self.pod_id, self.cronjob_id + ) if not prisma_client: verbose_proxy_logger.debug("prisma is None, returning False") return False + try: current_time = datetime.now(timezone.utc) - # Lease expiry time ttl_expiry = current_time + timedelta( seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS ) - # Attempt to acquire the lock by upserting the record in the `cronjob_locks` table - cronjob_lock = await prisma_client.db.litellm_cronjob.upsert( + # Use Prisma's findUnique with FOR UPDATE lock to prevent race conditions + lock_record = await prisma_client.db.litellm_cronjob.find_unique( where={"cronjob_id": self.cronjob_id}, - data={ - "create": { - "cronjob_id": self.cronjob_id, + ) + + if lock_record: + # If record exists, only update if it's inactive or expired + if lock_record.status == "ACTIVE" and lock_record.ttl > current_time: + return lock_record.pod_id == self.pod_id + + # Update existing record + updated_lock = await prisma_client.db.litellm_cronjob.update( + where={"cronjob_id": self.cronjob_id}, + data={ "pod_id": self.pod_id, "status": "ACTIVE", "last_updated": current_time, "ttl": ttl_expiry, }, - "update": { + ) + else: + # Create new record if none exists + updated_lock = await prisma_client.db.litellm_cronjob.create( + data={ + "cronjob_id": self.cronjob_id, + "pod_id": self.pod_id, "status": "ACTIVE", "last_updated": current_time, "ttl": ttl_expiry, - }, - }, - ) - verbose_proxy_logger.debug("cronjob_lock=%s", cronjob_lock) - - if cronjob_lock.status == "ACTIVE" and cronjob_lock.pod_id == self.pod_id: - verbose_proxy_logger.debug( - f"Pod {self.pod_id} has acquired the lock for {self.cronjob_id}." + } ) - return True # Lock successfully acquired - return False + + return updated_lock.pod_id == self.pod_id + except Exception as e: verbose_proxy_logger.error( f"Error acquiring the lock for {self.cronjob_id}: {e}" @@ -112,7 +122,7 @@ class PodLockManager: return False try: verbose_proxy_logger.debug( - "releasing lock for cronjob_id=%s", self.cronjob_id + "Pod %s releasing lock for cronjob_id=%s", self.pod_id, self.cronjob_id ) await prisma_client.db.litellm_cronjob.update( where={"cronjob_id": self.cronjob_id, "pod_id": self.pod_id}, diff --git a/tests/proxy_unit_tests/test_e2e_pod_lock_manager.py b/tests/proxy_unit_tests/test_e2e_pod_lock_manager.py new file mode 100644 index 0000000000..7d36bb4791 --- /dev/null +++ b/tests/proxy_unit_tests/test_e2e_pod_lock_manager.py @@ -0,0 +1,354 @@ +import os +import sys +import traceback +import uuid +from datetime import datetime, timezone, timedelta + +from dotenv import load_dotenv +from fastapi import Request +from fastapi.routing import APIRoute +import httpx + +load_dotenv() +import io +import os +import time + +# this file is to test litellm/proxy + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +import asyncio +import logging + +import pytest +from litellm.proxy.db.pod_lock_manager import PodLockManager +import litellm +from litellm._logging import verbose_proxy_logger +from litellm.proxy.management_endpoints.internal_user_endpoints import ( + new_user, + user_info, + user_update, +) +from litellm.proxy.auth.auth_checks import get_key_object +from litellm.proxy.management_endpoints.key_management_endpoints import ( + delete_key_fn, + generate_key_fn, + generate_key_helper_fn, + info_key_fn, + list_keys, + regenerate_key_fn, + update_key_fn, +) +from litellm.proxy.management_endpoints.team_endpoints import ( + new_team, + team_info, + update_team, +) +from litellm.proxy.proxy_server import ( + LitellmUserRoles, + audio_transcriptions, + chat_completion, + completion, + embeddings, + image_generation, + model_list, + moderations, + user_api_key_auth, +) +from litellm.proxy.management_endpoints.customer_endpoints import ( + new_end_user, +) +from litellm.proxy.spend_tracking.spend_management_endpoints import ( + global_spend, + spend_key_fn, + spend_user_fn, + view_spend_logs, +) +from litellm.proxy.utils import PrismaClient, ProxyLogging, hash_token, update_spend + +verbose_proxy_logger.setLevel(level=logging.DEBUG) + +from starlette.datastructures import URL + +from litellm.caching.caching import DualCache +from litellm.proxy._types import ( + DynamoDBArgs, + GenerateKeyRequest, + KeyRequest, + LiteLLM_UpperboundKeyGenerateParams, + NewCustomerRequest, + NewTeamRequest, + NewUserRequest, + ProxyErrorTypes, + ProxyException, + UpdateKeyRequest, + UpdateTeamRequest, + UpdateUserRequest, + UserAPIKeyAuth, +) + +proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache()) + + +request_data = { + "model": "azure-gpt-3.5", + "messages": [ + {"role": "user", "content": "this is my new test. respond in 50 lines"} + ], +} + + +@pytest.fixture +def prisma_client(): + from litellm.proxy.proxy_cli import append_query_params + + ### add connection pool + pool timeout args + params = {"connection_limit": 100, "pool_timeout": 60} + database_url = os.getenv("DATABASE_URL") + modified_url = append_query_params(database_url, params) + os.environ["DATABASE_URL"] = modified_url + + # Assuming PrismaClient is a class that needs to be instantiated + prisma_client = PrismaClient( + database_url=os.environ["DATABASE_URL"], proxy_logging_obj=proxy_logging_obj + ) + + # Reset litellm.proxy.proxy_server.prisma_client to None + litellm.proxy.proxy_server.litellm_proxy_budget_name = ( + f"litellm-proxy-budget-{time.time()}" + ) + litellm.proxy.proxy_server.user_custom_key_generate = None + + return prisma_client + + +async def setup_db_connection(prisma_client): + setattr(litellm.proxy.proxy_server, "prisma_client", prisma_client) + setattr(litellm.proxy.proxy_server, "master_key", "sk-1234") + await litellm.proxy.proxy_server.prisma_client.connect() + + +@pytest.mark.asyncio +async def test_pod_lock_acquisition_when_no_active_lock(prisma_client): + """Test if a pod can acquire a lock when no lock is active""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + lock_manager = PodLockManager(cronjob_id=cronjob_id) + + # Attempt to acquire lock + result = await lock_manager.acquire_lock() + + assert result == True, "Pod should be able to acquire lock when no lock exists" + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id == lock_manager.pod_id + + +@pytest.mark.asyncio +async def test_pod_lock_acquisition_after_completion(prisma_client): + """Test if a new pod can acquire lock after previous pod completes""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + # First pod acquires and releases lock + first_lock_manager = PodLockManager(cronjob_id=cronjob_id) + await first_lock_manager.acquire_lock() + await first_lock_manager.release_lock() + + # Second pod attempts to acquire lock + second_lock_manager = PodLockManager(cronjob_id=cronjob_id) + result = await second_lock_manager.acquire_lock() + + assert result == True, "Second pod should acquire lock after first pod releases it" + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id == second_lock_manager.pod_id + + +@pytest.mark.asyncio +async def test_pod_lock_acquisition_after_expiry(prisma_client): + """Test if a new pod can acquire lock after previous pod's lock expires""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + # First pod acquires lock + first_lock_manager = PodLockManager(cronjob_id=cronjob_id) + await first_lock_manager.acquire_lock() + + # release the lock from the first pod + await first_lock_manager.release_lock() + + # Second pod attempts to acquire lock + second_lock_manager = PodLockManager(cronjob_id=cronjob_id) + result = await second_lock_manager.acquire_lock() + + assert ( + result == True + ), "Second pod should acquire lock after first pod's lock expires" + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id == second_lock_manager.pod_id + + +@pytest.mark.asyncio +async def test_pod_lock_release(prisma_client): + """Test if a pod can successfully release its lock""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + lock_manager = PodLockManager(cronjob_id=cronjob_id) + + # Acquire and then release lock + await lock_manager.acquire_lock() + await lock_manager.release_lock() + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "INACTIVE" + + +@pytest.mark.asyncio +async def test_concurrent_lock_acquisition(prisma_client): + """Test that only one pod can acquire the lock when multiple pods try simultaneously""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + # Create multiple lock managers simulating different pods + lock_manager1 = PodLockManager(cronjob_id=cronjob_id) + lock_manager2 = PodLockManager(cronjob_id=cronjob_id) + lock_manager3 = PodLockManager(cronjob_id=cronjob_id) + + # Try to acquire locks concurrently + results = await asyncio.gather( + lock_manager1.acquire_lock(), + lock_manager2.acquire_lock(), + lock_manager3.acquire_lock(), + ) + + # Only one should succeed + print("all results=", results) + assert sum(results) == 1, "Only one pod should acquire the lock" + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id in [ + lock_manager1.pod_id, + lock_manager2.pod_id, + lock_manager3.pod_id, + ] + + +@pytest.mark.asyncio +async def test_lock_renewal(prisma_client): + """Test that a pod can successfully renew its lock""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + lock_manager = PodLockManager(cronjob_id=cronjob_id) + + # Acquire initial lock + await lock_manager.acquire_lock() + + # Get initial TTL + initial_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + initial_ttl = initial_record.ttl + + # Wait a short time + await asyncio.sleep(1) + + # Renew the lock + await lock_manager.renew_lock() + + # Get updated record + renewed_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + + assert renewed_record.ttl > initial_ttl, "Lock TTL should be extended after renewal" + assert renewed_record.status == "ACTIVE" + assert renewed_record.pod_id == lock_manager.pod_id + + +@pytest.mark.asyncio +async def test_lock_acquisition_with_expired_ttl(prisma_client): + """Test that a pod can acquire a lock when existing lock has expired TTL""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + first_lock_manager = PodLockManager(cronjob_id=cronjob_id) + + # First pod acquires lock + await first_lock_manager.acquire_lock() + + # Manually expire the TTL + expired_time = datetime.now(timezone.utc) - timedelta(seconds=10) + await prisma_client.db.litellm_cronjob.update( + where={"cronjob_id": cronjob_id}, data={"ttl": expired_time} + ) + + # Second pod tries to acquire without explicit release + second_lock_manager = PodLockManager(cronjob_id=cronjob_id) + result = await second_lock_manager.acquire_lock() + + assert result == True, "Should acquire lock when existing lock has expired TTL" + + # Verify in database + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id == second_lock_manager.pod_id + + +@pytest.mark.asyncio +async def test_release_expired_lock(prisma_client): + """Test that a pod cannot release a lock that has been taken over by another pod""" + await setup_db_connection(prisma_client) + + cronjob_id = str(uuid.uuid4()) + first_lock_manager = PodLockManager(cronjob_id=cronjob_id) + + # First pod acquires lock + await first_lock_manager.acquire_lock() + + # Manually expire the TTL + expired_time = datetime.now(timezone.utc) - timedelta(seconds=10) + await prisma_client.db.litellm_cronjob.update( + where={"cronjob_id": cronjob_id}, data={"ttl": expired_time} + ) + + # Second pod acquires the lock + second_lock_manager = PodLockManager(cronjob_id=cronjob_id) + await second_lock_manager.acquire_lock() + + # First pod attempts to release its lock + await first_lock_manager.release_lock() + + # Verify that second pod's lock is still active + lock_record = await prisma_client.db.litellm_cronjob.find_first( + where={"cronjob_id": cronjob_id} + ) + assert lock_record.status == "ACTIVE" + assert lock_record.pod_id == second_lock_manager.pod_id From 28a9edb547d456ae221ba4472445ac3ac07f8034 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Mar 2025 13:31:54 -0700 Subject: [PATCH 160/244] test(test_caching_handler.py): move to in-memory cache - prevent redis flakiness from impacting ci/cd --- tests/local_testing/test_caching_handler.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/local_testing/test_caching_handler.py b/tests/local_testing/test_caching_handler.py index c2466000c4..c0e3b8d306 100644 --- a/tests/local_testing/test_caching_handler.py +++ b/tests/local_testing/test_caching_handler.py @@ -40,12 +40,7 @@ import logging def setup_cache(): # Set up the cache - cache = Cache( - type=LiteLLMCacheType.REDIS, - host=os.environ["REDIS_HOST"], - port=os.environ["REDIS_PORT"], - password=os.environ["REDIS_PASSWORD"], - ) + cache = Cache(type=LiteLLMCacheType.LOCAL) litellm.cache = cache return cache From 193052ed7054292e7d01794cf3b3fb76bbb83503 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 15:05:17 -0700 Subject: [PATCH 161/244] test pod lock manager --- .../litellm/proxy/db/test_pod_lock_manager.py | 215 ++++++++++++++++-- 1 file changed, 200 insertions(+), 15 deletions(-) diff --git a/tests/litellm/proxy/db/test_pod_lock_manager.py b/tests/litellm/proxy/db/test_pod_lock_manager.py index e685172c1e..bce7b66409 100644 --- a/tests/litellm/proxy/db/test_pod_lock_manager.py +++ b/tests/litellm/proxy/db/test_pod_lock_manager.py @@ -42,38 +42,75 @@ def pod_lock_manager(): @pytest.mark.asyncio async def test_acquire_lock_success(pod_lock_manager, mock_prisma): """ - Test that the lock is acquired successfully if the DB response is successful + Test that the lock is acquired successfully when no existing lock exists """ + # Mock find_unique to return None (no existing lock) + mock_prisma.db.litellm_cronjob.find_unique.return_value = None + + # Mock successful creation of new lock mock_response = AsyncMock() mock_response.status = "ACTIVE" mock_response.pod_id = pod_lock_manager.pod_id - mock_prisma.db.litellm_cronjob.upsert.return_value = mock_response + mock_prisma.db.litellm_cronjob.create.return_value = mock_response result = await pod_lock_manager.acquire_lock() assert result == True - # Verify upsert was called with correct parameters - mock_prisma.db.litellm_cronjob.upsert.assert_called_once() - call_args = mock_prisma.db.litellm_cronjob.upsert.call_args[1] - assert call_args["where"]["cronjob_id"] == "test_job" - assert "create" in call_args["data"] - assert "update" in call_args["data"] + # Verify find_unique was called + mock_prisma.db.litellm_cronjob.find_unique.assert_called_once() + # Verify create was called with correct parameters + mock_prisma.db.litellm_cronjob.create.assert_called_once() + call_args = mock_prisma.db.litellm_cronjob.create.call_args[1] + assert call_args["data"]["cronjob_id"] == "test_job" + assert call_args["data"]["pod_id"] == pod_lock_manager.pod_id + assert call_args["data"]["status"] == "ACTIVE" @pytest.mark.asyncio -async def test_acquire_lock_failure(pod_lock_manager, mock_prisma): +async def test_acquire_lock_existing_active(pod_lock_manager, mock_prisma): """ - Test that the lock is not acquired if the lock is held by a different pod + Test that the lock is not acquired if there's an active lock by different pod """ - # Mock failed lock acquisition (different pod holds the lock) - mock_response = AsyncMock() - mock_response.status = "ACTIVE" - mock_response.pod_id = "different_pod_id" - mock_prisma.db.litellm_cronjob.upsert.return_value = mock_response + # Mock existing active lock + mock_existing = AsyncMock() + mock_existing.status = "ACTIVE" + mock_existing.pod_id = "different_pod_id" + mock_existing.ttl = datetime.now(timezone.utc) + timedelta(seconds=30) # Future TTL + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing result = await pod_lock_manager.acquire_lock() assert result == False + # Verify find_unique was called but update/create were not + mock_prisma.db.litellm_cronjob.find_unique.assert_called_once() + mock_prisma.db.litellm_cronjob.update.assert_not_called() + mock_prisma.db.litellm_cronjob.create.assert_not_called() + + +@pytest.mark.asyncio +async def test_acquire_lock_expired(pod_lock_manager, mock_prisma): + """ + Test that the lock can be acquired if existing lock is expired + """ + # Mock existing expired lock + mock_existing = AsyncMock() + mock_existing.status = "ACTIVE" + mock_existing.pod_id = "different_pod_id" + mock_existing.ttl = datetime.now(timezone.utc) - timedelta(seconds=30) # Past TTL + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing + + # Mock successful update + mock_updated = AsyncMock() + mock_updated.pod_id = pod_lock_manager.pod_id + mock_prisma.db.litellm_cronjob.update.return_value = mock_updated + + result = await pod_lock_manager.acquire_lock() + assert result == True + + # Verify both find_unique and update were called + mock_prisma.db.litellm_cronjob.find_unique.assert_called_once() + mock_prisma.db.litellm_cronjob.update.assert_called_once() + @pytest.mark.asyncio async def test_renew_lock(pod_lock_manager, mock_prisma): @@ -133,3 +170,151 @@ async def test_database_error_handling(pod_lock_manager, mock_prisma): assert await pod_lock_manager.acquire_lock() == False await pod_lock_manager.renew_lock() # Should not raise exception await pod_lock_manager.release_lock() # Should not raise exception + + +@pytest.mark.asyncio +async def test_acquire_lock_inactive_status(pod_lock_manager, mock_prisma): + """ + Test that the lock can be acquired if existing lock is INACTIVE + """ + # Mock existing inactive lock + mock_existing = AsyncMock() + mock_existing.status = "INACTIVE" + mock_existing.pod_id = "different_pod_id" + mock_existing.ttl = datetime.now(timezone.utc) + timedelta(seconds=30) + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing + + # Mock successful update + mock_updated = AsyncMock() + mock_updated.pod_id = pod_lock_manager.pod_id + mock_prisma.db.litellm_cronjob.update.return_value = mock_updated + + result = await pod_lock_manager.acquire_lock() + assert result == True + + mock_prisma.db.litellm_cronjob.update.assert_called_once() + + +@pytest.mark.asyncio +async def test_acquire_lock_same_pod(pod_lock_manager, mock_prisma): + """ + Test that the lock returns True if the same pod already holds the lock + """ + # Mock existing active lock held by same pod + mock_existing = AsyncMock() + mock_existing.status = "ACTIVE" + mock_existing.pod_id = pod_lock_manager.pod_id + mock_existing.ttl = datetime.now(timezone.utc) + timedelta(seconds=30) + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing + + result = await pod_lock_manager.acquire_lock() + assert result == True + + # Verify no update was needed + mock_prisma.db.litellm_cronjob.update.assert_not_called() + mock_prisma.db.litellm_cronjob.create.assert_not_called() + + +@pytest.mark.asyncio +async def test_acquire_lock_race_condition(pod_lock_manager, mock_prisma): + """ + Test handling of potential race conditions during lock acquisition + """ + # First find_unique returns None + mock_prisma.db.litellm_cronjob.find_unique.return_value = None + + # But create raises unique constraint violation + mock_prisma.db.litellm_cronjob.create.side_effect = Exception( + "Unique constraint violation" + ) + + result = await pod_lock_manager.acquire_lock() + assert result == False + + +@pytest.mark.asyncio +async def test_ttl_calculation(pod_lock_manager, mock_prisma): + """ + Test that TTL is calculated correctly when acquiring lock + """ + mock_prisma.db.litellm_cronjob.find_unique.return_value = None + mock_prisma.db.litellm_cronjob.create.return_value = AsyncMock() + + await pod_lock_manager.acquire_lock() + + call_args = mock_prisma.db.litellm_cronjob.create.call_args[1] + ttl = call_args["data"]["ttl"] + + # Verify TTL is in the future by DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + expected_ttl = datetime.now(timezone.utc) + timedelta( + seconds=DEFAULT_CRON_JOB_LOCK_TTL_SECONDS + ) + assert abs((ttl - expected_ttl).total_seconds()) < 1 # Allow 1 second difference + + +@pytest.mark.asyncio +async def test_concurrent_lock_acquisition_simulation(mock_prisma): + """ + Simulate multiple pods trying to acquire the lock simultaneously + """ + pod1 = PodLockManager(cronjob_id="test_job") + pod2 = PodLockManager(cronjob_id="test_job") + pod3 = PodLockManager(cronjob_id="test_job") + + # Simulate first pod getting the lock + mock_prisma.db.litellm_cronjob.find_unique.return_value = None + mock_response = AsyncMock() + mock_response.pod_id = pod1.pod_id + mock_response.status = "ACTIVE" + mock_prisma.db.litellm_cronjob.create.return_value = mock_response + + # First pod should get the lock + result1 = await pod1.acquire_lock() + assert result1 == True + + # Simulate other pods trying to acquire same lock immediately after + mock_existing = AsyncMock() + mock_existing.status = "ACTIVE" + mock_existing.pod_id = pod1.pod_id + mock_existing.ttl = datetime.now(timezone.utc) + timedelta(seconds=30) + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing + + # Other pods should fail to acquire + result2 = await pod2.acquire_lock() + result3 = await pod3.acquire_lock() + assert result2 == False + assert result3 == False + + +@pytest.mark.asyncio +async def test_lock_takeover_race_condition(mock_prisma): + """ + Test scenario where multiple pods try to take over an expired lock + """ + pod1 = PodLockManager(cronjob_id="test_job") + pod2 = PodLockManager(cronjob_id="test_job") + + # Simulate expired lock + mock_existing = AsyncMock() + mock_existing.status = "ACTIVE" + mock_existing.pod_id = "old_pod" + mock_existing.ttl = datetime.now(timezone.utc) - timedelta(seconds=30) + mock_prisma.db.litellm_cronjob.find_unique.return_value = mock_existing + + # Simulate pod1's update succeeding + mock_update1 = AsyncMock() + mock_update1.pod_id = pod1.pod_id + mock_prisma.db.litellm_cronjob.update.return_value = mock_update1 + + # First pod should successfully take over + result1 = await pod1.acquire_lock() + assert result1 == True + + # Simulate pod2's update failing due to race condition + mock_prisma.db.litellm_cronjob.update.side_effect = Exception( + "Row was updated by another transaction" + ) + + # Second pod should fail to take over + result2 = await pod2.acquire_lock() + assert result2 == False From 3df839d8783f4c1ca2c4d22e4d018601cec18780 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 15:12:19 -0700 Subject: [PATCH 162/244] use helper methods _commit_spend_updates_to_db_without_redis_buffer --- litellm/proxy/db/db_spend_update_writer.py | 109 ++++++++++++++------- 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 9047bd4c38..17247aff74 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -400,45 +400,86 @@ class DBSpendUpdateWriter: - Regular flow of this method """ if RedisUpdateBuffer._should_commit_spend_updates_to_redis(): - await self.redis_update_buffer.store_in_memory_spend_updates_in_redis( - prisma_client=prisma_client, - ) - - # Only commit from redis to db if this pod is the leader - if await self.pod_lock_manager.acquire_lock(): - verbose_proxy_logger.debug("acquired lock for spend updates") - - try: - db_spend_update_transactions = ( - await self.redis_update_buffer.get_all_update_transactions_from_redis() - ) - if db_spend_update_transactions is not None: - await DBSpendUpdateWriter._commit_spend_updates_to_db( - prisma_client=prisma_client, - n_retry_times=n_retry_times, - proxy_logging_obj=proxy_logging_obj, - db_spend_update_transactions=db_spend_update_transactions, - ) - except Exception as e: - verbose_proxy_logger.error(f"Error committing spend updates: {e}") - finally: - await self.pod_lock_manager.release_lock() - else: - db_spend_update_transactions = DBSpendUpdateTransactions( - user_list_transactions=prisma_client.user_list_transactions, - end_user_list_transactions=prisma_client.end_user_list_transactions, - key_list_transactions=prisma_client.key_list_transactions, - team_list_transactions=prisma_client.team_list_transactions, - team_member_list_transactions=prisma_client.team_member_list_transactions, - org_list_transactions=prisma_client.org_list_transactions, - ) - await DBSpendUpdateWriter._commit_spend_updates_to_db( + await self._commit_spend_updates_to_db_with_redis( prisma_client=prisma_client, n_retry_times=n_retry_times, proxy_logging_obj=proxy_logging_obj, - db_spend_update_transactions=db_spend_update_transactions, ) + else: + await self._commit_spend_updates_to_db_without_redis_buffer( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + ) + + async def _commit_spend_updates_to_db_with_redis( + self, + prisma_client: PrismaClient, + n_retry_times: int, + proxy_logging_obj: ProxyLogging, + ): + """ + Handler to commit spend updates to Redis and attempt to acquire lock to commit to db + + This is a v2 scalable approach to first commit spend updates to redis, then commit to db + + This minimizes DB Deadlocks since + - All pods only need to write their spend updates to redis + - Only 1 pod will commit to db at a time (based on if it can acquire the lock over writing to DB) + """ + await self.redis_update_buffer.store_in_memory_spend_updates_in_redis( + prisma_client=prisma_client, + ) + + # Only commit from redis to db if this pod is the leader + if await self.pod_lock_manager.acquire_lock(): + verbose_proxy_logger.debug("acquired lock for spend updates") + + try: + db_spend_update_transactions = ( + await self.redis_update_buffer.get_all_update_transactions_from_redis() + ) + if db_spend_update_transactions is not None: + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions=db_spend_update_transactions, + ) + except Exception as e: + verbose_proxy_logger.error(f"Error committing spend updates: {e}") + finally: + await self.pod_lock_manager.release_lock() + + async def _commit_spend_updates_to_db_without_redis_buffer( + self, + prisma_client: PrismaClient, + n_retry_times: int, + proxy_logging_obj: ProxyLogging, + ): + """ + Commits all the spend `UPDATE` transactions to the Database + + This is the regular flow of committing to db without using a redis buffer + + Note: This flow causes Deadlocks in production (1K RPS+). Use self._commit_spend_updates_to_db_with_redis() instead if you expect 1K+ RPS. + """ + db_spend_update_transactions = DBSpendUpdateTransactions( + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, + ) + await DBSpendUpdateWriter._commit_spend_updates_to_db( + prisma_client=prisma_client, + n_retry_times=n_retry_times, + proxy_logging_obj=proxy_logging_obj, + db_spend_update_transactions=db_spend_update_transactions, + ) + @staticmethod async def _commit_spend_updates_to_db( # noqa: PLR0915 prisma_client: PrismaClient, From 205db622bf660d5bcff79d8631b5b2851ee96364 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 12:32:04 -0700 Subject: [PATCH 163/244] =?UTF-8?q?fix(proxy=5Fserver.py):=20get=20master?= =?UTF-8?q?=20key=20from=20environment,=20if=20not=20set=20in=20=E2=80=A6?= =?UTF-8?q?=20(#9617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(proxy_server.py): get master key from environment, if not set in general settings or general settings not set at all * test: mark flaky test * test(test_proxy_server.py): mock prisma client * ci: add new github workflow for testing just the mock tests * fix: fix linting error * ci(conftest.py): add conftest.py to isolate proxy tests * build(pyproject.toml): add respx to dev dependencies * build(pyproject.toml): add prisma to dev dependencies * test: fix mock prompt management tests to use a mock anthropic key * ci(test-litellm.yml): parallelize mock testing make it run faster * build(pyproject.toml): add hypercorn as dev dep * build(pyproject.toml): separate proxy vs. core dev dependencies make it easier for non-proxy contributors to run tests locally - e.g. no need to install hypercorn * ci(test-litellm.yml): pin python version * test(test_rerank.py): move test - cannot be mocked, requires aws credentials for e2e testing * ci: add thank you message to ci * test: add mock env var to test * test: add autouse to tests * test: test mock env vars for e2e tests --- .github/workflows/test-litellm.yml | 35 ++ Makefile | 3 + litellm/proxy/proxy_server.py | 2 + poetry.lock | 444 +++++++++--------- pyproject.toml | 5 + tests/litellm/conftest.py | 63 +++ .../test_custom_prompt_management.py | 7 +- .../test_spend_management_endpoints.py | 7 +- tests/litellm/proxy/test_proxy_server.py | 90 ++++ tests/litellm/rerank_api/test_rerank_main.py | 51 -- tests/litellm/test_main.py | 12 +- tests/llm_translation/test_cohere.py | 1 + tests/llm_translation/test_rerank.py | 39 ++ 13 files changed, 477 insertions(+), 282 deletions(-) create mode 100644 .github/workflows/test-litellm.yml create mode 100644 tests/litellm/conftest.py delete mode 100644 tests/litellm/rerank_api/test_rerank_main.py diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml new file mode 100644 index 0000000000..09de0ff110 --- /dev/null +++ b/.github/workflows/test-litellm.yml @@ -0,0 +1,35 @@ +name: LiteLLM Tests + +on: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - name: Thank You Message + run: | + echo "### 🙏 Thank you for contributing to LiteLLM!" >> $GITHUB_STEP_SUMMARY + echo "Your PR is being tested now. We appreciate your help in making LiteLLM better!" >> $GITHUB_STEP_SUMMARY + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: | + poetry install --with dev,proxy-dev --extras proxy + poetry run pip install pytest-xdist + + - name: Run tests + run: | + poetry run pytest tests/litellm -x -vv -n 4 \ No newline at end of file diff --git a/Makefile b/Makefile index 6c231d3cc2..a06509312d 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ help: install-dev: poetry install --with dev +install-proxy-dev: + poetry install --with dev,proxy-dev + lint: install-dev poetry run pip install types-requests types-setuptools types-redis types-PyYAML cd litellm && poetry run mypy . --ignore-missing-imports diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 16f28e5bdc..7c9b60d5cb 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -463,6 +463,8 @@ async def proxy_startup_event(app: FastAPI): if premium_user is False: premium_user = _license_check.is_premium() + ## CHECK MASTER KEY IN ENVIRONMENT ## + master_key = get_secret_str("LITELLM_MASTER_KEY") ### LOAD CONFIG ### worker_config: Optional[Union[str, dict]] = get_secret("WORKER_CONFIG") # type: ignore env_config_yaml: Optional[str] = get_secret_str("CONFIG_FILE_PATH") diff --git a/poetry.lock b/poetry.lock index 9273f91acc..64e8eaa8ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,7 +6,6 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -18,7 +17,6 @@ version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, @@ -131,7 +129,6 @@ version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, @@ -146,7 +143,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -161,7 +157,6 @@ version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, @@ -184,8 +179,6 @@ version = "3.11.0" description = "In-process task scheduler with Cron-like capabilities" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"}, {file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"}, @@ -214,8 +207,6 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_full_version < \"3.11.3\" and (extra == \"extra-proxy\" or extra == \"proxy\" or python_version < \"3.11\")" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -227,7 +218,6 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, @@ -247,8 +237,6 @@ version = "1.32.0" description = "Microsoft Azure Core Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_core-1.32.0-py3-none-any.whl", hash = "sha256:eac191a0efb23bfa83fddf321b27b122b4ec847befa3091fa736a5c32c50d7b4"}, {file = "azure_core-1.32.0.tar.gz", hash = "sha256:22b3c35d6b2dae14990f6c1be2912bf23ffe50b220e708a28ab1bb92b1c730e5"}, @@ -268,8 +256,6 @@ version = "1.21.0" description = "Microsoft Azure Identity Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9"}, {file = "azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6"}, @@ -288,8 +274,6 @@ version = "4.9.0" description = "Microsoft Azure Key Vault Secrets Client Library for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "azure_keyvault_secrets-4.9.0-py3-none-any.whl", hash = "sha256:33c7e2aca2cc2092cebc8c6e96eca36a5cc30c767e16ea429c5fa21270e9fba6"}, {file = "azure_keyvault_secrets-4.9.0.tar.gz", hash = "sha256:2a03bb2ffd9a0d6c8ad1c330d9d0310113985a9de06607ece378fd72a5889fe1"}, @@ -306,8 +290,6 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = true python-versions = ">=3.7,<4.0" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -319,8 +301,6 @@ version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\" and python_version < \"3.9\"" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, @@ -349,7 +329,6 @@ version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, @@ -396,8 +375,6 @@ version = "1.34.34" description = "The AWS SDK for Python" optional = true python-versions = ">= 3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "boto3-1.34.34-py3-none-any.whl", hash = "sha256:33a8b6d9136fa7427160edb92d2e50f2035f04e9d63a2d1027349053e12626aa"}, {file = "boto3-1.34.34.tar.gz", hash = "sha256:b2f321e20966f021ec800b7f2c01287a3dd04fc5965acdfbaa9c505a24ca45d1"}, @@ -417,8 +394,6 @@ version = "1.34.162" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "botocore-1.34.162-py3-none-any.whl", hash = "sha256:2d918b02db88d27a75b48275e6fb2506e9adaaddbec1ffa6a8a0898b34e769be"}, {file = "botocore-1.34.162.tar.gz", hash = "sha256:adc23be4fb99ad31961236342b7cbf3c0bfc62532cd02852196032e8c0d682f3"}, @@ -428,8 +403,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -441,8 +416,6 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -454,7 +427,6 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -466,8 +438,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -547,7 +517,6 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -649,7 +618,6 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -664,12 +632,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "coloredlogs" @@ -677,8 +643,6 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -696,8 +660,6 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -747,7 +709,6 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -759,8 +720,6 @@ version = "2.6.1" description = "DNS toolkit" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -781,8 +740,6 @@ version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -798,8 +755,6 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -814,8 +769,6 @@ version = "0.115.12" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, @@ -836,8 +789,6 @@ version = "0.16.0" description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" optional = true python-versions = "<4.0,>=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "fastapi_sso-0.16.0-py3-none-any.whl", hash = "sha256:3a66a942474ef9756d3a9d8b945d55bd9faf99781facdb9b87a40b73d6d6b0c3"}, {file = "fastapi_sso-0.16.0.tar.gz", hash = "sha256:f3941f986347566b7d3747c710cf474a907f581bfb6697ff3bb3e44eb76b438c"}, @@ -856,7 +807,6 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -873,7 +823,6 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -890,7 +839,6 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -992,7 +940,6 @@ version = "2025.3.0" description = "File-system specification" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"}, {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"}, @@ -1032,8 +979,6 @@ version = "2.24.2" description = "Google API client core library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"}, {file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"}, @@ -1043,12 +988,12 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] proto-plus = [ {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, @@ -1069,8 +1014,6 @@ version = "2.38.0" description = "Google Authentication Library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, @@ -1095,8 +1038,6 @@ version = "2.24.2" description = "Google Cloud Kms API client library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "google_cloud_kms-2.24.2-py2.py3-none-any.whl", hash = "sha256:368209b035dfac691a467c1cf50986d8b1b26cac1166bdfbaa25d738df91ff7b"}, {file = "google_cloud_kms-2.24.2.tar.gz", hash = "sha256:e9e18bbfafd1a4035c76c03fb5ff03f4f57f596d08e1a9ede7e69ec0151b27a1"}, @@ -1115,8 +1056,6 @@ version = "1.69.2" description = "Common protobufs used in Google APIs" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, @@ -1135,8 +1074,6 @@ version = "0.14.2" description = "IAM API client library" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351"}, {file = "grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20"}, @@ -1153,8 +1090,6 @@ version = "1.70.0" description = "HTTP/2-based RPC framework" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, @@ -1216,14 +1151,75 @@ files = [ [package.extras] protobuf = ["grpcio-tools (>=1.70.0)"] +[[package]] +name = "grpcio" +version = "1.71.0" +description = "HTTP/2-based RPC framework" +optional = true +python-versions = ">=3.9" +files = [ + {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, + {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"}, + {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"}, + {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"}, + {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"}, + {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"}, + {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"}, + {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"}, + {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"}, + {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"}, + {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"}, + {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"}, + {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"}, + {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"}, + {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"}, + {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"}, + {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"}, + {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"}, + {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"}, + {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"}, + {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"}, + {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"}, + {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"}, + {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"}, + {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"}, + {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"}, + {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"}, + {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"}, + {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"}, + {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, + {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.71.0)"] + [[package]] name = "grpcio-status" version = "1.70.0" description = "Status proto mapping for gRPC" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85"}, {file = "grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101"}, @@ -1234,14 +1230,28 @@ googleapis-common-protos = ">=1.5.5" grpcio = ">=1.70.0" protobuf = ">=5.26.1,<6.0dev" +[[package]] +name = "grpcio-status" +version = "1.71.0" +description = "Status proto mapping for gRPC" +optional = true +python-versions = ">=3.9" +files = [ + {file = "grpcio_status-1.71.0-py3-none-any.whl", hash = "sha256:843934ef8c09e3e858952887467f8256aac3910c55f077a359a65b2b3cde3e68"}, + {file = "grpcio_status-1.71.0.tar.gz", hash = "sha256:11405fed67b68f406b3f3c7c5ae5104a79d2d309666d10d61b152e91d28fb968"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.71.0" +protobuf = ">=5.26.1,<6.0dev" + [[package]] name = "gunicorn" version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -1263,19 +1273,43 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + [[package]] name = "httpcore" version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -1297,7 +1331,6 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1322,8 +1355,6 @@ version = "0.4.0" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, @@ -1335,7 +1366,6 @@ version = "0.29.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" -groups = ["main"] files = [ {file = "huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa"}, {file = "huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5"}, @@ -1370,8 +1400,6 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -1380,13 +1408,48 @@ files = [ [package.dependencies] pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} +[[package]] +name = "hypercorn" +version = "0.15.0" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hypercorn-0.15.0-py3-none-any.whl", hash = "sha256:5008944999612fd188d7a1ca02e89d20065642b89503020ac392dfed11840730"}, + {file = "hypercorn-0.15.0.tar.gz", hash = "sha256:d517f68d5dc7afa9a9d50ecefb0f769f466ebe8c1c18d2c2f447a24e763c9a63"}, +] + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +taskgroup = {version = "*", markers = "python_version < \"3.11\""} +tomli = {version = "*", markers = "python_version < \"3.11\""} +wsproto = ">=0.14.0" + +[package.extras] +docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] +uvloop = ["uvloop"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1401,7 +1464,6 @@ version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -1425,8 +1487,6 @@ version = "6.4.5" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.9\"" files = [ {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, @@ -1449,7 +1509,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1461,8 +1520,6 @@ version = "0.7.2" description = "An ISO 8601 date/time/duration parser and formatter" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -1474,7 +1531,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1492,7 +1548,6 @@ version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, @@ -1578,8 +1633,6 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1591,7 +1644,6 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -1615,7 +1667,6 @@ version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, @@ -1631,7 +1682,6 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -1701,7 +1751,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1713,8 +1762,6 @@ version = "1.5.0" description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "mcp-1.5.0-py3-none-any.whl", hash = "sha256:51c3f35ce93cb702f7513c12406bbea9665ef75a08db909200b07da9db641527"}, {file = "mcp-1.5.0.tar.gz", hash = "sha256:5b2766c05e68e01a2034875e250139839498c61792163a7b221fc170c12f5aa9"}, @@ -1741,8 +1788,6 @@ version = "0.4.1" description = "" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5"}, {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24"}, @@ -1765,10 +1810,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">1.20", markers = "python_version < \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] [package.extras] @@ -1780,8 +1825,6 @@ version = "1.32.0" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7"}, {file = "msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2"}, @@ -1801,8 +1844,6 @@ version = "1.3.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "msal_extensions-1.3.0-py3-none-any.whl", hash = "sha256:105328ddcbdd342016c9949d8f89e3917554740c8ab26669c0fa0e069e730a0e"}, {file = "msal_extensions-1.3.0.tar.gz", hash = "sha256:96918996642b38c78cd59b55efa0f06fd1373c90e0949be8615697c048fba62c"}, @@ -1820,7 +1861,6 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -1925,7 +1965,6 @@ version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -1985,7 +2024,6 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1995,10 +2033,8 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" -optional = true +optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -2010,8 +2046,6 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.12\"" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2057,8 +2091,6 @@ version = "2.2.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.12\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"}, {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"}, @@ -2123,8 +2155,6 @@ version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, @@ -2137,14 +2167,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "openai" -version = "1.68.2" +version = "1.69.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ - {file = "openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36"}, - {file = "openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19"}, + {file = "openai-1.69.0-py3-none-any.whl", hash = "sha256:73c4b2ddfd050060f8d93c70367189bd891e70a5adb6d69c04c3571f4fea5627"}, + {file = "openai-1.69.0.tar.gz", hash = "sha256:7b8a10a8ff77e1ae827e5e4c8480410af2070fb68bc973d6c994cf8218f1f98d"}, ] [package.dependencies] @@ -2168,8 +2197,6 @@ version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, @@ -2258,7 +2285,6 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -2270,7 +2296,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2282,8 +2307,6 @@ version = "1.3.10" description = "Resolve a name to an object." optional = false python-versions = ">=3.6" -groups = ["main"] -markers = "python_version < \"3.9\"" files = [ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, @@ -2295,7 +2318,6 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -2312,7 +2334,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2322,14 +2343,23 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] + [[package]] name = "prisma" version = "0.11.0" description = "Prisma Client Python is an auto-generated and fully type-safe database client" -optional = true +optional = false python-versions = ">=3.7.0" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "prisma-0.11.0-py3-none-any.whl", hash = "sha256:22bb869e59a2968b99f3483bb417717273ffbc569fd1e9ceed95e5614cbaf53a"}, {file = "prisma-0.11.0.tar.gz", hash = "sha256:3f2f2fd2361e1ec5ff655f2a04c7860c2f2a5bc4c91f78ca9c5c6349735bf693"}, @@ -2355,7 +2385,6 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -2463,8 +2492,6 @@ version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -2482,8 +2509,6 @@ version = "5.29.4" description = "" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"}, {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"}, @@ -2504,8 +2529,6 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -2513,19 +2536,17 @@ files = [ [[package]] name = "pyasn1-modules" -version = "0.4.1" +version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" +pyasn1 = ">=0.6.1,<0.7.0" [[package]] name = "pycodestyle" @@ -2533,7 +2554,6 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -2545,8 +2565,6 @@ version = "2.22" description = "C parser in Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2558,7 +2576,6 @@ version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -2580,7 +2597,6 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -2693,8 +2709,6 @@ version = "2.8.1" description = "Settings management using Pydantic" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, @@ -2715,7 +2729,6 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -2727,8 +2740,6 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\" or extra == \"extra-proxy\"" files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -2749,8 +2760,6 @@ version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = true python-versions = ">=3.6" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, @@ -2777,8 +2786,6 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and sys_platform == \"win32\" and python_version < \"3.14\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -2793,7 +2800,6 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -2816,7 +2822,6 @@ version = "0.21.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, @@ -2835,7 +2840,6 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2853,8 +2857,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2869,7 +2871,6 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -2884,8 +2885,6 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -2897,8 +2896,6 @@ version = "3.0.0" description = "Universally unique lexicographically sortable identifier" optional = true python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, @@ -2913,7 +2910,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2976,8 +2972,6 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\" or extra == \"proxy\"" files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2996,8 +2990,6 @@ version = "0.4.1" description = "Python client library and CLI for using Redis as a vector database" optional = true python-versions = "<3.14,>=3.9" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "redisvl-0.4.1-py3-none-any.whl", hash = "sha256:6db5d5bc95b1fe8032a1cdae74ce1c65bc7fe9054e5429b5d34d5a91d28bae5f"}, {file = "redisvl-0.4.1.tar.gz", hash = "sha256:fd6a36426ba94792c0efca20915c31232d4ee3cc58eb23794a62c142696401e6"}, @@ -3032,7 +3024,6 @@ version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -3048,7 +3039,6 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -3152,7 +3142,6 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -3174,8 +3163,6 @@ version = "0.8.0" description = "Resend Python SDK" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "resend-0.8.0-py2.py3-none-any.whl", hash = "sha256:adc1515dadf4f4fc6b90db55a237f0f37fc56fd74287a986519a8a187fdb661d"}, {file = "resend-0.8.0.tar.gz", hash = "sha256:94142394701724dbcfcd8f760f675c662a1025013e741dd7cc773ca885526257"}, @@ -3184,13 +3171,26 @@ files = [ [package.dependencies] requests = "2.31.0" +[[package]] +name = "respx" +version = "0.20.2" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +optional = false +python-versions = ">=3.7" +files = [ + {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, + {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, +] + +[package.dependencies] +httpx = ">=0.21.0" + [[package]] name = "rpds-py" version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, @@ -3303,8 +3303,6 @@ version = "2.2.0" description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "rq-2.2.0-py3-none-any.whl", hash = "sha256:dacbfe1ccb79a45c8cd95dec7951620679fa0195570b63da3f9347622d33accc"}, {file = "rq-2.2.0.tar.gz", hash = "sha256:b636760f1e4c183022031c142faa0483e687885824e9732ba2953f994104e203"}, @@ -3320,8 +3318,6 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = true python-versions = ">=3.6,<4" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -3336,8 +3332,6 @@ version = "0.10.4" description = "An Amazon S3 Transfer Manager" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, @@ -3355,8 +3349,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "extra == \"extra-proxy\" or extra == \"proxy\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3368,7 +3360,6 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3380,8 +3371,6 @@ version = "2.1.3" description = "SSE plugin for Starlette" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, @@ -3401,8 +3390,6 @@ version = "0.44.0" description = "The little ASGI library that shines." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, @@ -3421,8 +3408,6 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = true python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -3431,14 +3416,27 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "taskgroup" +version = "0.2.2" +description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout" +optional = false +python-versions = "*" +files = [ + {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"}, + {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"}, +] + +[package.dependencies] +exceptiongroup = "*" +typing_extensions = ">=4.12.2,<5" + [[package]] name = "tenacity" version = "9.0.0" description = "Retry code until it succeeds" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -3454,7 +3452,6 @@ version = "0.7.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, @@ -3507,7 +3504,6 @@ version = "0.21.0" description = "" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, @@ -3540,8 +3536,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3581,10 +3575,8 @@ files = [ name = "tomlkit" version = "0.13.2" description = "Style preserving TOML library" -optional = true +optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -3596,7 +3588,6 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -3614,14 +3605,13 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, + {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, ] [[package]] @@ -3630,8 +3620,6 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = true python-versions = ">=2" -groups = ["main"] -markers = "extra == \"proxy\" and platform_system == \"Windows\"" files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -3643,8 +3631,6 @@ version = "5.2" description = "tzinfo object for the local timezone" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, @@ -3663,8 +3649,6 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main"] -markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, @@ -3681,8 +3665,6 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, @@ -3700,8 +3682,6 @@ version = "0.29.0" description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -3721,8 +3701,6 @@ version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = true python-versions = ">=3.8.0" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, @@ -3774,8 +3752,6 @@ version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = true python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"proxy\"" files = [ {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, @@ -3865,13 +3841,26 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "yarl" version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, @@ -3984,7 +3973,6 @@ version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, @@ -4003,6 +3991,6 @@ extra-proxy = ["azure-identity", "azure-keyvault-secrets", "google-cloud-kms", " proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", "fastapi-sso", "gunicorn", "mcp", "orjson", "pynacl", "python-multipart", "pyyaml", "rq", "uvicorn", "uvloop", "websockets"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "9c863b11189227a035a9130c8872de44fe7c5e1e32b47569a56af86e3f6570c5" +content-hash = "0f195796116a7c7a4a04d9958a7662d74baccb5266f531bb58a4403fd4db4e0e" diff --git a/pyproject.toml b/pyproject.toml index c3b0fd3224..325bf8b4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,11 @@ mypy = "^1.0" pytest = "^7.4.3" pytest-mock = "^3.12.0" pytest-asyncio = "^0.21.1" +respx = "^0.20.2" + +[tool.poetry.group.proxy-dev.dependencies] +prisma = "0.11.0" +hypercorn = "^0.15.0" [build-system] requires = ["poetry-core", "wheel"] diff --git a/tests/litellm/conftest.py b/tests/litellm/conftest.py new file mode 100644 index 0000000000..b3561d8a62 --- /dev/null +++ b/tests/litellm/conftest.py @@ -0,0 +1,63 @@ +# conftest.py + +import importlib +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +import litellm + + +@pytest.fixture(scope="function", autouse=True) +def setup_and_teardown(): + """ + This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained. + """ + curr_dir = os.getcwd() # Get the current working directory + sys.path.insert( + 0, os.path.abspath("../..") + ) # Adds the project directory to the system path + + import litellm + from litellm import Router + + importlib.reload(litellm) + + try: + if hasattr(litellm, "proxy") and hasattr(litellm.proxy, "proxy_server"): + import litellm.proxy.proxy_server + + importlib.reload(litellm.proxy.proxy_server) + except Exception as e: + print(f"Error reloading litellm.proxy.proxy_server: {e}") + + import asyncio + + loop = asyncio.get_event_loop_policy().new_event_loop() + asyncio.set_event_loop(loop) + print(litellm) + # from litellm import Router, completion, aembedding, acompletion, embedding + yield + + # Teardown code (executes after the yield point) + loop.close() # Close the loop created earlier + asyncio.set_event_loop(None) # Remove the reference to the loop + + +def pytest_collection_modifyitems(config, items): + # Separate tests in 'test_amazing_proxy_custom_logger.py' and other tests + custom_logger_tests = [ + item for item in items if "custom_logger" in item.parent.name + ] + other_tests = [item for item in items if "custom_logger" not in item.parent.name] + + # Sort tests based on their names + custom_logger_tests.sort(key=lambda x: x.name) + other_tests.sort(key=lambda x: x.name) + + # Reorder the items list + items[:] = custom_logger_tests + other_tests diff --git a/tests/litellm/integrations/test_custom_prompt_management.py b/tests/litellm/integrations/test_custom_prompt_management.py index 00dc6fe96f..646b222e72 100644 --- a/tests/litellm/integrations/test_custom_prompt_management.py +++ b/tests/litellm/integrations/test_custom_prompt_management.py @@ -19,6 +19,11 @@ from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import StandardCallbackDynamicParams +@pytest.fixture(autouse=True) +def setup_anthropic_api_key(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-some-key") + + class TestCustomPromptManagement(CustomPromptManagement): def get_chat_completion_prompt( self, @@ -50,7 +55,7 @@ class TestCustomPromptManagement(CustomPromptManagement): @pytest.mark.asyncio -async def test_custom_prompt_management_with_prompt_id(): +async def test_custom_prompt_management_with_prompt_id(monkeypatch): custom_prompt_management = TestCustomPromptManagement() litellm.callbacks = [custom_prompt_management] diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index a5ee9ddf70..609fbbcc52 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -26,6 +26,11 @@ def client(): return TestClient(app) +@pytest.fixture(autouse=True) +def add_anthropic_api_key_to_env(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") + + @pytest.mark.asyncio async def test_ui_view_spend_logs_with_user_id(client, monkeypatch): # Mock data for the test @@ -500,7 +505,7 @@ class TestSpendLogsPayload: return mock_response @pytest.mark.asyncio - async def test_spend_logs_payload_success_log_with_api_base(self): + async def test_spend_logs_payload_success_log_with_api_base(self, monkeypatch): from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler litellm.callbacks = [_ProxyDBLogger(message_logging=False)] diff --git a/tests/litellm/proxy/test_proxy_server.py b/tests/litellm/proxy/test_proxy_server.py index 77b521e836..c1e935addd 100644 --- a/tests/litellm/proxy/test_proxy_server.py +++ b/tests/litellm/proxy/test_proxy_server.py @@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, mock_open, patch import click import httpx import pytest +import yaml from fastapi import FastAPI from fastapi.testclient import TestClient @@ -74,3 +75,92 @@ async def test_initialize_scheduled_jobs_credentials(monkeypatch): call[0] for call in mock_proxy_config.get_credentials.mock_calls ] assert len(mock_scheduler_calls) > 0 + + +# Mock Prisma +class MockPrisma: + def __init__(self, database_url=None, proxy_logging_obj=None, http_client=None): + self.database_url = database_url + self.proxy_logging_obj = proxy_logging_obj + self.http_client = http_client + + async def connect(self): + pass + + async def disconnect(self): + pass + + +mock_prisma = MockPrisma() + + +@patch( + "litellm.proxy.proxy_server.ProxyStartupEvent._setup_prisma_client", + return_value=mock_prisma, +) +@pytest.mark.asyncio +async def test_aaaproxy_startup_master_key(mock_prisma, monkeypatch, tmp_path): + """ + Test that master_key is correctly loaded from either config.yaml or environment variables + """ + import yaml + from fastapi import FastAPI + + # Import happens here - this is when the module probably reads the config path + from litellm.proxy.proxy_server import proxy_startup_event + + # Mock the Prisma import + monkeypatch.setattr("litellm.proxy.proxy_server.PrismaClient", MockPrisma) + + # Create test app + app = FastAPI() + + # Test Case 1: Master key from config.yaml + test_master_key = "sk-12345" + test_config = {"general_settings": {"master_key": test_master_key}} + + # Create a temporary config file + config_path = tmp_path / "config.yaml" + with open(config_path, "w") as f: + yaml.dump(test_config, f) + + print(f"SET ENV VARIABLE - CONFIG_FILE_PATH, str(config_path): {str(config_path)}") + # Second setting of CONFIG_FILE_PATH to a different value + monkeypatch.setenv("CONFIG_FILE_PATH", str(config_path)) + print(f"config_path: {config_path}") + print(f"os.getenv('CONFIG_FILE_PATH'): {os.getenv('CONFIG_FILE_PATH')}") + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_master_key + + # Test Case 2: Master key from environment variable + test_env_master_key = "sk-67890" + + # Create empty config + empty_config = {"general_settings": {}} + with open(config_path, "w") as f: + yaml.dump(empty_config, f) + + monkeypatch.setenv("LITELLM_MASTER_KEY", test_env_master_key) + print("test_env_master_key: {}".format(test_env_master_key)) + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_env_master_key + + # Test Case 3: Master key with os.environ prefix + test_resolved_key = "sk-resolved-key" + test_config_with_prefix = { + "general_settings": {"master_key": "os.environ/CUSTOM_MASTER_KEY"} + } + + # Create config with os.environ prefix + with open(config_path, "w") as f: + yaml.dump(test_config_with_prefix, f) + + monkeypatch.setenv("CUSTOM_MASTER_KEY", test_resolved_key) + async with proxy_startup_event(app): + from litellm.proxy.proxy_server import master_key + + assert master_key == test_resolved_key diff --git a/tests/litellm/rerank_api/test_rerank_main.py b/tests/litellm/rerank_api/test_rerank_main.py deleted file mode 100644 index d3051b81f9..0000000000 --- a/tests/litellm/rerank_api/test_rerank_main.py +++ /dev/null @@ -1,51 +0,0 @@ -import json -import os -import sys - -import pytest -from fastapi.testclient import TestClient - -sys.path.insert( - 0, os.path.abspath("../../..") -) # Adds the parent directory to the system path -from unittest.mock import MagicMock, patch - -from litellm import rerank -from litellm.llms.custom_httpx.http_handler import HTTPHandler - - -def test_rerank_infer_region_from_model_arn(monkeypatch): - mock_response = MagicMock() - - monkeypatch.setenv("AWS_REGION_NAME", "us-east-1") - args = { - "model": "bedrock/arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0", - "query": "hello", - "documents": ["hello", "world"], - } - - def return_val(): - return { - "results": [ - {"index": 0, "relevanceScore": 0.6716859340667725}, - {"index": 1, "relevanceScore": 0.0004994205664843321}, - ] - } - - mock_response.json = return_val - mock_response.headers = {"key": "value"} - mock_response.status_code = 200 - - client = HTTPHandler() - - with patch.object(client, "post", return_value=mock_response) as mock_post: - rerank( - model=args["model"], - query=args["query"], - documents=args["documents"], - client=client, - ) - mock_post.assert_called_once() - print(f"mock_post.call_args: {mock_post.call_args.kwargs}") - assert "us-west-2" in mock_post.call_args.kwargs["url"] - assert "us-east-1" not in mock_post.call_args.kwargs["url"] diff --git a/tests/litellm/test_main.py b/tests/litellm/test_main.py index 4892300eb9..99ac8514f6 100644 --- a/tests/litellm/test_main.py +++ b/tests/litellm/test_main.py @@ -14,6 +14,15 @@ from unittest.mock import MagicMock, patch import litellm +@pytest.fixture(autouse=True) +def add_api_keys_to_env(monkeypatch): + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") + monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-api03-1234567890") + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "my-fake-aws-access-key-id") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "my-fake-aws-secret-access-key") + monkeypatch.setenv("AWS_REGION", "us-east-1") + + @pytest.fixture def openai_api_response(): mock_response_data = { @@ -130,7 +139,8 @@ def test_completion_missing_role(openai_api_response): ) @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio -async def test_url_with_format_param(model, sync_mode): +async def test_url_with_format_param(model, sync_mode, monkeypatch): + from litellm import acompletion, completion from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler diff --git a/tests/llm_translation/test_cohere.py b/tests/llm_translation/test_cohere.py index f34098477d..72df3ad436 100644 --- a/tests/llm_translation/test_cohere.py +++ b/tests/llm_translation/test_cohere.py @@ -22,6 +22,7 @@ litellm.num_retries = 3 @pytest.mark.parametrize("stream", [True, False]) +@pytest.mark.flaky(retries=3, delay=1) @pytest.mark.asyncio async def test_chat_completion_cohere_citations(stream): try: diff --git a/tests/llm_translation/test_rerank.py b/tests/llm_translation/test_rerank.py index d2cb2b6fea..5de6c1a8ec 100644 --- a/tests/llm_translation/test_rerank.py +++ b/tests/llm_translation/test_rerank.py @@ -481,3 +481,42 @@ def test_rerank_cohere_api(): assert response.results[0]["document"]["text"] is not None assert response.results[0]["document"]["text"] == "hello" assert response.results[1]["document"]["text"] == "world" + + +def test_rerank_infer_region_from_model_arn(monkeypatch): + + mock_response = MagicMock() + + monkeypatch.setenv("AWS_REGION_NAME", "us-east-1") + args = { + "model": "bedrock/arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0", + "query": "hello", + "documents": ["hello", "world"], + } + + def return_val(): + return { + "results": [ + {"index": 0, "relevanceScore": 0.6716859340667725}, + {"index": 1, "relevanceScore": 0.0004994205664843321}, + ] + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + client = HTTPHandler() + + with patch.object(client, "post", return_value=mock_response) as mock_post: + litellm.rerank( + model=args["model"], + query=args["query"], + documents=args["documents"], + client=client, + ) + + mock_post.assert_called_once() + print(f"mock_post.call_args: {mock_post.call_args.kwargs}") + assert "us-west-2" in mock_post.call_args.kwargs["url"] + assert "us-east-1" not in mock_post.call_args.kwargs["url"] From 1a441def03e645c5e28ece1ceaa21876a3081afd Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 12:47:26 -0700 Subject: [PATCH 164/244] fix(logging): add json formatting for uncaught exceptions (#9615) (#9619) * fix(logging): add json formatting for uncaught exceptions (#9615) * fix(_logging.py): cleanup logging to catch unhandled exceptions * fix(_logging.py): avoid using 'print' ' --------- Co-authored-by: Henrique Cavarsan --- litellm/_logging.py | 60 +++++++++++++++++++++++++-- litellm/proxy/_new_secret_config.yaml | 1 + 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/litellm/_logging.py b/litellm/_logging.py index 151ae6003d..d7e2c9e778 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -1,6 +1,7 @@ import json import logging import os +import sys from datetime import datetime from logging import Formatter @@ -40,9 +41,56 @@ class JsonFormatter(Formatter): return json.dumps(json_record) +# Function to set up exception handlers for JSON logging +def _setup_json_exception_handlers(formatter): + # Create a handler with JSON formatting for exceptions + error_handler = logging.StreamHandler() + error_handler.setFormatter(formatter) + + # Setup excepthook for uncaught exceptions + def json_excepthook(exc_type, exc_value, exc_traceback): + record = logging.LogRecord( + name="LiteLLM", + level=logging.ERROR, + pathname="", + lineno=0, + msg=str(exc_value), + args=(), + exc_info=(exc_type, exc_value, exc_traceback), + ) + error_handler.handle(record) + + sys.excepthook = json_excepthook + + # Configure asyncio exception handler if possible + try: + import asyncio + + def async_json_exception_handler(loop, context): + exception = context.get("exception") + if exception: + record = logging.LogRecord( + name="LiteLLM", + level=logging.ERROR, + pathname="", + lineno=0, + msg=str(exception), + args=(), + exc_info=None, + ) + error_handler.handle(record) + else: + loop.default_exception_handler(context) + + asyncio.get_event_loop().set_exception_handler(async_json_exception_handler) + except Exception: + pass + + # Create a formatter and set it for the handler if json_logs: handler.setFormatter(JsonFormatter()) + _setup_json_exception_handlers(JsonFormatter()) else: formatter = logging.Formatter( "\033[92m%(asctime)s - %(name)s:%(levelname)s\033[0m: %(filename)s:%(lineno)s - %(message)s", @@ -65,18 +113,24 @@ def _turn_on_json(): handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) - # Define a list of the loggers to update - loggers = [verbose_router_logger, verbose_proxy_logger, verbose_logger] + # Define all loggers to update, including root logger + loggers = [logging.getLogger()] + [ + verbose_router_logger, + verbose_proxy_logger, + verbose_logger, + ] # Iterate through each logger and update its handlers for logger in loggers: # Remove all existing handlers for h in logger.handlers[:]: logger.removeHandler(h) - # Add the new handler logger.addHandler(handler) + # Set up exception handlers + _setup_json_exception_handlers(JsonFormatter()) + def _turn_on_debug(): verbose_logger.setLevel(level=logging.DEBUG) # set package log to debug diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index e57c5f3980..72edeb55ca 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -20,6 +20,7 @@ model_list: litellm_settings: num_retries: 0 callbacks: ["prometheus"] + json_logs: true router_settings: routing_strategy: usage-based-routing-v2 # 👈 KEY CHANGE From 9e4165ac75887608c91b9c5d8f1ad28b75af08eb Mon Sep 17 00:00:00 2001 From: Dbzman Date: Fri, 28 Mar 2025 21:20:12 +0100 Subject: [PATCH 165/244] fix: wrong indentation for ttlSecondsAfterFinished (#9611) --- deploy/charts/litellm-helm/templates/migrations-job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/deploy/charts/litellm-helm/templates/migrations-job.yaml index e994c45548..1c4b6817fa 100644 --- a/deploy/charts/litellm-helm/templates/migrations-job.yaml +++ b/deploy/charts/litellm-helm/templates/migrations-job.yaml @@ -65,6 +65,6 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} - ttlSecondsAfterFinished: {{ .Values.migrationJob.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.migrationJob.ttlSecondsAfterFinished }} backoffLimit: {{ .Values.migrationJob.backoffLimit }} {{- end }} From 8c9ff23e195e074b5abc73ba24e8b8a3a7ffaa41 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Mar 2025 13:31:54 -0700 Subject: [PATCH 166/244] test(test_caching_handler.py): move to in-memory cache - prevent redis flakiness from impacting ci/cd --- tests/local_testing/test_caching_handler.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/local_testing/test_caching_handler.py b/tests/local_testing/test_caching_handler.py index c2466000c4..c0e3b8d306 100644 --- a/tests/local_testing/test_caching_handler.py +++ b/tests/local_testing/test_caching_handler.py @@ -40,12 +40,7 @@ import logging def setup_cache(): # Set up the cache - cache = Cache( - type=LiteLLMCacheType.REDIS, - host=os.environ["REDIS_HOST"], - port=os.environ["REDIS_PORT"], - password=os.environ["REDIS_PASSWORD"], - ) + cache = Cache(type=LiteLLMCacheType.LOCAL) litellm.cache = cache return cache From 7c73987066dd1a003f5960c26847506237e2f323 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 15:17:01 -0700 Subject: [PATCH 167/244] fix loc of DBSpendUpdateTransactions --- litellm/proxy/_types.py | 9 +++++++++ litellm/proxy/db/db_spend_update_writer.py | 6 ++---- litellm/proxy/db/redis_update_buffer.py | 12 ++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 17b4acd138..1c3251c12a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2733,3 +2733,12 @@ class DailyUserSpendTransaction(TypedDict): prompt_tokens: int completion_tokens: int spend: float + + +class DBSpendUpdateTransactions(TypedDict): + user_list_transactions: Optional[Dict[str, float]] + end_user_list_transactions: Optional[Dict[str, float]] + key_list_transactions: Optional[Dict[str, float]] + team_list_transactions: Optional[Dict[str, float]] + team_member_list_transactions: Optional[Dict[str, float]] + org_list_transactions: Optional[Dict[str, float]] diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 17247aff74..8f175fc905 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -18,15 +18,13 @@ from litellm.caching import DualCache, RedisCache from litellm.constants import DB_SPEND_UPDATE_JOB_NAME from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, + DBSpendUpdateTransactions, Litellm_EntityType, LiteLLM_UserTable, SpendLogsPayload, ) from litellm.proxy.db.pod_lock_manager import PodLockManager -from litellm.proxy.db.redis_update_buffer import ( - DBSpendUpdateTransactions, - RedisUpdateBuffer, -) +from litellm.proxy.db.redis_update_buffer import RedisUpdateBuffer if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient, ProxyLogging diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 2207911024..6f334e90ed 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -4,10 +4,11 @@ Handles buffering database `UPDATE` transactions in Redis before committing them This is to prevent deadlocks and improve reliability """ -from typing import TYPE_CHECKING, Any, Dict, Optional, TypedDict, Union, cast +from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast from litellm._logging import verbose_proxy_logger from litellm.caching import RedisCache +from litellm.proxy._types import DBSpendUpdateTransactions from litellm.secret_managers.main import str_to_bool if TYPE_CHECKING: @@ -16,15 +17,6 @@ else: PrismaClient = Any -class DBSpendUpdateTransactions(TypedDict): - user_list_transactions: Optional[Dict[str, float]] - end_user_list_transactions: Optional[Dict[str, float]] - key_list_transactions: Optional[Dict[str, float]] - team_list_transactions: Optional[Dict[str, float]] - team_member_list_transactions: Optional[Dict[str, float]] - org_list_transactions: Optional[Dict[str, float]] - - class RedisUpdateBuffer: """ Handles buffering database `UPDATE` transactions in Redis before committing them to the database From 06a45706b295a5be82955bf78384bac3e8aa4254 Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Fri, 28 Mar 2025 15:18:05 -0700 Subject: [PATCH 168/244] Rebase 3 --- .../vertex_ai/test_vertex_ai_common_utils.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index 9412cc6d81..77fa066197 100644 --- a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -153,3 +153,71 @@ async def test_get_supports_system_message(): model="random-model-name", custom_llm_provider="vertex_ai" ) assert result == False +def test_basic_anyof_conversion(): + """Test basic conversion of anyOf with 'null'.""" + schema = { + "type": "object", + "properties": { + "example": { + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] + } + } + } + + convert_anyof_null_to_nullable(schema) + + expected = { + "type": "object", + "properties": { + "example": { + "anyOf": [ + {"type": "string", "nullable": True} + ] + } + } + } + assert schema == expected + + +def test_nested_anyof_conversion(): + """Test nested conversion with 'anyOf' inside properties.""" + schema = { + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "inner": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "string"}, + {"type": "null"} + ] + } + } + } + } + } + + convert_anyof_null_to_nullable(schema) + + expected = { + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "inner": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}, "nullable": True}, + {"type": "string", "nullable": True} + ] + } + } + } + } + } + assert schema == expected From 09daeac18851bb80b218ebf9cfb5a22d87cfe37d Mon Sep 17 00:00:00 2001 From: Nicholas Grabar Date: Fri, 28 Mar 2025 15:15:14 -0700 Subject: [PATCH 169/244] Rebasing 2 --- .../vertex_ai/test_vertex_ai_common_utils.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index 77fa066197..5c2f70527c 100644 --- a/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -221,3 +221,25 @@ def test_nested_anyof_conversion(): } } assert schema == expected + +def test_anyof_with_excessive_nesting(): + """Test conversion with excessive nesting > max levels +1 deep.""" + # generate a schema with excessive nesting + schema = {"type": "object", "properties": {}} + current = schema + for _ in range(DEFAULT_MAX_RECURSE_DEPTH + 1): + current["properties"] = { + "nested": { + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ], + "properties": {} + } + } + current = current["properties"]["nested"] + + + # running the conversion will raise an error + with pytest.raises(ValueError, match=f"Max depth of {DEFAULT_MAX_RECURSE_DEPTH} exceeded while processing schema. Please check the schema for excessive nesting."): + convert_anyof_null_to_nullable(schema) From df136c5689af818ed44c1b7c28d8eb3cdc66f769 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 15:20:28 -0700 Subject: [PATCH 170/244] add docstring for DBSpendUpdateTransactions --- litellm/proxy/_types.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 1c3251c12a..24beef6b63 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2736,6 +2736,10 @@ class DailyUserSpendTransaction(TypedDict): class DBSpendUpdateTransactions(TypedDict): + """ + Internal Data Structure for buffering spend updates in Redis or in memory before committing them to the database + """ + user_list_transactions: Optional[Dict[str, float]] end_user_list_transactions: Optional[Dict[str, float]] key_list_transactions: Optional[Dict[str, float]] From 222898d7271726ca23d78e59b124553c83b74532 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 15:57:40 -0700 Subject: [PATCH 171/244] Fix anthropic thinking + response_format (#9594) * fix(anthropic/chat/transformation.py): Don't set tool choice on response_format conversion when thinking is enabled Not allowed by Anthropic Fixes https://github.com/BerriAI/litellm/issues/8901 * refactor: move test to base anthropic chat tests ensures consistent behaviour across vertex/anthropic/bedrock * fix(anthropic/chat/transformation.py): if thinking token is specified and max tokens is not - ensure max token to anthropic is higher than thinking tokens * feat(converse_transformation.py): correctly handle thinking + response format on Bedrock Converse Fixes https://github.com/BerriAI/litellm/issues/8901 * fix(converse_transformation.py): correctly handle adding max tokens * test: handle service unavailable error --- litellm/constants.py | 1 + litellm/llms/anthropic/chat/transformation.py | 17 ++++++++++-- litellm/llms/base_llm/chat/transformation.py | 26 ++++++++++++++++++- .../bedrock/chat/converse_transformation.py | 12 +++++++-- tests/llm_translation/base_llm_unit_tests.py | 23 ++++++++++++++++ .../test_anthropic_completion.py | 6 +++++ .../test_bedrock_completion.py | 15 ++++++++++- tests/llm_translation/test_cohere.py | 2 ++ 8 files changed, 96 insertions(+), 6 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index da66f897c9..de0a7e366d 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -7,6 +7,7 @@ DEFAULT_MAX_RETRIES = 2 DEFAULT_FAILURE_THRESHOLD_PERCENT = ( 0.5 # default cooldown a deployment if 50% of requests fail in a given minute ) +DEFAULT_MAX_TOKENS = 4096 DEFAULT_REDIS_SYNC_INTERVAL = 1 DEFAULT_COOLDOWN_TIME_SECONDS = 5 DEFAULT_REPLICATE_POLLING_RETRIES = 5 diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 1a77c453f4..dcbc6775dc 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -300,6 +300,15 @@ class AnthropicConfig(BaseConfig): model: str, drop_params: bool, ) -> dict: + + is_thinking_enabled = self.is_thinking_enabled( + non_default_params=non_default_params + ) + + ## handle thinking tokens + self.update_optional_params_with_thinking_tokens( + non_default_params=non_default_params, optional_params=optional_params + ) for param, value in non_default_params.items(): if param == "max_tokens": optional_params["max_tokens"] = value @@ -349,19 +358,23 @@ class AnthropicConfig(BaseConfig): - Remember that the model will pass the input to the tool, so the name of the tool and description should be from the model’s perspective. """ - _tool_choice = {"name": RESPONSE_FORMAT_TOOL_NAME, "type": "tool"} + if not is_thinking_enabled: + _tool_choice = {"name": RESPONSE_FORMAT_TOOL_NAME, "type": "tool"} + optional_params["tool_choice"] = _tool_choice + _tool = self._create_json_tool_call_for_response_format( json_schema=json_schema, ) optional_params = self._add_tools_to_optional_params( optional_params=optional_params, tools=[_tool] ) - optional_params["tool_choice"] = _tool_choice + optional_params["json_mode"] = True if param == "user": optional_params["metadata"] = {"user_id": value} if param == "thinking": optional_params["thinking"] = value + return optional_params def _create_json_tool_call_for_response_format( diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index 1b5a6bc58e..45ea06b9e4 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -13,12 +13,13 @@ from typing import ( Optional, Type, Union, + cast, ) import httpx from pydantic import BaseModel -from litellm.constants import RESPONSE_FORMAT_TOOL_NAME +from litellm.constants import DEFAULT_MAX_TOKENS, RESPONSE_FORMAT_TOOL_NAME from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.types.llms.openai import ( AllMessageValues, @@ -102,6 +103,29 @@ class BaseConfig(ABC): ) -> Optional[dict]: return type_to_response_format_param(response_format=response_format) + def is_thinking_enabled(self, non_default_params: dict) -> bool: + return non_default_params.get("thinking", {}).get("type", None) == "enabled" + + def update_optional_params_with_thinking_tokens( + self, non_default_params: dict, optional_params: dict + ): + """ + Handles scenario where max tokens is not specified. For anthropic models (anthropic api/bedrock/vertex ai), this requires having the max tokens being set and being greater than the thinking token budget. + + Checks 'non_default_params' for 'thinking' and 'max_tokens' + + if 'thinking' is enabled and 'max_tokens' is not specified, set 'max_tokens' to the thinking token budget + DEFAULT_MAX_TOKENS + """ + is_thinking_enabled = self.is_thinking_enabled(non_default_params) + if is_thinking_enabled and "max_tokens" not in non_default_params: + thinking_token_budget = cast(dict, non_default_params["thinking"]).get( + "budget_tokens", None + ) + if thinking_token_budget is not None: + optional_params["max_tokens"] = ( + thinking_token_budget + DEFAULT_MAX_TOKENS + ) + def should_fake_stream( self, model: Optional[str], diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index bb874cfe38..b63b76a8ee 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -210,6 +210,10 @@ class AmazonConverseConfig(BaseConfig): drop_params: bool, messages: Optional[List[AllMessageValues]] = None, ) -> dict: + is_thinking_enabled = self.is_thinking_enabled(non_default_params) + self.update_optional_params_with_thinking_tokens( + non_default_params=non_default_params, optional_params=optional_params + ) for param, value in non_default_params.items(): if param == "response_format" and isinstance(value, dict): @@ -247,8 +251,11 @@ class AmazonConverseConfig(BaseConfig): optional_params = self._add_tools_to_optional_params( optional_params=optional_params, tools=[_tool] ) - if litellm.utils.supports_tool_choice( - model=model, custom_llm_provider=self.custom_llm_provider + if ( + litellm.utils.supports_tool_choice( + model=model, custom_llm_provider=self.custom_llm_provider + ) + and not is_thinking_enabled ): optional_params["tool_choice"] = ToolChoiceValuesBlock( tool=SpecificToolChoiceBlock( @@ -284,6 +291,7 @@ class AmazonConverseConfig(BaseConfig): optional_params["tool_choice"] = _tool_choice_value if param == "thinking": optional_params["thinking"] = value + return optional_params @overload diff --git a/tests/llm_translation/base_llm_unit_tests.py b/tests/llm_translation/base_llm_unit_tests.py index 82a1ef40fb..eb58512197 100644 --- a/tests/llm_translation/base_llm_unit_tests.py +++ b/tests/llm_translation/base_llm_unit_tests.py @@ -1008,6 +1008,11 @@ class BaseAnthropicChatTest(ABC): """Must return the base completion call args""" pass + @abstractmethod + def get_base_completion_call_args_with_thinking(self) -> dict: + """Must return the base completion call args""" + pass + @property def completion_function(self): return litellm.completion @@ -1066,3 +1071,21 @@ class BaseAnthropicChatTest(ABC): json.loads(built_response.choices[0].message.content).keys() == json.loads(non_stream_response.choices[0].message.content).keys() ), f"Got={json.loads(built_response.choices[0].message.content)}, Expected={json.loads(non_stream_response.choices[0].message.content)}" + + def test_completion_thinking_with_response_format(self): + from pydantic import BaseModel + + class RFormat(BaseModel): + question: str + answer: str + + base_completion_call_args = self.get_base_completion_call_args_with_thinking() + + messages = [{"role": "user", "content": "Generate 5 question + answer pairs"}] + response = self.completion_function( + **base_completion_call_args, + messages=messages, + response_format=RFormat, + ) + + print(response) diff --git a/tests/llm_translation/test_anthropic_completion.py b/tests/llm_translation/test_anthropic_completion.py index a83d1d69e9..3f4c0b63f0 100644 --- a/tests/llm_translation/test_anthropic_completion.py +++ b/tests/llm_translation/test_anthropic_completion.py @@ -467,6 +467,12 @@ class TestAnthropicCompletion(BaseLLMChatTest, BaseAnthropicChatTest): def get_base_completion_call_args(self) -> dict: return {"model": "anthropic/claude-3-5-sonnet-20240620"} + def get_base_completion_call_args_with_thinking(self) -> dict: + return { + "model": "anthropic/claude-3-7-sonnet-latest", + "thinking": {"type": "enabled", "budget_tokens": 16000}, + } + def test_tool_call_no_arguments(self, tool_call_no_arguments): """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" from litellm.litellm_core_utils.prompt_templates.factory import ( diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index 1694033a09..917993a98c 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -35,7 +35,7 @@ from litellm import ( from litellm.llms.bedrock.chat import BedrockLLM from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_tools_pt -from base_llm_unit_tests import BaseLLMChatTest +from base_llm_unit_tests import BaseLLMChatTest, BaseAnthropicChatTest from base_rerank_unit_tests import BaseLLMRerankTest from base_embedding_unit_tests import BaseLLMEmbeddingTest @@ -2191,6 +2191,19 @@ class TestBedrockConverseChatCrossRegion(BaseLLMChatTest): assert cost > 0 +class TestBedrockConverseAnthropicUnitTests(BaseAnthropicChatTest): + def get_base_completion_call_args(self) -> dict: + return { + "model": "bedrock/us.anthropic.claude-3-5-sonnet-20241022-v2:0", + } + + def get_base_completion_call_args_with_thinking(self) -> dict: + return { + "model": "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + "thinking": {"type": "enabled", "budget_tokens": 16000}, + } + + class TestBedrockConverseChatNormal(BaseLLMChatTest): def get_base_completion_call_args(self) -> dict: os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" diff --git a/tests/llm_translation/test_cohere.py b/tests/llm_translation/test_cohere.py index 72df3ad436..124a5c8788 100644 --- a/tests/llm_translation/test_cohere.py +++ b/tests/llm_translation/test_cohere.py @@ -56,6 +56,8 @@ async def test_chat_completion_cohere_citations(stream): assert citations_chunk else: assert response.citations is not None + except litellm.ServiceUnavailableError: + pass except Exception as e: pytest.fail(f"Error occurred: {e}") From cec5280e033e6ff57ed1a1da0016622ad34ae1e9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 16:16:44 -0700 Subject: [PATCH 172/244] debugging for Redis TX management --- litellm/proxy/db/db_spend_update_writer.py | 27 ++++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 8f175fc905..fea40e58e7 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -496,6 +496,9 @@ class DBSpendUpdateWriter: ### UPDATE USER TABLE ### user_list_transactions = db_spend_update_transactions["user_list_transactions"] + verbose_proxy_logger.debug( + "User Spend transactions: {}".format(user_list_transactions) + ) if ( user_list_transactions is not None and len(user_list_transactions.keys()) > 0 @@ -536,14 +539,12 @@ class DBSpendUpdateWriter: ) ### UPDATE END-USER TABLE ### - verbose_proxy_logger.debug( - "End-User Spend transactions: {}".format( - len(prisma_client.end_user_list_transactions.keys()) - ) - ) end_user_list_transactions = db_spend_update_transactions[ "end_user_list_transactions" ] + verbose_proxy_logger.debug( + "End-User Spend transactions: {}".format(end_user_list_transactions) + ) if ( end_user_list_transactions is not None and len(end_user_list_transactions.keys()) > 0 @@ -595,12 +596,10 @@ class DBSpendUpdateWriter: ) ### UPDATE TEAM TABLE ### - verbose_proxy_logger.debug( - "Team Spend transactions: {}".format( - len(prisma_client.team_list_transactions.keys()) - ) - ) team_list_transactions = db_spend_update_transactions["team_list_transactions"] + verbose_proxy_logger.debug( + "Team Spend transactions: {}".format(team_list_transactions) + ) if ( team_list_transactions is not None and len(team_list_transactions.keys()) > 0 @@ -649,6 +648,11 @@ class DBSpendUpdateWriter: team_member_list_transactions = db_spend_update_transactions[ "team_member_list_transactions" ] + verbose_proxy_logger.debug( + "Team Membership Spend transactions: {}".format( + team_member_list_transactions + ) + ) if ( team_member_list_transactions is not None and len(team_member_list_transactions.keys()) > 0 @@ -694,6 +698,9 @@ class DBSpendUpdateWriter: ### UPDATE ORG TABLE ### org_list_transactions = db_spend_update_transactions["org_list_transactions"] + verbose_proxy_logger.debug( + "Org Spend transactions: {}".format(org_list_transactions) + ) if org_list_transactions is not None and len(org_list_transactions.keys()) > 0: for i in range(n_retry_times + 1): start_time = time.time() From e5e0ffa28a6b182dec898e986e839029a9cb0f62 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 17:23:47 -0700 Subject: [PATCH 173/244] add async_rpush --- litellm/caching/redis_cache.py | 135 +++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index 0571ac9f15..620ba5d73a 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -1045,3 +1045,138 @@ class RedisCache(BaseCache): except Exception as e: verbose_logger.debug(f"Redis TTL Error: {e}") return None + + async def _generic_redis_command( + self, + command: str, + key: str, + *args, + parent_otel_span: Optional[Span] = None, + **kwargs, + ) -> Any: + """ + Generic method to execute any Redis command + + Args: + command: The Redis command to execute (e.g. 'rpush', 'lpop') + key: The Redis key to operate on + args: Additional arguments to pass to the Redis command + parent_otel_span: Optional parent OpenTelemetry span + kwargs: Additional keyword arguments + + Returns: + Any: The result of the Redis command + """ + _redis_client = self.init_async_client() + key = self.check_and_fix_namespace(key=key) + start_time = time.time() + + try: + # Get the method from the Redis client + redis_method = getattr(_redis_client, command) + if not redis_method: + raise AttributeError(f"Redis client has no method named '{command}'") + + # Execute the command + result = await redis_method(key, *args) + + # Log success + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_success_hook( + service=ServiceTypes.REDIS, + duration=_duration, + call_type=f"async_{command}", + start_time=start_time, + end_time=end_time, + parent_otel_span=parent_otel_span, + event_metadata={"key": key}, + ) + ) + return result + except Exception as e: + # Log failure + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_failure_hook( + service=ServiceTypes.REDIS, + duration=_duration, + error=e, + call_type=f"async_{command}", + start_time=start_time, + end_time=end_time, + parent_otel_span=parent_otel_span, + event_metadata={"key": key}, + ) + ) + verbose_logger.error( + f"LiteLLM Redis Caching: async {command}() - Got exception from REDIS: {str(e)}" + ) + raise e + + async def async_rpush( + self, + key: str, + values: List[Any], + parent_otel_span: Optional[Span] = None, + **kwargs, + ) -> int: + """ + Append one or multiple values to a list stored at key + + Args: + key: The Redis key of the list + values: One or more values to append to the list + parent_otel_span: Optional parent OpenTelemetry span + + Returns: + int: The length of the list after the push operation + """ + print_verbose(f"RPUSH to Redis list: key: {key}, values: {values}") + return await self._generic_redis_command( + "rpush", key, *values, parent_otel_span=parent_otel_span, **kwargs + ) + + async def async_lpop( + self, + key: str, + count: Optional[int] = None, + parent_otel_span: Optional[Span] = None, + **kwargs, + ) -> Union[Any, List[Any]]: + """ + Remove and return the first element(s) of a list stored at key + + Args: + key: The Redis key of the list + count: Number of elements to pop (if None, pops a single element) + parent_otel_span: Optional parent OpenTelemetry span + + Returns: + Union[Any, List[Any]]: The popped value(s) or None if list is empty + """ + print_verbose(f"LPOP from Redis list: key: {key}, count: {count}") + args = [count] if count is not None else [] + + # Fix: Pass command as a positional argument, not as a keyword argument + result = await self._generic_redis_command( + "lpop", key, *args, parent_otel_span=parent_otel_span, **kwargs + ) + + # Handle result parsing if needed + if isinstance(result, bytes): + try: + return result.decode("utf-8") + except Exception: + return result + elif isinstance(result, list) and all( + isinstance(item, bytes) for item in result + ): + try: + return [item.decode("utf-8") for item in result] + except Exception: + return result + + return result From 88458a6568f933d00ddefe9d9e57622b19e53964 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 17:24:54 -0700 Subject: [PATCH 174/244] redis update buffer queue --- litellm/proxy/db/db_spend_update_writer.py | 2 +- litellm/proxy/db/redis_update_buffer.py | 240 +++++++++++++-------- 2 files changed, 152 insertions(+), 90 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index fea40e58e7..55b95d16bf 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -436,7 +436,7 @@ class DBSpendUpdateWriter: try: db_spend_update_transactions = ( - await self.redis_update_buffer.get_all_update_transactions_from_redis() + await self.redis_update_buffer.get_all_update_transactions_from_redis_buffer() ) if db_spend_update_transactions is not None: await DBSpendUpdateWriter._commit_spend_updates_to_db( diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 6f334e90ed..ab43dc575d 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -4,10 +4,12 @@ Handles buffering database `UPDATE` transactions in Redis before committing them This is to prevent deadlocks and improve reliability """ -from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast +import json +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from litellm._logging import verbose_proxy_logger from litellm.caching import RedisCache +from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.proxy._types import DBSpendUpdateTransactions from litellm.secret_managers.main import str_to_bool @@ -16,6 +18,9 @@ if TYPE_CHECKING: else: PrismaClient = Any +REDIS_UPDATE_BUFFER_KEY = "litellm_spend_update_buffer" +MAX_REDIS_BUFFER_DEQUEUE_COUNT = 100 + class RedisUpdateBuffer: """ @@ -61,14 +66,21 @@ class RedisUpdateBuffer: - value is the spend amount ``` - { - "0929880201": 10, - "0929880202": 20, - "0929880203": 30, - } + Redis List: + key_list_transactions: + [ + "0929880201": 1.2, + "0929880202": 0.01, + "0929880203": 0.001, + ] ``` """ - IN_MEMORY_UPDATE_TRANSACTIONS: DBSpendUpdateTransactions = ( + if self.redis_cache is None: + verbose_proxy_logger.debug( + "redis_cache is None, skipping store_in_memory_spend_updates_in_redis" + ) + return + db_spend_update_transactions: DBSpendUpdateTransactions = ( DBSpendUpdateTransactions( user_list_transactions=prisma_client.user_list_transactions, end_user_list_transactions=prisma_client.end_user_list_transactions, @@ -78,30 +90,47 @@ class RedisUpdateBuffer: org_list_transactions=prisma_client.org_list_transactions, ) ) - for key, _transactions in IN_MEMORY_UPDATE_TRANSACTIONS.items(): - await self.increment_all_transaction_objects_in_redis( - key=key, - transactions=cast(Dict, _transactions), - ) - async def increment_all_transaction_objects_in_redis( - self, - key: str, - transactions: Dict, + # only store in redis if there are any updates to commit + if ( + self._number_of_transactions_to_store_in_redis(db_spend_update_transactions) + == 0 + ): + return + + list_of_transactions = [safe_dumps(db_spend_update_transactions)] + await self.redis_cache.async_rpush( + key=REDIS_UPDATE_BUFFER_KEY, + values=list_of_transactions, + ) + self._clear_all_in_memory_spend_updates(prisma_client) + + @staticmethod + def _number_of_transactions_to_store_in_redis( + db_spend_update_transactions: DBSpendUpdateTransactions, + ) -> int: + """ + Gets the number of transactions to store in Redis + """ + num_transactions = 0 + for v in db_spend_update_transactions.values(): + if isinstance(v, dict): + num_transactions += len(v) + return num_transactions + + @staticmethod + def _clear_all_in_memory_spend_updates( + prisma_client: PrismaClient, ): """ - Increments all transaction objects in Redis + Clears all in-memory spend updates """ - if self.redis_cache is None: - verbose_proxy_logger.debug( - "redis_cache is None, skipping increment_all_transaction_objects_in_redis" - ) - return - for transaction_id, transaction_amount in transactions.items(): - await self.redis_cache.async_increment( - key=f"{key}:{transaction_id}", - value=transaction_amount, - ) + prisma_client.user_list_transactions = {} + prisma_client.end_user_list_transactions = {} + prisma_client.key_list_transactions = {} + prisma_client.team_list_transactions = {} + prisma_client.team_member_list_transactions = {} + prisma_client.org_list_transactions = {} @staticmethod def _remove_prefix_from_keys(data: Dict[str, Any], prefix: str) -> Dict[str, Any]: @@ -110,77 +139,110 @@ class RedisUpdateBuffer: """ return {key.replace(prefix, "", 1): value for key, value in data.items()} - async def get_all_update_transactions_from_redis( + async def get_all_update_transactions_from_redis_buffer( self, ) -> Optional[DBSpendUpdateTransactions]: """ Gets all the update transactions from Redis + + On Redis we store a list of transactions as a JSON string + + eg. + [ + DBSpendUpdateTransactions( + user_list_transactions={ + "user_id_1": 1.2, + "user_id_2": 0.01, + }, + end_user_list_transactions={}, + key_list_transactions={ + "0929880201": 1.2, + "0929880202": 0.01, + }, + team_list_transactions={}, + team_member_list_transactions={}, + org_list_transactions={}, + ), + DBSpendUpdateTransactions( + user_list_transactions={ + "user_id_3": 1.2, + "user_id_4": 0.01, + }, + end_user_list_transactions={}, + key_list_transactions={ + "key_id_1": 1.2, + "key_id_2": 0.01, + }, + team_list_transactions={}, + team_member_list_transactions={}, + org_list_transactions={}, + ] """ if self.redis_cache is None: return None - user_transaction_keys = await self.redis_cache.async_scan_iter( - "user_list_transactions:*" + list_of_transactions = await self.redis_cache.async_lpop( + key=REDIS_UPDATE_BUFFER_KEY, + count=MAX_REDIS_BUFFER_DEQUEUE_COUNT, ) - end_user_transaction_keys = await self.redis_cache.async_scan_iter( - "end_user_list_transactions:*" - ) - key_transaction_keys = await self.redis_cache.async_scan_iter( - "key_list_transactions:*" - ) - team_transaction_keys = await self.redis_cache.async_scan_iter( - "team_list_transactions:*" - ) - team_member_transaction_keys = await self.redis_cache.async_scan_iter( - "team_member_list_transactions:*" - ) - org_transaction_keys = await self.redis_cache.async_scan_iter( - "org_list_transactions:*" + if list_of_transactions is None: + return None + + # Parse the list of transactions from JSON strings + parsed_transactions = self._parse_list_of_transactions(list_of_transactions) + + # If there are no transactions, return None + if len(parsed_transactions) == 0: + return None + + # Combine all transactions into a single transaction + combined_transaction = self._combine_list_of_transactions(parsed_transactions) + + return combined_transaction + + @staticmethod + def _parse_list_of_transactions( + list_of_transactions: List[str], + ) -> List[DBSpendUpdateTransactions]: + """ + Parses the list of transactions from Redis + """ + return [json.loads(transaction) for transaction in list_of_transactions] + + @staticmethod + def _combine_list_of_transactions( + list_of_transactions: List[DBSpendUpdateTransactions], + ) -> DBSpendUpdateTransactions: + """ + Combines the list of transactions into a single DBSpendUpdateTransactions object + """ + # Initialize a new combined transaction object with empty dictionaries + combined_transaction = DBSpendUpdateTransactions( + user_list_transactions={}, + end_user_list_transactions={}, + key_list_transactions={}, + team_list_transactions={}, + team_member_list_transactions={}, + org_list_transactions={}, ) - user_list_transactions = await self.redis_cache.async_batch_get_cache( - user_transaction_keys - ) - end_user_list_transactions = await self.redis_cache.async_batch_get_cache( - end_user_transaction_keys - ) - key_list_transactions = await self.redis_cache.async_batch_get_cache( - key_transaction_keys - ) - team_list_transactions = await self.redis_cache.async_batch_get_cache( - team_transaction_keys - ) - team_member_list_transactions = await self.redis_cache.async_batch_get_cache( - team_member_transaction_keys - ) - org_list_transactions = await self.redis_cache.async_batch_get_cache( - org_transaction_keys - ) + # Define the transaction fields to process + transaction_fields = [ + "user_list_transactions", + "end_user_list_transactions", + "key_list_transactions", + "team_list_transactions", + "team_member_list_transactions", + "org_list_transactions", + ] - # filter out the "prefix" from the keys using the helper method - user_list_transactions = self._remove_prefix_from_keys( - user_list_transactions, "user_list_transactions:" - ) - end_user_list_transactions = self._remove_prefix_from_keys( - end_user_list_transactions, "end_user_list_transactions:" - ) - key_list_transactions = self._remove_prefix_from_keys( - key_list_transactions, "key_list_transactions:" - ) - team_list_transactions = self._remove_prefix_from_keys( - team_list_transactions, "team_list_transactions:" - ) - team_member_list_transactions = self._remove_prefix_from_keys( - team_member_list_transactions, "team_member_list_transactions:" - ) - org_list_transactions = self._remove_prefix_from_keys( - org_list_transactions, "org_list_transactions:" - ) + # Loop through each transaction and combine the values + for transaction in list_of_transactions: + # Process each field type + for field in transaction_fields: + if transaction.get(field): + for entity_id, amount in transaction[field].items(): + combined_transaction[field][entity_id] = ( + combined_transaction[field].get(entity_id, 0) + amount + ) - return DBSpendUpdateTransactions( - user_list_transactions=user_list_transactions, - end_user_list_transactions=end_user_list_transactions, - key_list_transactions=key_list_transactions, - team_list_transactions=team_list_transactions, - team_member_list_transactions=team_member_list_transactions, - org_list_transactions=org_list_transactions, - ) + return combined_transaction From eae2adcee0a4ff98544eeabd4b4850f0794ecdc3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 19:49:38 -0700 Subject: [PATCH 175/244] redis cache add async push and pop methods --- litellm/caching/redis_cache.py | 183 ++++++++++++++------------------- 1 file changed, 77 insertions(+), 106 deletions(-) diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index 620ba5d73a..7378ed878a 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -1046,76 +1046,6 @@ class RedisCache(BaseCache): verbose_logger.debug(f"Redis TTL Error: {e}") return None - async def _generic_redis_command( - self, - command: str, - key: str, - *args, - parent_otel_span: Optional[Span] = None, - **kwargs, - ) -> Any: - """ - Generic method to execute any Redis command - - Args: - command: The Redis command to execute (e.g. 'rpush', 'lpop') - key: The Redis key to operate on - args: Additional arguments to pass to the Redis command - parent_otel_span: Optional parent OpenTelemetry span - kwargs: Additional keyword arguments - - Returns: - Any: The result of the Redis command - """ - _redis_client = self.init_async_client() - key = self.check_and_fix_namespace(key=key) - start_time = time.time() - - try: - # Get the method from the Redis client - redis_method = getattr(_redis_client, command) - if not redis_method: - raise AttributeError(f"Redis client has no method named '{command}'") - - # Execute the command - result = await redis_method(key, *args) - - # Log success - end_time = time.time() - _duration = end_time - start_time - asyncio.create_task( - self.service_logger_obj.async_service_success_hook( - service=ServiceTypes.REDIS, - duration=_duration, - call_type=f"async_{command}", - start_time=start_time, - end_time=end_time, - parent_otel_span=parent_otel_span, - event_metadata={"key": key}, - ) - ) - return result - except Exception as e: - # Log failure - end_time = time.time() - _duration = end_time - start_time - asyncio.create_task( - self.service_logger_obj.async_service_failure_hook( - service=ServiceTypes.REDIS, - duration=_duration, - error=e, - call_type=f"async_{command}", - start_time=start_time, - end_time=end_time, - parent_otel_span=parent_otel_span, - event_metadata={"key": key}, - ) - ) - verbose_logger.error( - f"LiteLLM Redis Caching: async {command}() - Got exception from REDIS: {str(e)}" - ) - raise e - async def async_rpush( self, key: str, @@ -1134,10 +1064,38 @@ class RedisCache(BaseCache): Returns: int: The length of the list after the push operation """ - print_verbose(f"RPUSH to Redis list: key: {key}, values: {values}") - return await self._generic_redis_command( - "rpush", key, *values, parent_otel_span=parent_otel_span, **kwargs - ) + _redis_client: Any = self.init_async_client() + start_time = time.time() + try: + response = await _redis_client.rpush(key, *values) + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_success_hook( + service=ServiceTypes.REDIS, + duration=_duration, + call_type="async_rpush", + ) + ) + return response + except Exception as e: + # NON blocking - notify users Redis is throwing an exception + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_failure_hook( + service=ServiceTypes.REDIS, + duration=_duration, + error=e, + call_type="async_rpush", + ) + ) + verbose_logger.error( + f"LiteLLM Redis Cache RPUSH: - Got exception from REDIS : {str(e)}" + ) + raise e async def async_lpop( self, @@ -1146,37 +1104,50 @@ class RedisCache(BaseCache): parent_otel_span: Optional[Span] = None, **kwargs, ) -> Union[Any, List[Any]]: - """ - Remove and return the first element(s) of a list stored at key - - Args: - key: The Redis key of the list - count: Number of elements to pop (if None, pops a single element) - parent_otel_span: Optional parent OpenTelemetry span - - Returns: - Union[Any, List[Any]]: The popped value(s) or None if list is empty - """ + _redis_client: Any = self.init_async_client() + start_time = time.time() print_verbose(f"LPOP from Redis list: key: {key}, count: {count}") - args = [count] if count is not None else [] + try: + result = await _redis_client.lpop(key, count) + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_success_hook( + service=ServiceTypes.REDIS, + duration=_duration, + call_type="async_lpop", + ) + ) - # Fix: Pass command as a positional argument, not as a keyword argument - result = await self._generic_redis_command( - "lpop", key, *args, parent_otel_span=parent_otel_span, **kwargs - ) - - # Handle result parsing if needed - if isinstance(result, bytes): - try: - return result.decode("utf-8") - except Exception: - return result - elif isinstance(result, list) and all( - isinstance(item, bytes) for item in result - ): - try: - return [item.decode("utf-8") for item in result] - except Exception: - return result - - return result + # Handle result parsing if needed + if isinstance(result, bytes): + try: + return result.decode("utf-8") + except Exception: + return result + elif isinstance(result, list) and all( + isinstance(item, bytes) for item in result + ): + try: + return [item.decode("utf-8") for item in result] + except Exception: + return result + return result + except Exception as e: + # NON blocking - notify users Redis is throwing an exception + ## LOGGING ## + end_time = time.time() + _duration = end_time - start_time + asyncio.create_task( + self.service_logger_obj.async_service_failure_hook( + service=ServiceTypes.REDIS, + duration=_duration, + error=e, + call_type="async_lpop", + ) + ) + verbose_logger.error( + f"LiteLLM Redis Cache LPOP: - Got exception from REDIS : {str(e)}" + ) + raise e From 21bf15263cf23d2e2a5031c3ce4985c83fe468f9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 19:51:15 -0700 Subject: [PATCH 176/244] use asyncio lock for updating PrismaClient txs --- litellm/proxy/db/redis_update_buffer.py | 54 ++++++++++++++----------- litellm/proxy/utils.py | 1 + 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index ab43dc575d..3d27e41d15 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -80,30 +80,35 @@ class RedisUpdateBuffer: "redis_cache is None, skipping store_in_memory_spend_updates_in_redis" ) return - db_spend_update_transactions: DBSpendUpdateTransactions = ( - DBSpendUpdateTransactions( - user_list_transactions=prisma_client.user_list_transactions, - end_user_list_transactions=prisma_client.end_user_list_transactions, - key_list_transactions=prisma_client.key_list_transactions, - team_list_transactions=prisma_client.team_list_transactions, - team_member_list_transactions=prisma_client.team_member_list_transactions, - org_list_transactions=prisma_client.org_list_transactions, + async with prisma_client.in_memory_transaction_lock: + db_spend_update_transactions: DBSpendUpdateTransactions = ( + DBSpendUpdateTransactions( + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, + ) ) - ) - # only store in redis if there are any updates to commit - if ( - self._number_of_transactions_to_store_in_redis(db_spend_update_transactions) - == 0 - ): - return + # only store in redis if there are any updates to commit + if ( + self._number_of_transactions_to_store_in_redis( + db_spend_update_transactions + ) + == 0 + ): + return - list_of_transactions = [safe_dumps(db_spend_update_transactions)] - await self.redis_cache.async_rpush( - key=REDIS_UPDATE_BUFFER_KEY, - values=list_of_transactions, - ) - self._clear_all_in_memory_spend_updates(prisma_client) + list_of_transactions = [safe_dumps(db_spend_update_transactions)] + await self.redis_cache.async_rpush( + key=REDIS_UPDATE_BUFFER_KEY, + values=list_of_transactions, + ) + + # clear the in-memory spend updates + RedisUpdateBuffer._clear_all_in_memory_spend_updates(prisma_client) @staticmethod def _number_of_transactions_to_store_in_redis( @@ -201,12 +206,15 @@ class RedisUpdateBuffer: @staticmethod def _parse_list_of_transactions( - list_of_transactions: List[str], + list_of_transactions: Union[Any, List[Any]], ) -> List[DBSpendUpdateTransactions]: """ Parses the list of transactions from Redis """ - return [json.loads(transaction) for transaction in list_of_transactions] + if isinstance(list_of_transactions, list): + return [json.loads(transaction) for transaction in list_of_transactions] + else: + return [json.loads(list_of_transactions)] @staticmethod def _combine_list_of_transactions( diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index d0d4ea9b4e..66dd224dff 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1121,6 +1121,7 @@ class PrismaClient: self.iam_token_db_auth: Optional[bool] = str_to_bool( os.getenv("IAM_TOKEN_DB_AUTH") ) + self.in_memory_transaction_lock = asyncio.Lock() verbose_proxy_logger.debug("Creating Prisma Client..") try: from prisma import Prisma # type: ignore From 183c33bb026ba17ec91122611e8baedd2a937fe7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 19:52:07 -0700 Subject: [PATCH 177/244] prisma client in_memory_transaction_lock --- litellm/proxy/db/db_spend_update_writer.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 55b95d16bf..72692339c8 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -142,6 +142,7 @@ class DBSpendUpdateWriter: transaction_list: dict, entity_type: Litellm_EntityType, debug_msg: Optional[str] = None, + prisma_client: Optional[PrismaClient] = None, ) -> bool: """ Common helper method to update a transaction list for an entity @@ -163,16 +164,18 @@ class DBSpendUpdateWriter: verbose_proxy_logger.debug( f"adding spend to {entity_type.value} db. Response cost: {response_cost}. {entity_type.value}_id: {entity_id}." ) + if prisma_client is None: + return False if entity_id is None: verbose_proxy_logger.debug( f"track_cost_callback: {entity_type.value}_id is None. Not tracking spend for {entity_type.value}" ) return False - - transaction_list[entity_id] = response_cost + transaction_list.get( - entity_id, 0 - ) + async with prisma_client.in_memory_transaction_lock: + transaction_list[entity_id] = response_cost + transaction_list.get( + entity_id, 0 + ) return True except Exception as e: @@ -197,6 +200,7 @@ class DBSpendUpdateWriter: transaction_list=prisma_client.key_list_transactions, entity_type=Litellm_EntityType.KEY, debug_msg=f"adding spend to key db. Response cost: {response_cost}. Token: {hashed_token}.", + prisma_client=prisma_client, ) except Exception as e: verbose_proxy_logger.exception( @@ -236,6 +240,7 @@ class DBSpendUpdateWriter: entity_id=_id, transaction_list=prisma_client.user_list_transactions, entity_type=Litellm_EntityType.USER, + prisma_client=prisma_client, ) if end_user_id is not None: @@ -244,6 +249,7 @@ class DBSpendUpdateWriter: entity_id=end_user_id, transaction_list=prisma_client.end_user_list_transactions, entity_type=Litellm_EntityType.END_USER, + prisma_client=prisma_client, ) except Exception as e: verbose_proxy_logger.info( @@ -270,6 +276,7 @@ class DBSpendUpdateWriter: entity_id=team_id, transaction_list=prisma_client.team_list_transactions, entity_type=Litellm_EntityType.TEAM, + prisma_client=prisma_client, ) try: @@ -282,6 +289,7 @@ class DBSpendUpdateWriter: entity_id=team_member_key, transaction_list=prisma_client.team_member_list_transactions, entity_type=Litellm_EntityType.TEAM_MEMBER, + prisma_client=prisma_client, ) except Exception: pass @@ -309,6 +317,7 @@ class DBSpendUpdateWriter: entity_id=org_id, transaction_list=prisma_client.org_list_transactions, entity_type=Litellm_EntityType.ORGANIZATION, + prisma_client=prisma_client, ) except Exception as e: verbose_proxy_logger.info( From ba550e214705abd20411e4b2446a27a25c567be2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 19:52:39 -0700 Subject: [PATCH 178/244] test local spend accuracy --- litellm/proxy/proxy_config.yaml | 1 + .../local_test_spend_accuracy_tests.py | 279 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 tests/otel_tests/local_test_spend_accuracy_tests.py diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index a1be54421b..9ca63f18ea 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -12,3 +12,4 @@ litellm_settings: cache: true cache_params: type: redis + supported_call_types: [] diff --git a/tests/otel_tests/local_test_spend_accuracy_tests.py b/tests/otel_tests/local_test_spend_accuracy_tests.py new file mode 100644 index 0000000000..6d756219c7 --- /dev/null +++ b/tests/otel_tests/local_test_spend_accuracy_tests.py @@ -0,0 +1,279 @@ +import pytest +import asyncio +import aiohttp +import json +from httpx import AsyncClient +from typing import Any, Optional +import uuid + +""" +Tests to run + +Basic Tests: +1. Basic Spend Accuracy Test: + - 1 Request costs $0.037 + - Make 12 requests + - Expect the spend for each of the following to be 12 * $0.037 + Key: $0.444 (call /info endpoint for each object to validate) + Team: $0.444 + User: $0.444 + Org: $0.444 + End User: $0.444 + +2. Long term spend accuracy test (with 2 bursts of requests) + - 1 Request costs $0.037 + - Burst 1: 12 requests + - Burst 2: 22 requests + + - Expect the spend for each of the following to be (12 + 22) * $0.037 + Key: $1.296 + Team: $1.296 + User: $1.296 + Org: $1.296 + End User: $1.296 + +Additional Test Scenarios: + +3. Concurrent Request Accuracy Test: + - Make 20 concurrent requests + - Verify total spend is 20 * $0.037 + - Check for race conditions in spend tracking + +4. Error Case Test: + - Make 10 successful requests ($0.037 each) + - Make 5 failed requests + - Verify spend is only counted for successful requests (10 * $0.037) + +5. Mixed Request Type Test: + - Make different types of requests with varying costs + - Verify accurate total spend calculation +""" + + +async def create_organization(session, organization_alias: str): + """Helper function to create a new organization""" + url = "http://0.0.0.0:4002/organization/new" + headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} + data = {"organization_alias": organization_alias} + async with session.post(url, headers=headers, json=data) as response: + return await response.json() + + +async def create_team(session, org_id: str): + """Helper function to create a new team under an organization""" + url = "http://0.0.0.0:4002/team/new" + headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} + data = {"organization_id": org_id, "team_alias": f"test-team-{uuid.uuid4()}"} + async with session.post(url, headers=headers, json=data) as response: + return await response.json() + + +async def create_user(session, org_id: str): + """Helper function to create a new user""" + url = "http://0.0.0.0:4002/user/new" + headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} + data = {"user_name": f"test-user-{uuid.uuid4()}"} + async with session.post(url, headers=headers, json=data) as response: + return await response.json() + + +async def generate_key(session, user_id: str, team_id: str): + """Helper function to generate a key for a specific user and team""" + url = "http://0.0.0.0:4002/key/generate" + headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} + data = {"user_id": user_id, "team_id": team_id} + async with session.post(url, headers=headers, json=data) as response: + return await response.json() + + +async def chat_completion(session, key: str): + """Make a chat completion request""" + from openai import AsyncOpenAI + import uuid + + client = AsyncOpenAI(api_key=key, base_url="http://0.0.0.0:4002/v1") + + response = await client.chat.completions.create( + model="fake-openai-endpoint", + messages=[{"role": "user", "content": f"Test message {uuid.uuid4()}"}], + ) + return response + + +async def get_spend_info(session, entity_type: str, entity_id: str): + """Helper function to get spend information for an entity""" + url = f"http://0.0.0.0:4002/{entity_type}/info" + headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"} + if entity_type == "key": + data = {"key": entity_id} + else: + data = {f"{entity_type}_id": entity_id} + + async with session.get(url, headers=headers, params=data) as response: + return await response.json() + + +@pytest.mark.asyncio +async def test_basic_spend_accuracy(): + """ + Test basic spend accuracy across different entities: + 1. Create org, team, user, and key + 2. Make 12 requests at $0.037 each + 3. Verify spend accuracy for key, team, user, org, and end user + """ + SPEND_PER_REQUEST = 3.75 * 10**-5 + NUM_LLM_REQUESTS = 20 + expected_spend = NUM_LLM_REQUESTS * SPEND_PER_REQUEST # 12 requests at $0.037 each + + # Add tolerance constant at the top of the test + TOLERANCE = 1e-10 # Small number to account for floating-point precision + + async with aiohttp.ClientSession() as session: + # Create organization + org_response = await create_organization( + session=session, organization_alias=f"test-org-{uuid.uuid4()}" + ) + print("org_response: ", org_response) + org_id = org_response["organization_id"] + + # Create team under organization + team_response = await create_team(session, org_id) + print("team_response: ", team_response) + team_id = team_response["team_id"] + + # Create user + user_response = await create_user(session, org_id) + print("user_response: ", user_response) + user_id = user_response["user_id"] + + # Generate key + key_response = await generate_key(session, user_id, team_id) + print("key_response: ", key_response) + key = key_response["key"] + + # Make 12 requests + for _ in range(NUM_LLM_REQUESTS): + response = await chat_completion(session, key) + print("response: ", response) + + # wait 10 seconds for spend to be updated + await asyncio.sleep(15) + + # Get spend information for each entity + key_info = await get_spend_info(session, "key", key) + print("key_info: ", key_info) + team_info = await get_spend_info(session, "team", team_id) + print("team_info: ", team_info) + user_info = await get_spend_info(session, "user", user_id) + print("user_info: ", user_info) + org_info = await get_spend_info(session, "organization", org_id) + print("org_info: ", org_info) + + # Verify spend for each entity + assert ( + abs(key_info["info"]["spend"] - expected_spend) < TOLERANCE + ), f"Key spend {key_info['info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(user_info["user_info"]["spend"] - expected_spend) < TOLERANCE + ), f"User spend {user_info['info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(team_info["team_info"]["spend"] - expected_spend) < TOLERANCE + ), f"Team spend {team_info['team_info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(org_info["spend"] - expected_spend) < TOLERANCE + ), f"Organization spend {org_info['spend']} does not match expected {expected_spend}" + + +@pytest.mark.asyncio +async def test_long_term_spend_accuracy_with_bursts(): + """ + Test long-term spend accuracy with multiple bursts of requests: + 1. Create org, team, user, and key + 2. Burst 1: Make 12 requests + 3. Burst 2: Make 22 more requests + 4. Verify the total spend (34 requests) is tracked accurately across all entities + """ + SPEND_PER_REQUEST = 3.75 * 10**-5 # Cost per request + BURST_1_REQUESTS = 22 # Number of requests in first burst + BURST_2_REQUESTS = 12 # Number of requests in second burst + TOTAL_REQUESTS = BURST_1_REQUESTS + BURST_2_REQUESTS + expected_spend = TOTAL_REQUESTS * SPEND_PER_REQUEST + + # Tolerance for floating-point comparison + TOLERANCE = 1e-10 + + async with aiohttp.ClientSession() as session: + # Create organization + org_response = await create_organization( + session=session, organization_alias=f"test-org-{uuid.uuid4()}" + ) + print("org_response: ", org_response) + org_id = org_response["organization_id"] + + # Create team under organization + team_response = await create_team(session, org_id) + print("team_response: ", team_response) + team_id = team_response["team_id"] + + # Create user + user_response = await create_user(session, org_id) + print("user_response: ", user_response) + user_id = user_response["user_id"] + + # Generate key + key_response = await generate_key(session, user_id, team_id) + print("key_response: ", key_response) + key = key_response["key"] + + # First burst: 12 requests + print(f"Starting first burst of {BURST_1_REQUESTS} requests...") + for i in range(BURST_1_REQUESTS): + response = await chat_completion(session, key) + print(f"Burst 1 - Request {i+1}/{BURST_1_REQUESTS} completed") + + # Wait for spend to be updated + await asyncio.sleep(8) + + # Check intermediate spend + intermediate_key_info = await get_spend_info(session, "key", key) + print(f"After Burst 1 - Key spend: {intermediate_key_info['info']['spend']}") + + # Second burst: 22 requests + print(f"Starting second burst of {BURST_2_REQUESTS} requests...") + for i in range(BURST_2_REQUESTS): + response = await chat_completion(session, key) + print(f"Burst 2 - Request {i+1}/{BURST_2_REQUESTS} completed") + + # Wait for spend to be updated + await asyncio.sleep(8) + + # Get final spend information for each entity + key_info = await get_spend_info(session, "key", key) + team_info = await get_spend_info(session, "team", team_id) + user_info = await get_spend_info(session, "user", user_id) + org_info = await get_spend_info(session, "organization", org_id) + + print(f"Final key spend: {key_info['info']['spend']}") + print(f"Final team spend: {team_info['team_info']['spend']}") + print(f"Final user spend: {user_info['user_info']['spend']}") + print(f"Final org spend: {org_info['spend']}") + + # Verify total spend for each entity + assert ( + abs(key_info["info"]["spend"] - expected_spend) < TOLERANCE + ), f"Key spend {key_info['info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(user_info["user_info"]["spend"] - expected_spend) < TOLERANCE + ), f"User spend {user_info['user_info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(team_info["team_info"]["spend"] - expected_spend) < TOLERANCE + ), f"Team spend {team_info['team_info']['spend']} does not match expected {expected_spend}" + + assert ( + abs(org_info["spend"] - expected_spend) < TOLERANCE + ), f"Organization spend {org_info['spend']} does not match expected {expected_spend}" From 69d573468575478811cfe3680782141753dab189 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 20:10:58 -0700 Subject: [PATCH 179/244] fix - locking in memory leads to failing tests --- litellm/proxy/db/db_spend_update_writer.py | 7 ++-- litellm/proxy/db/redis_update_buffer.py | 47 ++++++++++------------ litellm/proxy/proxy_config.yaml | 8 ---- litellm/proxy/utils.py | 1 - 4 files changed, 25 insertions(+), 38 deletions(-) diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 72692339c8..f46b03b57a 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -172,10 +172,9 @@ class DBSpendUpdateWriter: f"track_cost_callback: {entity_type.value}_id is None. Not tracking spend for {entity_type.value}" ) return False - async with prisma_client.in_memory_transaction_lock: - transaction_list[entity_id] = response_cost + transaction_list.get( - entity_id, 0 - ) + transaction_list[entity_id] = response_cost + transaction_list.get( + entity_id, 0 + ) return True except Exception as e: diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 3d27e41d15..f842fc85d2 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -80,35 +80,32 @@ class RedisUpdateBuffer: "redis_cache is None, skipping store_in_memory_spend_updates_in_redis" ) return - async with prisma_client.in_memory_transaction_lock: - db_spend_update_transactions: DBSpendUpdateTransactions = ( - DBSpendUpdateTransactions( - user_list_transactions=prisma_client.user_list_transactions, - end_user_list_transactions=prisma_client.end_user_list_transactions, - key_list_transactions=prisma_client.key_list_transactions, - team_list_transactions=prisma_client.team_list_transactions, - team_member_list_transactions=prisma_client.team_member_list_transactions, - org_list_transactions=prisma_client.org_list_transactions, - ) + db_spend_update_transactions: DBSpendUpdateTransactions = ( + DBSpendUpdateTransactions( + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, ) + ) - # only store in redis if there are any updates to commit - if ( - self._number_of_transactions_to_store_in_redis( - db_spend_update_transactions - ) - == 0 - ): - return + # only store in redis if there are any updates to commit + if ( + self._number_of_transactions_to_store_in_redis(db_spend_update_transactions) + == 0 + ): + return - list_of_transactions = [safe_dumps(db_spend_update_transactions)] - await self.redis_cache.async_rpush( - key=REDIS_UPDATE_BUFFER_KEY, - values=list_of_transactions, - ) + list_of_transactions = [safe_dumps(db_spend_update_transactions)] + await self.redis_cache.async_rpush( + key=REDIS_UPDATE_BUFFER_KEY, + values=list_of_transactions, + ) - # clear the in-memory spend updates - RedisUpdateBuffer._clear_all_in_memory_spend_updates(prisma_client) + # clear the in-memory spend updates + RedisUpdateBuffer._clear_all_in_memory_spend_updates(prisma_client) @staticmethod def _number_of_transactions_to_store_in_redis( diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 9ca63f18ea..106003f996 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -5,11 +5,3 @@ model_list: api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ -general_settings: - use_redis_transaction_buffer: True - -litellm_settings: - cache: true - cache_params: - type: redis - supported_call_types: [] diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 66dd224dff..d0d4ea9b4e 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1121,7 +1121,6 @@ class PrismaClient: self.iam_token_db_auth: Optional[bool] = str_to_bool( os.getenv("IAM_TOKEN_DB_AUTH") ) - self.in_memory_transaction_lock = asyncio.Lock() verbose_proxy_logger.debug("Creating Prisma Client..") try: from prisma import Prisma # type: ignore From 4879e1ecf0eee6fad97d9ac1fe2870e923b3733e Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 20:24:28 -0700 Subject: [PATCH 180/244] fix(openrouter/chat/transformation.py): raise informative message for openrouter key error (#9626) Related Issue: https://github.com/Aider-AI/aider/issues/3550#issuecomment-2763052355 --- litellm/llms/openrouter/chat/transformation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/litellm/llms/openrouter/chat/transformation.py b/litellm/llms/openrouter/chat/transformation.py index 4b95ec87cf..ab4d3c52b9 100644 --- a/litellm/llms/openrouter/chat/transformation.py +++ b/litellm/llms/openrouter/chat/transformation.py @@ -84,5 +84,11 @@ class OpenRouterChatCompletionStreamingHandler(BaseModelResponseIterator): model=chunk["model"], choices=new_choices, ) + except KeyError as e: + raise OpenRouterException( + message=f"KeyError: {e}, Got unexpected response from OpenRouter: {chunk}", + status_code=400, + headers={"Content-Type": "application/json"}, + ) except Exception as e: raise e From 4fb9d27a35459ede5e22056c99a513138f27713c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 20:55:43 -0700 Subject: [PATCH 181/244] use constants for redis buffer in DB --- litellm/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/constants.py b/litellm/constants.py index ea8bc76ac4..e224b3d33e 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -18,6 +18,8 @@ DEFAULT_IMAGE_WIDTH = 300 DEFAULT_IMAGE_HEIGHT = 300 MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB = 1024 # 1MB = 1024KB SINGLE_DEPLOYMENT_TRAFFIC_FAILURE_THRESHOLD = 1000 # Minimum number of requests to consider "reasonable traffic". Used for single-deployment cooldown logic. +REDIS_UPDATE_BUFFER_KEY = "litellm_spend_update_buffer" +MAX_REDIS_BUFFER_DEQUEUE_COUNT = 100 #### RELIABILITY #### REPEATED_STREAMING_CHUNK_LIMIT = 100 # catch if model starts looping the same chunk while streaming. Uses high default to prevent false positives. #### Networking settings #### From 29d6968f58d546244b1744ea1a42fa5d9ab4a798 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 20:57:28 -0700 Subject: [PATCH 182/244] fix linting --- litellm/proxy/db/redis_update_buffer.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index f842fc85d2..0b090db56a 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -5,10 +5,11 @@ This is to prevent deadlocks and improve reliability """ import json -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union from litellm._logging import verbose_proxy_logger from litellm.caching import RedisCache +from litellm.constants import MAX_REDIS_BUFFER_DEQUEUE_COUNT, REDIS_UPDATE_BUFFER_KEY from litellm.litellm_core_utils.safe_json_dumps import safe_dumps from litellm.proxy._types import DBSpendUpdateTransactions from litellm.secret_managers.main import str_to_bool @@ -18,9 +19,6 @@ if TYPE_CHECKING: else: PrismaClient = Any -REDIS_UPDATE_BUFFER_KEY = "litellm_spend_update_buffer" -MAX_REDIS_BUFFER_DEQUEUE_COUNT = 100 - class RedisUpdateBuffer: """ @@ -245,9 +243,9 @@ class RedisUpdateBuffer: # Process each field type for field in transaction_fields: if transaction.get(field): - for entity_id, amount in transaction[field].items(): - combined_transaction[field][entity_id] = ( - combined_transaction[field].get(entity_id, 0) + amount + for entity_id, amount in transaction[field].items(): # type: ignore + combined_transaction[field][entity_id] = ( # type: ignore + combined_transaction[field].get(entity_id, 0) + amount # type: ignore ) return combined_transaction From c95fb6c692d1ee27f3c6e64eabefa83ab3722b45 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 20:59:28 -0700 Subject: [PATCH 183/244] MAX_REDIS_BUFFER_DEQUEUE_COUNT --- litellm/proxy/db/redis_update_buffer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index 0b090db56a..f77c839aaf 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -5,7 +5,7 @@ This is to prevent deadlocks and improve reliability """ import json -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from litellm._logging import verbose_proxy_logger from litellm.caching import RedisCache From 7c93b19b001e4d7b6c701c92965071c4333395d8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Mar 2025 21:47:26 -0700 Subject: [PATCH 184/244] add migration.sql --- .../migration.sql | 407 ++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 migrations/20250329044652_add_cron_job_table/migration.sql diff --git a/migrations/20250329044652_add_cron_job_table/migration.sql b/migrations/20250329044652_add_cron_job_table/migration.sql new file mode 100644 index 0000000000..edfa99abd5 --- /dev/null +++ b/migrations/20250329044652_add_cron_job_table/migration.sql @@ -0,0 +1,407 @@ +-- CreateEnum +CREATE TYPE "JobStatus" AS ENUM ('ACTIVE', 'INACTIVE'); + +-- CreateTable +CREATE TABLE "LiteLLM_BudgetTable" ( + "budget_id" TEXT NOT NULL, + "max_budget" DOUBLE PRECISION, + "soft_budget" DOUBLE PRECISION, + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "model_max_budget" JSONB, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_BudgetTable_pkey" PRIMARY KEY ("budget_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_CredentialsTable" ( + "credential_id" TEXT NOT NULL, + "credential_name" TEXT NOT NULL, + "credential_values" JSONB NOT NULL, + "credential_info" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_CredentialsTable_pkey" PRIMARY KEY ("credential_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ProxyModelTable" ( + "model_id" TEXT NOT NULL, + "model_name" TEXT NOT NULL, + "litellm_params" JSONB NOT NULL, + "model_info" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_ProxyModelTable_pkey" PRIMARY KEY ("model_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_OrganizationTable" ( + "organization_id" TEXT NOT NULL, + "organization_alias" TEXT NOT NULL, + "budget_id" TEXT NOT NULL, + "metadata" JSONB NOT NULL DEFAULT '{}', + "models" TEXT[], + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "model_spend" JSONB NOT NULL DEFAULT '{}', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_OrganizationTable_pkey" PRIMARY KEY ("organization_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ModelTable" ( + "id" SERIAL NOT NULL, + "aliases" JSONB, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_ModelTable_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_TeamTable" ( + "team_id" TEXT NOT NULL, + "team_alias" TEXT, + "organization_id" TEXT, + "admins" TEXT[], + "members" TEXT[], + "members_with_roles" JSONB NOT NULL DEFAULT '{}', + "metadata" JSONB NOT NULL DEFAULT '{}', + "max_budget" DOUBLE PRECISION, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "models" TEXT[], + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "blocked" BOOLEAN NOT NULL DEFAULT false, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "model_id" INTEGER, + + CONSTRAINT "LiteLLM_TeamTable_pkey" PRIMARY KEY ("team_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_UserTable" ( + "user_id" TEXT NOT NULL, + "user_alias" TEXT, + "team_id" TEXT, + "sso_user_id" TEXT, + "organization_id" TEXT, + "password" TEXT, + "teams" TEXT[] DEFAULT ARRAY[]::TEXT[], + "user_role" TEXT, + "max_budget" DOUBLE PRECISION, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "user_email" TEXT, + "models" TEXT[], + "metadata" JSONB NOT NULL DEFAULT '{}', + "max_parallel_requests" INTEGER, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "LiteLLM_UserTable_pkey" PRIMARY KEY ("user_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_VerificationToken" ( + "token" TEXT NOT NULL, + "key_name" TEXT, + "key_alias" TEXT, + "soft_budget_cooldown" BOOLEAN NOT NULL DEFAULT false, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "expires" TIMESTAMP(3), + "models" TEXT[], + "aliases" JSONB NOT NULL DEFAULT '{}', + "config" JSONB NOT NULL DEFAULT '{}', + "user_id" TEXT, + "team_id" TEXT, + "permissions" JSONB NOT NULL DEFAULT '{}', + "max_parallel_requests" INTEGER, + "metadata" JSONB NOT NULL DEFAULT '{}', + "blocked" BOOLEAN, + "tpm_limit" BIGINT, + "rpm_limit" BIGINT, + "max_budget" DOUBLE PRECISION, + "budget_duration" TEXT, + "budget_reset_at" TIMESTAMP(3), + "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], + "model_spend" JSONB NOT NULL DEFAULT '{}', + "model_max_budget" JSONB NOT NULL DEFAULT '{}', + "budget_id" TEXT, + "organization_id" TEXT, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "created_by" TEXT, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_by" TEXT, + + CONSTRAINT "LiteLLM_VerificationToken_pkey" PRIMARY KEY ("token") +); + +-- CreateTable +CREATE TABLE "LiteLLM_EndUserTable" ( + "user_id" TEXT NOT NULL, + "alias" TEXT, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "allowed_model_region" TEXT, + "default_model" TEXT, + "budget_id" TEXT, + "blocked" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "LiteLLM_EndUserTable_pkey" PRIMARY KEY ("user_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_Config" ( + "param_name" TEXT NOT NULL, + "param_value" JSONB, + + CONSTRAINT "LiteLLM_Config_pkey" PRIMARY KEY ("param_name") +); + +-- CreateTable +CREATE TABLE "LiteLLM_SpendLogs" ( + "request_id" TEXT NOT NULL, + "call_type" TEXT NOT NULL, + "api_key" TEXT NOT NULL DEFAULT '', + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "total_tokens" INTEGER NOT NULL DEFAULT 0, + "prompt_tokens" INTEGER NOT NULL DEFAULT 0, + "completion_tokens" INTEGER NOT NULL DEFAULT 0, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "completionStartTime" TIMESTAMP(3), + "model" TEXT NOT NULL DEFAULT '', + "model_id" TEXT DEFAULT '', + "model_group" TEXT DEFAULT '', + "custom_llm_provider" TEXT DEFAULT '', + "api_base" TEXT DEFAULT '', + "user" TEXT DEFAULT '', + "metadata" JSONB DEFAULT '{}', + "cache_hit" TEXT DEFAULT '', + "cache_key" TEXT DEFAULT '', + "request_tags" JSONB DEFAULT '[]', + "team_id" TEXT, + "end_user" TEXT, + "requester_ip_address" TEXT, + "messages" JSONB DEFAULT '{}', + "response" JSONB DEFAULT '{}', + + CONSTRAINT "LiteLLM_SpendLogs_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_ErrorLogs" ( + "request_id" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "api_base" TEXT NOT NULL DEFAULT '', + "model_group" TEXT NOT NULL DEFAULT '', + "litellm_model_name" TEXT NOT NULL DEFAULT '', + "model_id" TEXT NOT NULL DEFAULT '', + "request_kwargs" JSONB NOT NULL DEFAULT '{}', + "exception_type" TEXT NOT NULL DEFAULT '', + "exception_string" TEXT NOT NULL DEFAULT '', + "status_code" TEXT NOT NULL DEFAULT '', + + CONSTRAINT "LiteLLM_ErrorLogs_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_UserNotifications" ( + "request_id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "models" TEXT[], + "justification" TEXT NOT NULL, + "status" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_UserNotifications_pkey" PRIMARY KEY ("request_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_TeamMembership" ( + "user_id" TEXT NOT NULL, + "team_id" TEXT NOT NULL, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "budget_id" TEXT, + + CONSTRAINT "LiteLLM_TeamMembership_pkey" PRIMARY KEY ("user_id","team_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_OrganizationMembership" ( + "user_id" TEXT NOT NULL, + "organization_id" TEXT NOT NULL, + "user_role" TEXT, + "spend" DOUBLE PRECISION DEFAULT 0.0, + "budget_id" TEXT, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "LiteLLM_OrganizationMembership_pkey" PRIMARY KEY ("user_id","organization_id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_InvitationLink" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "is_accepted" BOOLEAN NOT NULL DEFAULT false, + "accepted_at" TIMESTAMP(3), + "expires_at" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL, + "created_by" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL, + "updated_by" TEXT NOT NULL, + + CONSTRAINT "LiteLLM_InvitationLink_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_AuditLog" ( + "id" TEXT NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "changed_by" TEXT NOT NULL DEFAULT '', + "changed_by_api_key" TEXT NOT NULL DEFAULT '', + "action" TEXT NOT NULL, + "table_name" TEXT NOT NULL, + "object_id" TEXT NOT NULL, + "before_value" JSONB, + "updated_values" JSONB, + + CONSTRAINT "LiteLLM_AuditLog_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_DailyUserSpend" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "date" TEXT NOT NULL, + "api_key" TEXT NOT NULL, + "model" TEXT NOT NULL, + "model_group" TEXT, + "custom_llm_provider" TEXT, + "prompt_tokens" INTEGER NOT NULL DEFAULT 0, + "completion_tokens" INTEGER NOT NULL DEFAULT 0, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "api_requests" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_DailyUserSpend_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LiteLLM_CronJob" ( + "cronjob_id" TEXT NOT NULL, + "pod_id" TEXT NOT NULL, + "status" "JobStatus" NOT NULL DEFAULT 'INACTIVE', + "last_updated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ttl" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_CronJob_pkey" PRIMARY KEY ("cronjob_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_CredentialsTable_credential_name_key" ON "LiteLLM_CredentialsTable"("credential_name"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_TeamTable_model_id_key" ON "LiteLLM_TeamTable"("model_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_UserTable_sso_user_id_key" ON "LiteLLM_UserTable"("sso_user_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_SpendLogs_startTime_idx" ON "LiteLLM_SpendLogs"("startTime"); + +-- CreateIndex +CREATE INDEX "LiteLLM_SpendLogs_end_user_idx" ON "LiteLLM_SpendLogs"("end_user"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_OrganizationMembership_user_id_organization_id_key" ON "LiteLLM_OrganizationMembership"("user_id", "organization_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_date_idx" ON "LiteLLM_DailyUserSpend"("date"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_user_id_idx" ON "LiteLLM_DailyUserSpend"("user_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_api_key_idx" ON "LiteLLM_DailyUserSpend"("api_key"); + +-- CreateIndex +CREATE INDEX "LiteLLM_DailyUserSpend_model_idx" ON "LiteLLM_DailyUserSpend"("model"); + +-- CreateIndex +CREATE UNIQUE INDEX "LiteLLM_DailyUserSpend_user_id_date_api_key_model_custom_ll_key" ON "LiteLLM_DailyUserSpend"("user_id", "date", "api_key", "model", "custom_llm_provider"); + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationTable" ADD CONSTRAINT "LiteLLM_OrganizationTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_model_id_fkey" FOREIGN KEY ("model_id") REFERENCES "LiteLLM_ModelTable"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_UserTable" ADD CONSTRAINT "LiteLLM_UserTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_EndUserTable" ADD CONSTRAINT "LiteLLM_EndUserTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_TeamMembership" ADD CONSTRAINT "LiteLLM_TeamMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; From 5ac61a75725b8bf3679f392c775405f135044967 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Sat, 29 Mar 2025 00:23:09 -0700 Subject: [PATCH 185/244] Add bedrock latency optimized inference support (#9623) * fix(converse_transformation.py): add performanceConfig param support on bedrock Closes https://github.com/BerriAI/litellm/issues/7606 * fix(converse_transformation.py): refactor to use more flexible single getter for params which are separate config blocks * test(test_main.py): add e2e mock test for bedrock performance config * build(model_prices_and_context_window.json): add versioned multimodal embedding * refactor(multimodal_embeddings/): migrate to config pattern * feat(vertex_ai/multimodalembeddings): calculate usage for multimodal embedding calls Enables cost calculation for multimodal embeddings * feat(vertex_ai/multimodalembeddings): get usage object for embedding calls ensures accurate cost tracking for vertexai multimodal embedding calls * fix(embedding_handler.py): remove unused imports * fix: fix linting errors * fix: handle response api usage calculation * test(test_vertex_ai_multimodal_embedding_transformation.py): update tests * test: mark flaky test * feat(vertex_ai/multimodal_embeddings/transformation.py): support text+image+video input * docs(vertex.md): document sending text + image to vertex multimodal embeddings * test: remove incorrect file * fix(multimodal_embeddings/transformation.py): fix linting error * style: remove unused import --- docs/my-website/docs/providers/vertex.md | 116 ++++++- litellm/cost_calculator.py | 38 ++- .../litellm_core_utils/llm_cost_calc/utils.py | 91 +++++- .../bedrock/chat/converse_transformation.py | 23 +- litellm/llms/vertex_ai/cost_calculator.py | 1 + .../embedding_handler.py | 206 +++--------- .../multimodal_embeddings/transformation.py | 297 ++++++++++++++++++ litellm/main.py | 1 + ...odel_prices_and_context_window_backup.json | 17 + litellm/responses/utils.py | 14 +- litellm/types/llms/bedrock.py | 9 +- litellm/types/llms/vertex_ai.py | 12 +- litellm/types/utils.py | 18 ++ model_prices_and_context_window.json | 17 + .../test_embedding_handler.py | 43 --- ..._ai_multimodal_embedding_transformation.py | 78 +++++ tests/litellm/test_main.py | 20 ++ tests/llm_translation/test_openai.py | 1 + .../test_amazing_vertex_completion.py | 49 +++ 19 files changed, 806 insertions(+), 245 deletions(-) create mode 100644 litellm/llms/vertex_ai/multimodal_embeddings/transformation.py delete mode 100644 tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py create mode 100644 tests/litellm/llms/vertex_ai/multimodal_embeddings/test_vertex_ai_multimodal_embedding_transformation.py diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index f2489de769..103cc07ef5 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -2080,7 +2080,12 @@ print(response) ## **Multi-Modal Embeddings** -Usage + +Known Limitations: +- Only supports 1 image / video / image per request +- Only supports GCS or base64 encoded images / videos + +### Usage @@ -2296,6 +2301,115 @@ print(f"Text Embedding: {embeddings.text_embedding}") +### Text + Image + Video Embeddings + + + + +Text + Image + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"] # will be sent as a gcs image +) +``` + +Text + Video + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image +) +``` + +Image + Video + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image +) +``` + + + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: multimodalembedding@001 + litellm_params: + model: vertex_ai/multimodalembedding@001 + vertex_project: "adroit-crow-413218" + vertex_location: "us-central1" + vertex_credentials: adroit-crow-413218-a956eef1a2a8.json + +litellm_settings: + drop_params: True +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request use OpenAI Python SDK, Langchain Python SDK + + +Text + Image + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"], +) + +print(response) +``` + +Text + Video +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"], +) + +print(response) +``` + +Image + Video +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"], +) + +print(response) +``` + + + + + ## **Image Generation Models** Usage diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 7b95c45ac7..f343591443 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -2,7 +2,7 @@ ## File for 'response_cost' calculation in Logging import time from functools import lru_cache -from typing import Any, List, Literal, Optional, Tuple, Union +from typing import Any, List, Literal, Optional, Tuple, Union, cast from pydantic import BaseModel @@ -462,13 +462,36 @@ def _model_contains_known_llm_provider(model: str) -> bool: def _get_usage_object( completion_response: Any, ) -> Optional[Usage]: - usage_obj: Optional[Usage] = None - if completion_response is not None and isinstance( - completion_response, ModelResponse - ): - usage_obj = completion_response.get("usage") + usage_obj = cast( + Union[Usage, ResponseAPIUsage, dict, BaseModel], + ( + completion_response.get("usage") + if isinstance(completion_response, dict) + else getattr(completion_response, "get", lambda x: None)("usage") + ), + ) - return usage_obj + if usage_obj is None: + return None + if isinstance(usage_obj, Usage): + return usage_obj + elif ( + usage_obj is not None + and (isinstance(usage_obj, dict) or isinstance(usage_obj, ResponseAPIUsage)) + and ResponseAPILoggingUtils._is_response_api_usage(usage_obj) + ): + return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + usage_obj + ) + elif isinstance(usage_obj, dict): + return Usage(**usage_obj) + elif isinstance(usage_obj, BaseModel): + return Usage(**usage_obj.model_dump()) + else: + verbose_logger.debug( + f"Unknown usage object type: {type(usage_obj)}, usage_obj: {usage_obj}" + ) + return None def _is_known_usage_objects(usage_obj): @@ -662,6 +685,7 @@ def completion_cost( # noqa: PLR0915 elif len(prompt) > 0: prompt_tokens = token_counter(model=model, text=prompt) completion_tokens = token_counter(model=model, text=completion) + if model is None: raise ValueError( f"Model is None and does not exist in passed completion_response. Passed completion_response={completion_response}, model={model}" diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 6b778344d7..caccbc4bac 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -121,6 +121,31 @@ def _get_completion_token_base_cost(model_info: ModelInfo, usage: Usage) -> floa return model_info["output_cost_per_token"] +def calculate_cost_component( + model_info: ModelInfo, cost_key: str, usage_value: Optional[float] +) -> float: + """ + Generic cost calculator for any usage component + + Args: + model_info: Dictionary containing cost information + cost_key: The key for the cost multiplier in model_info (e.g., 'input_cost_per_audio_token') + usage_value: The actual usage value (e.g., number of tokens, characters, seconds) + + Returns: + float: The calculated cost + """ + cost_per_unit = model_info.get(cost_key) + if ( + cost_per_unit is not None + and isinstance(cost_per_unit, float) + and usage_value is not None + and usage_value > 0 + ): + return float(usage_value) * cost_per_unit + return 0.0 + + def generic_cost_per_token( model: str, usage: Usage, custom_llm_provider: str ) -> Tuple[float, float]: @@ -136,6 +161,7 @@ def generic_cost_per_token( Returns: Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd """ + ## GET MODEL INFO model_info = get_model_info(model=model, custom_llm_provider=custom_llm_provider) @@ -146,6 +172,9 @@ def generic_cost_per_token( text_tokens = usage.prompt_tokens cache_hit_tokens = 0 audio_tokens = 0 + character_count = 0 + image_count = 0 + video_length_seconds = 0 if usage.prompt_tokens_details: cache_hit_tokens = ( cast( @@ -163,6 +192,24 @@ def generic_cost_per_token( cast(Optional[int], getattr(usage.prompt_tokens_details, "audio_tokens", 0)) or 0 ) + character_count = ( + cast( + Optional[int], + getattr(usage.prompt_tokens_details, "character_count", 0), + ) + or 0 + ) + image_count = ( + cast(Optional[int], getattr(usage.prompt_tokens_details, "image_count", 0)) + or 0 + ) + video_length_seconds = ( + cast( + Optional[int], + getattr(usage.prompt_tokens_details, "video_length_seconds", 0), + ) + or 0 + ) ## EDGE CASE - text tokens not set inside PromptTokensDetails if text_tokens == 0: @@ -172,28 +219,38 @@ def generic_cost_per_token( prompt_cost = float(text_tokens) * prompt_base_cost - _cache_read_input_token_cost = model_info.get("cache_read_input_token_cost") - ### CACHE READ COST - if ( - _cache_read_input_token_cost is not None - and cache_hit_tokens is not None - and cache_hit_tokens > 0 - ): - prompt_cost += float(cache_hit_tokens) * _cache_read_input_token_cost + prompt_cost += calculate_cost_component( + model_info, "cache_read_input_token_cost", cache_hit_tokens + ) ### AUDIO COST - - audio_token_cost = model_info.get("input_cost_per_audio_token") - if audio_token_cost is not None and audio_tokens is not None and audio_tokens > 0: - prompt_cost += float(audio_tokens) * audio_token_cost + prompt_cost += calculate_cost_component( + model_info, "input_cost_per_audio_token", audio_tokens + ) ### CACHE WRITING COST - _cache_creation_input_token_cost = model_info.get("cache_creation_input_token_cost") - if _cache_creation_input_token_cost is not None: - prompt_cost += ( - float(usage._cache_creation_input_tokens) * _cache_creation_input_token_cost - ) + prompt_cost += calculate_cost_component( + model_info, + "cache_creation_input_token_cost", + usage._cache_creation_input_tokens, + ) + + ### CHARACTER COST + + prompt_cost += calculate_cost_component( + model_info, "input_cost_per_character", character_count + ) + + ### IMAGE COUNT COST + prompt_cost += calculate_cost_component( + model_info, "input_cost_per_image", image_count + ) + + ### VIDEO LENGTH COST + prompt_cost += calculate_cost_component( + model_info, "input_cost_per_video_per_second", video_length_seconds + ) ## CALCULATE OUTPUT COST completion_base_cost = _get_completion_token_base_cost( diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index b63b76a8ee..ced9c469b3 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -66,6 +66,13 @@ class AmazonConverseConfig(BaseConfig): def custom_llm_provider(self) -> Optional[str]: return "bedrock_converse" + @classmethod + def get_config_blocks(cls) -> dict: + return { + "guardrailConfig": GuardrailConfigBlock, + "performanceConfig": PerformanceConfigBlock, + } + @classmethod def get_config(cls): return { @@ -394,7 +401,6 @@ class AmazonConverseConfig(BaseConfig): optional_params: dict, messages: Optional[List[AllMessageValues]] = None, ) -> CommonRequestObject: - ## VALIDATE REQUEST """ Bedrock doesn't support tool calling without `tools=` param specified. @@ -420,11 +426,11 @@ class AmazonConverseConfig(BaseConfig): AmazonConverseConfig.__annotations__.keys() ) + ["top_k"] supported_tool_call_params = ["tools", "tool_choice"] - supported_guardrail_params = ["guardrailConfig"] + supported_config_params = list(self.get_config_blocks().keys()) total_supported_params = ( supported_converse_params + supported_tool_call_params - + supported_guardrail_params + + supported_config_params ) inference_params.pop("json_mode", None) # used for handling json_schema @@ -463,12 +469,11 @@ class AmazonConverseConfig(BaseConfig): ), } - # Guardrail Config - guardrail_config: Optional[GuardrailConfigBlock] = None - request_guardrails_config = inference_params.pop("guardrailConfig", None) - if request_guardrails_config is not None: - guardrail_config = GuardrailConfigBlock(**request_guardrails_config) - data["guardrailConfig"] = guardrail_config + # Handle all config blocks + for config_name, config_class in self.get_config_blocks().items(): + config_value = inference_params.pop(config_name, None) + if config_value is not None: + data[config_name] = config_class(**config_value) # type: ignore # Tool Config if bedrock_tool_config is not None: diff --git a/litellm/llms/vertex_ai/cost_calculator.py b/litellm/llms/vertex_ai/cost_calculator.py index 54efc62589..119ba2b036 100644 --- a/litellm/llms/vertex_ai/cost_calculator.py +++ b/litellm/llms/vertex_ai/cost_calculator.py @@ -239,6 +239,7 @@ def cost_per_token( Raises: Exception if model requires >128k pricing, but model cost not mapped """ + ## GET MODEL INFO model_info = litellm.get_model_info( model=model, custom_llm_provider=custom_llm_provider diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py index cd3de6e526..34879ae9ac 100644 --- a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py @@ -1,5 +1,5 @@ import json -from typing import List, Literal, Optional, Union +from typing import Literal, Optional, Union import httpx @@ -14,15 +14,11 @@ from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( VertexAIError, VertexLLM, ) -from litellm.types.llms.vertex_ai import ( - Instance, - InstanceImage, - InstanceVideo, - MultimodalPredictions, - VertexMultimodalEmbeddingRequest, -) -from litellm.types.utils import Embedding, EmbeddingResponse -from litellm.utils import is_base64_encoded +from litellm.types.utils import EmbeddingResponse + +from .transformation import VertexAIMultimodalEmbeddingConfig + +vertex_multimodal_embedding_handler = VertexAIMultimodalEmbeddingConfig() class VertexMultimodalEmbedding(VertexLLM): @@ -41,9 +37,11 @@ class VertexMultimodalEmbedding(VertexLLM): model_response: EmbeddingResponse, custom_llm_provider: Literal["gemini", "vertex_ai"], optional_params: dict, + litellm_params: dict, logging_obj: LiteLLMLoggingObj, api_key: Optional[str] = None, api_base: Optional[str] = None, + headers: dict = {}, encoding=None, vertex_project=None, vertex_location=None, @@ -86,31 +84,18 @@ class VertexMultimodalEmbedding(VertexLLM): else: sync_handler = client # type: ignore - optional_params = optional_params or {} + request_data = vertex_multimodal_embedding_handler.transform_embedding_request( + model, input, optional_params, headers + ) - request_data = VertexMultimodalEmbeddingRequest() - - if "instances" in optional_params: - request_data["instances"] = optional_params["instances"] - elif isinstance(input, list): - vertex_instances: List[Instance] = self.process_openai_embedding_input( - _input=input - ) - request_data["instances"] = vertex_instances - - else: - # construct instances - vertex_request_instance = Instance(**optional_params) - - if isinstance(input, str): - vertex_request_instance = self._process_input_element(input) - - request_data["instances"] = [vertex_request_instance] - - headers = { - "Content-Type": "application/json; charset=utf-8", - "Authorization": f"Bearer {auth_header}", - } + headers = vertex_multimodal_embedding_handler.validate_environment( + headers=headers, + model=model, + messages=[], + optional_params=optional_params, + api_key=auth_header, + api_base=api_base, + ) ## LOGGING logging_obj.pre_call( @@ -132,6 +117,10 @@ class VertexMultimodalEmbedding(VertexLLM): headers=headers, client=client, model_response=model_response, + optional_params=optional_params, + litellm_params=litellm_params, + logging_obj=logging_obj, + api_key=api_key, ) response = sync_handler.post( @@ -140,34 +129,30 @@ class VertexMultimodalEmbedding(VertexLLM): data=json.dumps(request_data), ) - if response.status_code != 200: - raise Exception(f"Error: {response.status_code} {response.text}") - - _json_response = response.json() - if "predictions" not in _json_response: - raise litellm.InternalServerError( - message=f"embedding response does not contain 'predictions', got {_json_response}", - llm_provider="vertex_ai", - model=model, - ) - _predictions = _json_response["predictions"] - vertex_predictions = MultimodalPredictions(predictions=_predictions) - model_response.data = self.transform_embedding_response_to_openai( - predictions=vertex_predictions + return vertex_multimodal_embedding_handler.transform_embedding_response( + model=model, + raw_response=response, + model_response=model_response, + logging_obj=logging_obj, + api_key=api_key, + request_data=request_data, + optional_params=optional_params, + litellm_params=litellm_params, ) - model_response.model = model - - return model_response async def async_multimodal_embedding( self, model: str, api_base: str, - data: VertexMultimodalEmbeddingRequest, + optional_params: dict, + litellm_params: dict, + data: dict, model_response: litellm.EmbeddingResponse, timeout: Optional[Union[float, httpx.Timeout]], + logging_obj: LiteLLMLoggingObj, headers={}, client: Optional[AsyncHTTPHandler] = None, + api_key: Optional[str] = None, ) -> litellm.EmbeddingResponse: if client is None: _params = {} @@ -191,112 +176,13 @@ class VertexMultimodalEmbedding(VertexLLM): except httpx.TimeoutException: raise VertexAIError(status_code=408, message="Timeout error occurred.") - _json_response = response.json() - if "predictions" not in _json_response: - raise litellm.InternalServerError( - message=f"embedding response does not contain 'predictions', got {_json_response}", - llm_provider="vertex_ai", - model=model, - ) - _predictions = _json_response["predictions"] - - vertex_predictions = MultimodalPredictions(predictions=_predictions) - model_response.data = self.transform_embedding_response_to_openai( - predictions=vertex_predictions + return vertex_multimodal_embedding_handler.transform_embedding_response( + model=model, + raw_response=response, + model_response=model_response, + logging_obj=logging_obj, + api_key=api_key, + request_data=data, + optional_params=optional_params, + litellm_params=litellm_params, ) - model_response.model = model - - return model_response - - def _process_input_element(self, input_element: str) -> Instance: - """ - Process the input element for multimodal embedding requests. checks if the if the input is gcs uri, base64 encoded image or plain text. - - Args: - input_element (str): The input element to process. - - Returns: - Dict[str, Any]: A dictionary representing the processed input element. - """ - if len(input_element) == 0: - return Instance(text=input_element) - elif "gs://" in input_element: - if "mp4" in input_element: - return Instance(video=InstanceVideo(gcsUri=input_element)) - else: - return Instance(image=InstanceImage(gcsUri=input_element)) - elif is_base64_encoded(s=input_element): - return Instance( - image=InstanceImage( - bytesBase64Encoded=( - input_element.split(",")[1] - if "," in input_element - else input_element - ) - ) - ) - else: - return Instance(text=input_element) - - def process_openai_embedding_input( - self, _input: Union[list, str] - ) -> List[Instance]: - """ - Process the input for multimodal embedding requests. - - Args: - _input (Union[list, str]): The input data to process. - - Returns: - List[Instance]: A list of processed VertexAI Instance objects. - """ - - _input_list = None - if not isinstance(_input, list): - _input_list = [_input] - else: - _input_list = _input - - processed_instances = [] - for element in _input_list: - if isinstance(element, str): - instance = Instance(**self._process_input_element(element)) - elif isinstance(element, dict): - instance = Instance(**element) - else: - raise ValueError(f"Unsupported input type: {type(element)}") - processed_instances.append(instance) - - return processed_instances - - def transform_embedding_response_to_openai( - self, predictions: MultimodalPredictions - ) -> List[Embedding]: - - openai_embeddings: List[Embedding] = [] - if "predictions" in predictions: - for idx, _prediction in enumerate(predictions["predictions"]): - if _prediction: - if "textEmbedding" in _prediction: - openai_embedding_object = Embedding( - embedding=_prediction["textEmbedding"], - index=idx, - object="embedding", - ) - openai_embeddings.append(openai_embedding_object) - elif "imageEmbedding" in _prediction: - openai_embedding_object = Embedding( - embedding=_prediction["imageEmbedding"], - index=idx, - object="embedding", - ) - openai_embeddings.append(openai_embedding_object) - elif "videoEmbeddings" in _prediction: - for video_embedding in _prediction["videoEmbeddings"]: - openai_embedding_object = Embedding( - embedding=video_embedding["embedding"], - index=idx, - object="embedding", - ) - openai_embeddings.append(openai_embedding_object) - return openai_embeddings diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py b/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py new file mode 100644 index 0000000000..8f96ca2bb0 --- /dev/null +++ b/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py @@ -0,0 +1,297 @@ +from typing import List, Optional, Union, cast + +from httpx import Headers, Response + +from litellm.exceptions import InternalServerError +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.llms.base_llm.embedding.transformation import LiteLLMLoggingObj +from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues +from litellm.types.llms.vertex_ai import ( + Instance, + InstanceImage, + InstanceVideo, + MultimodalPredictions, + VertexMultimodalEmbeddingRequest, +) +from litellm.types.utils import ( + Embedding, + EmbeddingResponse, + PromptTokensDetailsWrapper, + Usage, +) +from litellm.utils import _count_characters, is_base64_encoded + +from ...base_llm.embedding.transformation import BaseEmbeddingConfig +from ..common_utils import VertexAIError + + +class VertexAIMultimodalEmbeddingConfig(BaseEmbeddingConfig): + def get_supported_openai_params(self, model: str) -> list: + return ["dimensions"] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + for param, value in non_default_params.items(): + if param == "dimensions": + optional_params["outputDimensionality"] = value + return optional_params + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + default_headers = { + "Content-Type": "application/json; charset=utf-8", + "Authorization": f"Bearer {api_key}", + } + headers.update(default_headers) + return headers + + def _process_input_element(self, input_element: str) -> Instance: + """ + Process the input element for multimodal embedding requests. checks if the if the input is gcs uri, base64 encoded image or plain text. + + Args: + input_element (str): The input element to process. + + Returns: + Dict[str, Any]: A dictionary representing the processed input element. + """ + if len(input_element) == 0: + return Instance(text=input_element) + elif "gs://" in input_element: + if "mp4" in input_element: + return Instance(video=InstanceVideo(gcsUri=input_element)) + else: + return Instance(image=InstanceImage(gcsUri=input_element)) + elif is_base64_encoded(s=input_element): + return Instance( + image=InstanceImage( + bytesBase64Encoded=( + input_element.split(",")[1] + if "," in input_element + else input_element + ) + ) + ) + else: + return Instance(text=input_element) + + def process_openai_embedding_input( + self, _input: Union[list, str] + ) -> List[Instance]: + """ + Process the input for multimodal embedding requests. + + Args: + _input (Union[list, str]): The input data to process. + + Returns: + Union[Instance, List[Instance]]: Either a single Instance or list of Instance objects. + """ + _input_list = [_input] if not isinstance(_input, list) else _input + processed_instances = [] + + i = 0 + while i < len(_input_list): + current = _input_list[i] + + # Look ahead for potential media elements + next_elem = _input_list[i + 1] if i + 1 < len(_input_list) else None + + # If current is a text and next is a GCS URI, or current is a GCS URI + if isinstance(current, str): + instance_args: Instance = {} + + # Process current element + if "gs://" not in current: + instance_args["text"] = current + elif "mp4" in current: + instance_args["video"] = InstanceVideo(gcsUri=current) + else: + instance_args["image"] = InstanceImage(gcsUri=current) + + # Check next element if it's a GCS URI + if next_elem and isinstance(next_elem, str) and "gs://" in next_elem: + if "mp4" in next_elem: + instance_args["video"] = InstanceVideo(gcsUri=next_elem) + else: + instance_args["image"] = InstanceImage(gcsUri=next_elem) + i += 2 # Skip next element since we processed it + else: + i += 1 # Move to next element + + processed_instances.append(instance_args) + continue + + # Handle dict or other types + if isinstance(current, dict): + instance = Instance(**current) + processed_instances.append(instance) + else: + raise ValueError(f"Unsupported input type: {type(current)}") + i += 1 + + return processed_instances + + def transform_embedding_request( + self, + model: str, + input: AllEmbeddingInputValues, + optional_params: dict, + headers: dict, + ) -> dict: + optional_params = optional_params or {} + + request_data = VertexMultimodalEmbeddingRequest(instances=[]) + + if "instances" in optional_params: + request_data["instances"] = optional_params["instances"] + elif isinstance(input, list): + vertex_instances: List[Instance] = self.process_openai_embedding_input( + _input=input + ) + request_data["instances"] = vertex_instances + + else: + # construct instances + vertex_request_instance = Instance(**optional_params) + + if isinstance(input, str): + vertex_request_instance = self._process_input_element(input) + + request_data["instances"] = [vertex_request_instance] + + return cast(dict, request_data) + + def transform_embedding_response( + self, + model: str, + raw_response: Response, + model_response: EmbeddingResponse, + logging_obj: LiteLLMLoggingObj, + api_key: Optional[str], + request_data: dict, + optional_params: dict, + litellm_params: dict, + ) -> EmbeddingResponse: + if raw_response.status_code != 200: + raise Exception(f"Error: {raw_response.status_code} {raw_response.text}") + + _json_response = raw_response.json() + if "predictions" not in _json_response: + raise InternalServerError( + message=f"embedding response does not contain 'predictions', got {_json_response}", + llm_provider="vertex_ai", + model=model, + ) + _predictions = _json_response["predictions"] + vertex_predictions = MultimodalPredictions(predictions=_predictions) + model_response.data = self.transform_embedding_response_to_openai( + predictions=vertex_predictions + ) + model_response.model = model + + model_response.usage = self.calculate_usage( + request_data=cast(VertexMultimodalEmbeddingRequest, request_data), + vertex_predictions=vertex_predictions, + ) + + return model_response + + def calculate_usage( + self, + request_data: VertexMultimodalEmbeddingRequest, + vertex_predictions: MultimodalPredictions, + ) -> Usage: + ## Calculate text embeddings usage + prompt: Optional[str] = None + character_count: Optional[int] = None + + for instance in request_data["instances"]: + text = instance.get("text") + if text: + if prompt is None: + prompt = text + else: + prompt += text + + if prompt is not None: + character_count = _count_characters(prompt) + + ## Calculate image embeddings usage + image_count = 0 + for instance in request_data["instances"]: + if instance.get("image"): + image_count += 1 + + ## Calculate video embeddings usage + video_length_seconds = 0 + for prediction in vertex_predictions["predictions"]: + video_embeddings = prediction.get("videoEmbeddings") + if video_embeddings: + for embedding in video_embeddings: + duration = embedding["endOffsetSec"] - embedding["startOffsetSec"] + video_length_seconds += duration + + prompt_tokens_details = PromptTokensDetailsWrapper( + character_count=character_count, + image_count=image_count, + video_length_seconds=video_length_seconds, + ) + + return Usage( + prompt_tokens=0, + completion_tokens=0, + total_tokens=0, + prompt_tokens_details=prompt_tokens_details, + ) + + def transform_embedding_response_to_openai( + self, predictions: MultimodalPredictions + ) -> List[Embedding]: + + openai_embeddings: List[Embedding] = [] + if "predictions" in predictions: + for idx, _prediction in enumerate(predictions["predictions"]): + if _prediction: + if "textEmbedding" in _prediction: + openai_embedding_object = Embedding( + embedding=_prediction["textEmbedding"], + index=idx, + object="embedding", + ) + openai_embeddings.append(openai_embedding_object) + elif "imageEmbedding" in _prediction: + openai_embedding_object = Embedding( + embedding=_prediction["imageEmbedding"], + index=idx, + object="embedding", + ) + openai_embeddings.append(openai_embedding_object) + elif "videoEmbeddings" in _prediction: + for video_embedding in _prediction["videoEmbeddings"]: + openai_embedding_object = Embedding( + embedding=video_embedding["embedding"], + index=idx, + object="embedding", + ) + openai_embeddings.append(openai_embedding_object) + return openai_embeddings + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, Headers] + ) -> BaseLLMException: + return VertexAIError( + status_code=status_code, message=error_message, headers=headers + ) diff --git a/litellm/main.py b/litellm/main.py index b2732c165c..1e6c36aa6c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -3723,6 +3723,7 @@ def embedding( # noqa: PLR0915 encoding=encoding, logging_obj=logging, optional_params=optional_params, + litellm_params=litellm_params_dict, model_response=EmbeddingResponse(), vertex_project=vertex_ai_project, vertex_location=vertex_ai_location, diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index da780f8353..4ce592d7b0 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -5409,6 +5409,23 @@ "supported_modalities": ["text", "image", "video"], "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" }, + "multimodalembedding@001": { + "max_tokens": 2048, + "max_input_tokens": 2048, + "output_vector_size": 768, + "input_cost_per_character": 0.0000002, + "input_cost_per_image": 0.0001, + "input_cost_per_video_per_second": 0.0005, + "input_cost_per_video_per_second_above_8s_interval": 0.0010, + "input_cost_per_video_per_second_above_15s_interval": 0.0020, + "input_cost_per_token": 0.0000008, + "output_cost_per_token": 0, + "litellm_provider": "vertex_ai-embedding-models", + "mode": "embedding", + "supported_endpoints": ["/v1/embeddings"], + "supported_modalities": ["text", "image", "video"], + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" + }, "text-embedding-large-exp-03-07": { "max_tokens": 8192, "max_input_tokens": 8192, diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 49d850ec6a..679b9e16c6 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, cast, get_type_hints +from typing import Any, Dict, Union, cast, get_type_hints import litellm from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig @@ -78,16 +78,22 @@ class ResponsesAPIRequestUtils: class ResponseAPILoggingUtils: @staticmethod - def _is_response_api_usage(usage: dict) -> bool: + def _is_response_api_usage(usage: Union[dict, ResponseAPIUsage]) -> bool: """returns True if usage is from OpenAI Response API""" + if isinstance(usage, ResponseAPIUsage): + return True if "input_tokens" in usage and "output_tokens" in usage: return True return False @staticmethod - def _transform_response_api_usage_to_chat_usage(usage: dict) -> Usage: + def _transform_response_api_usage_to_chat_usage( + usage: Union[dict, ResponseAPIUsage] + ) -> Usage: """Tranforms the ResponseAPIUsage object to a Usage object""" - response_api_usage: ResponseAPIUsage = ResponseAPIUsage(**usage) + response_api_usage: ResponseAPIUsage = ( + ResponseAPIUsage(**usage) if isinstance(usage, dict) else usage + ) prompt_tokens: int = response_api_usage.input_tokens or 0 completion_tokens: int = response_api_usage.output_tokens or 0 return Usage( diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index 08738459f4..fe3f2e1b5f 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -191,6 +191,10 @@ class ContentBlockDeltaEvent(TypedDict, total=False): reasoningContent: BedrockConverseReasoningContentBlockDelta +class PerformanceConfigBlock(TypedDict): + latency: Literal["optimized", "throughput"] + + class CommonRequestObject( TypedDict, total=False ): # common request object across sync + async flows @@ -200,6 +204,7 @@ class CommonRequestObject( system: List[SystemContentBlock] toolConfig: ToolConfigBlock guardrailConfig: Optional[GuardrailConfigBlock] + performanceConfig: Optional[PerformanceConfigBlock] class RequestObject(CommonRequestObject, total=False): @@ -429,7 +434,9 @@ class AmazonNovaCanvasColorGuidedGenerationParams(TypedDict, total=False): negativeText: str -class AmazonNovaCanvasColorGuidedRequest(AmazonNovaCanvasRequestBase, TypedDict, total=False): +class AmazonNovaCanvasColorGuidedRequest( + AmazonNovaCanvasRequestBase, TypedDict, total=False +): """ Request for Amazon Nova Canvas Color Guided Generation API diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index ed6bcdb346..fef034ce80 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -369,9 +369,15 @@ class ResponseTuningJob(TypedDict): updateTime: Optional[str] +class VideoSegmentConfig(TypedDict, total=False): + startOffsetSec: int + endOffsetSec: int + intervalSec: int + + class InstanceVideo(TypedDict, total=False): gcsUri: str - videoSegmentConfig: Tuple[float, float, float] + videoSegmentConfig: VideoSegmentConfig class InstanceImage(TypedDict, total=False): @@ -386,7 +392,7 @@ class Instance(TypedDict, total=False): video: InstanceVideo -class VertexMultimodalEmbeddingRequest(TypedDict, total=False): +class VertexMultimodalEmbeddingRequest(TypedDict): instances: List[Instance] @@ -402,7 +408,7 @@ class MultimodalPrediction(TypedDict, total=False): videoEmbeddings: List[VideoEmbedding] -class MultimodalPredictions(TypedDict, total=False): +class MultimodalPredictions(TypedDict): predictions: List[MultimodalPrediction] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 2cc06eecbf..fe6330f8bd 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -779,6 +779,24 @@ class PromptTokensDetailsWrapper( image_tokens: Optional[int] = None """Image tokens sent to the model.""" + character_count: Optional[int] = None + """Character count sent to the model. Used for Vertex AI multimodal embeddings.""" + + image_count: Optional[int] = None + """Number of images sent to the model. Used for Vertex AI multimodal embeddings.""" + + video_length_seconds: Optional[float] = None + """Length of videos sent to the model. Used for Vertex AI multimodal embeddings.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.character_count is None: + del self.character_count + if self.image_count is None: + del self.image_count + if self.video_length_seconds is None: + del self.video_length_seconds + class Usage(CompletionUsage): _cache_creation_input_tokens: int = PrivateAttr( diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index da780f8353..4ce592d7b0 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -5409,6 +5409,23 @@ "supported_modalities": ["text", "image", "video"], "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" }, + "multimodalembedding@001": { + "max_tokens": 2048, + "max_input_tokens": 2048, + "output_vector_size": 768, + "input_cost_per_character": 0.0000002, + "input_cost_per_image": 0.0001, + "input_cost_per_video_per_second": 0.0005, + "input_cost_per_video_per_second_above_8s_interval": 0.0010, + "input_cost_per_video_per_second_above_15s_interval": 0.0020, + "input_cost_per_token": 0.0000008, + "output_cost_per_token": 0, + "litellm_provider": "vertex_ai-embedding-models", + "mode": "embedding", + "supported_endpoints": ["/v1/embeddings"], + "supported_modalities": ["text", "image", "video"], + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models" + }, "text-embedding-large-exp-03-07": { "max_tokens": 8192, "max_input_tokens": 8192, diff --git a/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py b/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py deleted file mode 100644 index cfd1167836..0000000000 --- a/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_embedding_handler.py +++ /dev/null @@ -1,43 +0,0 @@ -import json -import os -import sys -from unittest.mock import AsyncMock, MagicMock, patch - -import httpx -import pytest - -sys.path.insert( - 0, os.path.abspath("../../../../..") -) # Adds the parent directory to the system path - -from litellm.llms.vertex_ai.multimodal_embeddings.embedding_handler import ( - VertexMultimodalEmbedding, -) -from litellm.types.llms.vertex_ai import Instance, InstanceImage - - -class TestVertexMultimodalEmbedding: - def setup_method(self): - self.embedding_handler = VertexMultimodalEmbedding() - - def test_process_openai_embedding_input(self): - input_data = [ - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", - ] - expected_output = [ - Instance( - image=InstanceImage(bytesBase64Encoded=input_data[0].split(",")[1]) - ), - Instance( - image=InstanceImage(bytesBase64Encoded=input_data[1].split(",")[1]) - ), - ] - assert ( - self.embedding_handler._process_input_element(input_data[0]) - == expected_output[0] - ) - assert ( - self.embedding_handler._process_input_element(input_data[1]) - == expected_output[1] - ) diff --git a/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_vertex_ai_multimodal_embedding_transformation.py b/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_vertex_ai_multimodal_embedding_transformation.py new file mode 100644 index 0000000000..88a60cb7c0 --- /dev/null +++ b/tests/litellm/llms/vertex_ai/multimodal_embeddings/test_vertex_ai_multimodal_embedding_transformation.py @@ -0,0 +1,78 @@ +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.vertex_ai.multimodal_embeddings.transformation import ( + VertexAIMultimodalEmbeddingConfig, +) +from litellm.types.llms.vertex_ai import Instance, InstanceImage, InstanceVideo + + +class TestVertexMultimodalEmbedding: + def setup_method(self): + self.config = VertexAIMultimodalEmbeddingConfig() + + def test_process_openai_embedding_input(self): + input_data = [ + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=", + ] + expected_output = [ + Instance( + image=InstanceImage(bytesBase64Encoded=input_data[0].split(",")[1]) + ), + Instance( + image=InstanceImage(bytesBase64Encoded=input_data[1].split(",")[1]) + ), + ] + assert self.config._process_input_element(input_data[0]) == expected_output[0] + assert self.config._process_input_element(input_data[1]) == expected_output[1] + + def test_process_str_and_video_input(self): + input_data = ["hi", "gs://my-bucket/embeddings/supermarket-video.mp4"] + expected_output = [ + Instance( + text="hi", + video=InstanceVideo( + gcsUri="gs://my-bucket/embeddings/supermarket-video.mp4" + ), + ), + ] + assert self.config.process_openai_embedding_input(input_data) == expected_output + + def test_process_list_of_str_and_str_input(self): + input_data = ["hi", "hello"] + expected_output = [ + Instance(text="hi"), + Instance(text="hello"), + ] + assert self.config.process_openai_embedding_input(input_data) == expected_output + + def test_process_list_of_str_and_video_input(self): + input_data = [ + "hi", + "hello", + "gs://my-bucket/embeddings/supermarket-video.mp4", + "hey", + ] + expected_output = [ + Instance(text="hi"), + Instance( + text="hello", + video=InstanceVideo( + gcsUri="gs://my-bucket/embeddings/supermarket-video.mp4" + ), + ), + Instance(text="hey"), + ] + assert ( + self.config.process_openai_embedding_input(input_data) == expected_output + ), f"Expected {expected_output}, but got {self.config.process_openai_embedding_input(input_data)}" diff --git a/tests/litellm/test_main.py b/tests/litellm/test_main.py index 99ac8514f6..57161e7dd7 100644 --- a/tests/litellm/test_main.py +++ b/tests/litellm/test_main.py @@ -239,3 +239,23 @@ async def test_url_with_format_param_openai(model, sync_mode): json_str = json.dumps(mock_client.call_args.kwargs) assert "format" not in json_str + + +def test_bedrock_latency_optimized_inference(): + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + with patch.object(client, "post") as mock_post: + try: + response = litellm.completion( + model="bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0", + messages=[{"role": "user", "content": "Hello, how are you?"}], + performanceConfig={"latency": "optimized"}, + client=client, + ) + except Exception as e: + print(e) + + mock_post.assert_called_once() + json_data = json.loads(mock_post.call_args.kwargs["data"]) + assert json_data["performanceConfig"]["latency"] == "optimized" diff --git a/tests/llm_translation/test_openai.py b/tests/llm_translation/test_openai.py index 676019b86f..f28ec5efb6 100644 --- a/tests/llm_translation/test_openai.py +++ b/tests/llm_translation/test_openai.py @@ -416,6 +416,7 @@ def validate_web_search_annotations(annotations: ChatCompletionAnnotation): validate_response_url_citation(url_citation) +@pytest.mark.flaky(reruns=3) def test_openai_web_search(): """Makes a simple web search request and validates the response contains web search annotations and all expected fields are present""" litellm._turn_on_debug() diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 83c99d766b..36887a84da 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -2197,6 +2197,55 @@ def test_vertexai_embedding_embedding_latest(): pytest.fail(f"Error occurred: {e}") +def test_vertexai_multimodalembedding_embedding_latest(): + try: + import requests, base64 + + load_vertex_ai_credentials() + litellm._turn_on_debug() + + response = embedding( + model="vertex_ai/multimodalembedding@001", + input=["hi"], + dimensions=1, + auto_truncate=True, + task_type="RETRIEVAL_QUERY", + ) + + print(f"response.usage: {response.usage}") + assert response.usage is not None + assert response.usage.prompt_tokens_details is not None + + assert response._hidden_params["response_cost"] > 0 + print(f"response:", response) + except litellm.RateLimitError as e: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + + +def test_vertexai_embedding_embedding_latest(): + try: + load_vertex_ai_credentials() + litellm.set_verbose = True + + response = embedding( + model="vertex_ai/text-embedding-004", + input=["hi"], + dimensions=1, + auto_truncate=True, + task_type="RETRIEVAL_QUERY", + ) + + assert len(response.data[0]["embedding"]) == 1 + assert response.usage.prompt_tokens > 0 + print(f"response:", response) + except litellm.RateLimitError as e: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + + @pytest.mark.skip(reason="need to get gecko permissions on vertex ai to run this test") @pytest.mark.flaky(retries=3, delay=1) def test_vertexai_embedding_embedding_latest_input_type(): From cac60d50914631ab47d2b3539be828e369adefb2 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 00:31:06 -0700 Subject: [PATCH 186/244] docs(bedrock.md): add latency optimized inference to docs --- docs/my-website/docs/providers/bedrock.md | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index ed65e14b8b..922cca9723 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -664,6 +664,56 @@ curl http://0.0.0.0:4000/v1/chat/completions \ +## Usage - Latency Optimized Inference + + + + +```python +from litellm import completion + +response = completion( + model="bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0", + messages=[{"role": "user", "content": "What is the capital of France?"}], + performanceConfig={"latency": "optimized"}, +) +``` + + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: bedrock-claude-3-7 + litellm_params: + model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0 + performanceConfig: {"latency": "optimized"} # 👈 EITHER HERE OR ON REQUEST +``` + +2. Start proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $LITELLM_KEY" \ + -d '{ + "model": "bedrock-claude-3-7", + "messages": [{"role": "user", "content": "What is the capital of France?"}], + "performanceConfig": {"latency": "optimized"} # 👈 EITHER HERE OR ON CONFIG.YAML + }' +``` + + + + ## Usage - Bedrock Guardrails Example of using [Bedrock Guardrails with LiteLLM](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-use-converse-api.html) From 2fcfabd66f72850305765076d6f0348ee1188c2f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 00:31:35 -0700 Subject: [PATCH 187/244] docs(bedrock.md): clarify version on docs --- docs/my-website/docs/providers/bedrock.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 922cca9723..95e0c90bd7 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -666,6 +666,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \ ## Usage - Latency Optimized Inference +Valid from v1.65.1+ + From 2a0c7e7d20451fd20cb381582c6ff7c6baf0b42d Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 00:39:18 -0700 Subject: [PATCH 188/244] =?UTF-8?q?bump:=20version=201.65.0=20=E2=86=92=20?= =?UTF-8?q?1.65.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 325bf8b4e7..2dbfcc39de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.65.0" +version = "1.65.1" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -110,7 +110,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.65.0" +version = "1.65.1" version_files = [ "pyproject.toml:^version" ] From 217e8d7d448212d69612586ccfc822b805edec4a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 08:23:18 -0700 Subject: [PATCH 189/244] test: make script to run clearer --- tests/proxy_unit_tests/test_db_schema_migration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py index 47125e86a7..089c50ebf8 100644 --- a/tests/proxy_unit_tests/test_db_schema_migration.py +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -39,7 +39,9 @@ def test_aaaasschema_migration_check(schema_setup, monkeypatch): if not temp_migrations_dir.exists() or not any(temp_migrations_dir.iterdir()): print("No existing migrations found - first migration needed") - pytest.fail("No existing migrations found - first migration needed") + pytest.fail( + "No existing migrations found - first migration needed. Run `litellm/ci_cd/baseline_db.py` to create new migration -E.g. `python litellm/ci_cd/baseline_db_migration.py`." + ) # Apply all existing migrations subprocess.run( @@ -72,7 +74,7 @@ def test_aaaasschema_migration_check(schema_setup, monkeypatch): print("Schema differences:") print(diff_result.stdout) pytest.fail( - "Schema changes detected - new migration required. Run 'prisma migrate dev' to create new migration." + "Schema changes detected - new migration required. Run `litellm/ci_cd/run_migration.py` to create new migration -E.g. `python litellm/ci_cd/run_migration.py `." ) else: print("No schema changes detected. Migration not needed.") From 95e5dfae5a8de43e8004f9e94f3cbff6dd38fbf3 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 08:48:48 -0700 Subject: [PATCH 190/244] build(ci_cd/): add migration script --- .../migration.sql | 14 + .../migration.sql | 407 ------------------ 2 files changed, 14 insertions(+), 407 deletions(-) create mode 100644 deploy/migrations/20250329084805_new_cron_job_table/migration.sql delete mode 100644 migrations/20250329044652_add_cron_job_table/migration.sql diff --git a/deploy/migrations/20250329084805_new_cron_job_table/migration.sql b/deploy/migrations/20250329084805_new_cron_job_table/migration.sql new file mode 100644 index 0000000000..e7ea2e9015 --- /dev/null +++ b/deploy/migrations/20250329084805_new_cron_job_table/migration.sql @@ -0,0 +1,14 @@ +-- CreateEnum +CREATE TYPE "JobStatus" AS ENUM ('ACTIVE', 'INACTIVE'); + +-- CreateTable +CREATE TABLE "LiteLLM_CronJob" ( + "cronjob_id" TEXT NOT NULL, + "pod_id" TEXT NOT NULL, + "status" "JobStatus" NOT NULL DEFAULT 'INACTIVE', + "last_updated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ttl" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_CronJob_pkey" PRIMARY KEY ("cronjob_id") +); + diff --git a/migrations/20250329044652_add_cron_job_table/migration.sql b/migrations/20250329044652_add_cron_job_table/migration.sql deleted file mode 100644 index edfa99abd5..0000000000 --- a/migrations/20250329044652_add_cron_job_table/migration.sql +++ /dev/null @@ -1,407 +0,0 @@ --- CreateEnum -CREATE TYPE "JobStatus" AS ENUM ('ACTIVE', 'INACTIVE'); - --- CreateTable -CREATE TABLE "LiteLLM_BudgetTable" ( - "budget_id" TEXT NOT NULL, - "max_budget" DOUBLE PRECISION, - "soft_budget" DOUBLE PRECISION, - "max_parallel_requests" INTEGER, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "model_max_budget" JSONB, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_BudgetTable_pkey" PRIMARY KEY ("budget_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_CredentialsTable" ( - "credential_id" TEXT NOT NULL, - "credential_name" TEXT NOT NULL, - "credential_values" JSONB NOT NULL, - "credential_info" JSONB, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_CredentialsTable_pkey" PRIMARY KEY ("credential_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_ProxyModelTable" ( - "model_id" TEXT NOT NULL, - "model_name" TEXT NOT NULL, - "litellm_params" JSONB NOT NULL, - "model_info" JSONB, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_ProxyModelTable_pkey" PRIMARY KEY ("model_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_OrganizationTable" ( - "organization_id" TEXT NOT NULL, - "organization_alias" TEXT NOT NULL, - "budget_id" TEXT NOT NULL, - "metadata" JSONB NOT NULL DEFAULT '{}', - "models" TEXT[], - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "model_spend" JSONB NOT NULL DEFAULT '{}', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_OrganizationTable_pkey" PRIMARY KEY ("organization_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_ModelTable" ( - "id" SERIAL NOT NULL, - "aliases" JSONB, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_ModelTable_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LiteLLM_TeamTable" ( - "team_id" TEXT NOT NULL, - "team_alias" TEXT, - "organization_id" TEXT, - "admins" TEXT[], - "members" TEXT[], - "members_with_roles" JSONB NOT NULL DEFAULT '{}', - "metadata" JSONB NOT NULL DEFAULT '{}', - "max_budget" DOUBLE PRECISION, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "models" TEXT[], - "max_parallel_requests" INTEGER, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "blocked" BOOLEAN NOT NULL DEFAULT false, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "model_spend" JSONB NOT NULL DEFAULT '{}', - "model_max_budget" JSONB NOT NULL DEFAULT '{}', - "model_id" INTEGER, - - CONSTRAINT "LiteLLM_TeamTable_pkey" PRIMARY KEY ("team_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_UserTable" ( - "user_id" TEXT NOT NULL, - "user_alias" TEXT, - "team_id" TEXT, - "sso_user_id" TEXT, - "organization_id" TEXT, - "password" TEXT, - "teams" TEXT[] DEFAULT ARRAY[]::TEXT[], - "user_role" TEXT, - "max_budget" DOUBLE PRECISION, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "user_email" TEXT, - "models" TEXT[], - "metadata" JSONB NOT NULL DEFAULT '{}', - "max_parallel_requests" INTEGER, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "model_spend" JSONB NOT NULL DEFAULT '{}', - "model_max_budget" JSONB NOT NULL DEFAULT '{}', - "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "LiteLLM_UserTable_pkey" PRIMARY KEY ("user_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_VerificationToken" ( - "token" TEXT NOT NULL, - "key_name" TEXT, - "key_alias" TEXT, - "soft_budget_cooldown" BOOLEAN NOT NULL DEFAULT false, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "expires" TIMESTAMP(3), - "models" TEXT[], - "aliases" JSONB NOT NULL DEFAULT '{}', - "config" JSONB NOT NULL DEFAULT '{}', - "user_id" TEXT, - "team_id" TEXT, - "permissions" JSONB NOT NULL DEFAULT '{}', - "max_parallel_requests" INTEGER, - "metadata" JSONB NOT NULL DEFAULT '{}', - "blocked" BOOLEAN, - "tpm_limit" BIGINT, - "rpm_limit" BIGINT, - "max_budget" DOUBLE PRECISION, - "budget_duration" TEXT, - "budget_reset_at" TIMESTAMP(3), - "allowed_cache_controls" TEXT[] DEFAULT ARRAY[]::TEXT[], - "model_spend" JSONB NOT NULL DEFAULT '{}', - "model_max_budget" JSONB NOT NULL DEFAULT '{}', - "budget_id" TEXT, - "organization_id" TEXT, - "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - "created_by" TEXT, - "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - "updated_by" TEXT, - - CONSTRAINT "LiteLLM_VerificationToken_pkey" PRIMARY KEY ("token") -); - --- CreateTable -CREATE TABLE "LiteLLM_EndUserTable" ( - "user_id" TEXT NOT NULL, - "alias" TEXT, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "allowed_model_region" TEXT, - "default_model" TEXT, - "budget_id" TEXT, - "blocked" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "LiteLLM_EndUserTable_pkey" PRIMARY KEY ("user_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_Config" ( - "param_name" TEXT NOT NULL, - "param_value" JSONB, - - CONSTRAINT "LiteLLM_Config_pkey" PRIMARY KEY ("param_name") -); - --- CreateTable -CREATE TABLE "LiteLLM_SpendLogs" ( - "request_id" TEXT NOT NULL, - "call_type" TEXT NOT NULL, - "api_key" TEXT NOT NULL DEFAULT '', - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "total_tokens" INTEGER NOT NULL DEFAULT 0, - "prompt_tokens" INTEGER NOT NULL DEFAULT 0, - "completion_tokens" INTEGER NOT NULL DEFAULT 0, - "startTime" TIMESTAMP(3) NOT NULL, - "endTime" TIMESTAMP(3) NOT NULL, - "completionStartTime" TIMESTAMP(3), - "model" TEXT NOT NULL DEFAULT '', - "model_id" TEXT DEFAULT '', - "model_group" TEXT DEFAULT '', - "custom_llm_provider" TEXT DEFAULT '', - "api_base" TEXT DEFAULT '', - "user" TEXT DEFAULT '', - "metadata" JSONB DEFAULT '{}', - "cache_hit" TEXT DEFAULT '', - "cache_key" TEXT DEFAULT '', - "request_tags" JSONB DEFAULT '[]', - "team_id" TEXT, - "end_user" TEXT, - "requester_ip_address" TEXT, - "messages" JSONB DEFAULT '{}', - "response" JSONB DEFAULT '{}', - - CONSTRAINT "LiteLLM_SpendLogs_pkey" PRIMARY KEY ("request_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_ErrorLogs" ( - "request_id" TEXT NOT NULL, - "startTime" TIMESTAMP(3) NOT NULL, - "endTime" TIMESTAMP(3) NOT NULL, - "api_base" TEXT NOT NULL DEFAULT '', - "model_group" TEXT NOT NULL DEFAULT '', - "litellm_model_name" TEXT NOT NULL DEFAULT '', - "model_id" TEXT NOT NULL DEFAULT '', - "request_kwargs" JSONB NOT NULL DEFAULT '{}', - "exception_type" TEXT NOT NULL DEFAULT '', - "exception_string" TEXT NOT NULL DEFAULT '', - "status_code" TEXT NOT NULL DEFAULT '', - - CONSTRAINT "LiteLLM_ErrorLogs_pkey" PRIMARY KEY ("request_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_UserNotifications" ( - "request_id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "models" TEXT[], - "justification" TEXT NOT NULL, - "status" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_UserNotifications_pkey" PRIMARY KEY ("request_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_TeamMembership" ( - "user_id" TEXT NOT NULL, - "team_id" TEXT NOT NULL, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "budget_id" TEXT, - - CONSTRAINT "LiteLLM_TeamMembership_pkey" PRIMARY KEY ("user_id","team_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_OrganizationMembership" ( - "user_id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "user_role" TEXT, - "spend" DOUBLE PRECISION DEFAULT 0.0, - "budget_id" TEXT, - "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "LiteLLM_OrganizationMembership_pkey" PRIMARY KEY ("user_id","organization_id") -); - --- CreateTable -CREATE TABLE "LiteLLM_InvitationLink" ( - "id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "is_accepted" BOOLEAN NOT NULL DEFAULT false, - "accepted_at" TIMESTAMP(3), - "expires_at" TIMESTAMP(3) NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL, - "created_by" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL, - "updated_by" TEXT NOT NULL, - - CONSTRAINT "LiteLLM_InvitationLink_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LiteLLM_AuditLog" ( - "id" TEXT NOT NULL, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "changed_by" TEXT NOT NULL DEFAULT '', - "changed_by_api_key" TEXT NOT NULL DEFAULT '', - "action" TEXT NOT NULL, - "table_name" TEXT NOT NULL, - "object_id" TEXT NOT NULL, - "before_value" JSONB, - "updated_values" JSONB, - - CONSTRAINT "LiteLLM_AuditLog_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LiteLLM_DailyUserSpend" ( - "id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "date" TEXT NOT NULL, - "api_key" TEXT NOT NULL, - "model" TEXT NOT NULL, - "model_group" TEXT, - "custom_llm_provider" TEXT, - "prompt_tokens" INTEGER NOT NULL DEFAULT 0, - "completion_tokens" INTEGER NOT NULL DEFAULT 0, - "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, - "api_requests" INTEGER NOT NULL DEFAULT 0, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "LiteLLM_DailyUserSpend_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "LiteLLM_CronJob" ( - "cronjob_id" TEXT NOT NULL, - "pod_id" TEXT NOT NULL, - "status" "JobStatus" NOT NULL DEFAULT 'INACTIVE', - "last_updated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "ttl" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "LiteLLM_CronJob_pkey" PRIMARY KEY ("cronjob_id") -); - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_CredentialsTable_credential_name_key" ON "LiteLLM_CredentialsTable"("credential_name"); - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_TeamTable_model_id_key" ON "LiteLLM_TeamTable"("model_id"); - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_UserTable_sso_user_id_key" ON "LiteLLM_UserTable"("sso_user_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_SpendLogs_startTime_idx" ON "LiteLLM_SpendLogs"("startTime"); - --- CreateIndex -CREATE INDEX "LiteLLM_SpendLogs_end_user_idx" ON "LiteLLM_SpendLogs"("end_user"); - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_OrganizationMembership_user_id_organization_id_key" ON "LiteLLM_OrganizationMembership"("user_id", "organization_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DailyUserSpend_date_idx" ON "LiteLLM_DailyUserSpend"("date"); - --- CreateIndex -CREATE INDEX "LiteLLM_DailyUserSpend_user_id_idx" ON "LiteLLM_DailyUserSpend"("user_id"); - --- CreateIndex -CREATE INDEX "LiteLLM_DailyUserSpend_api_key_idx" ON "LiteLLM_DailyUserSpend"("api_key"); - --- CreateIndex -CREATE INDEX "LiteLLM_DailyUserSpend_model_idx" ON "LiteLLM_DailyUserSpend"("model"); - --- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_DailyUserSpend_user_id_date_api_key_model_custom_ll_key" ON "LiteLLM_DailyUserSpend"("user_id", "date", "api_key", "model", "custom_llm_provider"); - --- AddForeignKey -ALTER TABLE "LiteLLM_OrganizationTable" ADD CONSTRAINT "LiteLLM_OrganizationTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_TeamTable" ADD CONSTRAINT "LiteLLM_TeamTable_model_id_fkey" FOREIGN KEY ("model_id") REFERENCES "LiteLLM_ModelTable"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_UserTable" ADD CONSTRAINT "LiteLLM_UserTable_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_VerificationToken" ADD CONSTRAINT "LiteLLM_VerificationToken_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_EndUserTable" ADD CONSTRAINT "LiteLLM_EndUserTable_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_TeamMembership" ADD CONSTRAINT "LiteLLM_TeamMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "LiteLLM_OrganizationTable"("organization_id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_OrganizationMembership" ADD CONSTRAINT "LiteLLM_OrganizationMembership_budget_id_fkey" FOREIGN KEY ("budget_id") REFERENCES "LiteLLM_BudgetTable"("budget_id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "LiteLLM_InvitationLink" ADD CONSTRAINT "LiteLLM_InvitationLink_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "LiteLLM_UserTable"("user_id") ON DELETE RESTRICT ON UPDATE CASCADE; From 9b7ebb6a7d110ffb802698a4a35678967bde2024 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Sat, 29 Mar 2025 11:02:13 -0700 Subject: [PATCH 191/244] build(pyproject.toml): add new dev dependencies - for type checking (#9631) * build(pyproject.toml): add new dev dependencies - for type checking * build: reformat files to fit black * ci: reformat to fit black * ci(test-litellm.yml): make tests run clear * build(pyproject.toml): add ruff * fix: fix ruff checks * build(mypy/): fix mypy linting errors * fix(hashicorp_secret_manager.py): fix passing cert for tls auth * build(mypy/): resolve all mypy errors * test: update test * fix: fix black formatting * build(pre-commit-config.yaml): use poetry run black * fix(proxy_server.py): fix linting error * fix: fix ruff safe representation error --- .github/workflows/test-linting.yml | 53 +++ .github/workflows/test-litellm.yml | 2 +- .pre-commit-config.yaml | 10 +- .../enterprise_hooks/secret_detection.py | 4 - litellm/__init__.py | 98 +++--- litellm/_service_logger.py | 2 +- litellm/batches/main.py | 2 - litellm/caching/base_cache.py | 4 +- litellm/caching/caching_handler.py | 11 +- litellm/caching/disk_cache.py | 4 +- litellm/caching/dual_cache.py | 4 +- litellm/caching/llm_caching_handler.py | 1 - litellm/caching/redis_cache.py | 3 +- litellm/caching/redis_cluster_cache.py | 4 +- litellm/caching/redis_semantic_cache.py | 127 +++---- litellm/caching/s3_cache.py | 2 +- litellm/cost_calculator.py | 1 - litellm/fine_tuning/main.py | 3 - .../SlackAlerting/batching_handler.py | 1 - .../SlackAlerting/slack_alerting.py | 28 +- litellm/integrations/_types/open_inference.py | 2 +- litellm/integrations/argilla.py | 1 - litellm/integrations/arize/_utils.py | 4 +- litellm/integrations/arize/arize.py | 3 +- litellm/integrations/arize/arize_phoenix.py | 27 +- litellm/integrations/athina.py | 5 +- .../azure_storage/azure_storage.py | 13 +- litellm/integrations/braintrust_logging.py | 103 ++++-- litellm/integrations/custom_batch_logger.py | 1 - litellm/integrations/custom_guardrail.py | 1 - litellm/integrations/custom_logger.py | 2 +- litellm/integrations/datadog/datadog.py | 1 - .../gcs_bucket/gcs_bucket_base.py | 6 +- litellm/integrations/gcs_pubsub/pub_sub.py | 13 +- litellm/integrations/humanloop.py | 6 +- litellm/integrations/langfuse/langfuse.py | 6 +- .../integrations/langfuse/langfuse_handler.py | 5 +- .../langfuse/langfuse_prompt_management.py | 6 +- litellm/integrations/langsmith.py | 8 +- litellm/integrations/langtrace.py | 4 +- litellm/integrations/lunary.py | 2 - litellm/integrations/mlflow.py | 9 +- litellm/integrations/opentelemetry.py | 28 +- litellm/integrations/opik/opik.py | 1 - litellm/integrations/prometheus.py | 6 +- .../integrations/prompt_management_base.py | 7 +- litellm/integrations/s3.py | 2 +- litellm/integrations/weights_biases.py | 8 +- litellm/litellm_core_utils/core_helpers.py | 2 +- .../litellm_core_utils/default_encoding.py | 4 +- litellm/litellm_core_utils/litellm_logging.py | 317 +++++++++--------- .../convert_dict_to_response.py | 8 +- .../litellm_core_utils/model_param_helper.py | 1 - .../prompt_templates/common_utils.py | 1 - .../prompt_templates/factory.py | 26 +- .../litellm_core_utils/realtime_streaming.py | 1 - litellm/litellm_core_utils/redact_messages.py | 6 +- .../sensitive_data_masker.py | 6 +- .../streaming_chunk_builder_utils.py | 1 - .../litellm_core_utils/streaming_handler.py | 20 +- litellm/llms/anthropic/chat/handler.py | 16 +- litellm/llms/anthropic/chat/transformation.py | 46 +-- .../anthropic/completion/transformation.py | 6 +- .../messages/handler.py | 28 +- litellm/llms/azure/azure.py | 6 - litellm/llms/azure/batches/handler.py | 72 ++-- litellm/llms/azure/common_utils.py | 1 - litellm/llms/azure/files/handler.py | 91 +++-- litellm/llms/azure/fine_tuning/handler.py | 9 +- litellm/llms/azure_ai/chat/transformation.py | 1 - .../azure_ai/embed/cohere_transformation.py | 1 - litellm/llms/azure_ai/embed/handler.py | 2 - .../llms/azure_ai/rerank/transformation.py | 1 + litellm/llms/base.py | 1 - litellm/llms/base_llm/chat/transformation.py | 1 - .../llms/base_llm/responses/transformation.py | 1 - litellm/llms/bedrock/chat/converse_handler.py | 10 +- .../bedrock/chat/converse_transformation.py | 20 +- litellm/llms/bedrock/chat/invoke_handler.py | 14 +- .../base_invoke_transformation.py | 7 +- litellm/llms/bedrock/common_utils.py | 1 - .../amazon_titan_multimodal_transformation.py | 7 +- .../amazon_nova_canvas_transformation.py | 93 +++-- litellm/llms/bedrock/image/image_handler.py | 13 +- litellm/llms/bedrock/rerank/handler.py | 1 - litellm/llms/bedrock/rerank/transformation.py | 1 - litellm/llms/codestral/completion/handler.py | 3 - .../codestral/completion/transformation.py | 1 - litellm/llms/cohere/chat/transformation.py | 2 - litellm/llms/cohere/embed/handler.py | 1 - litellm/llms/cohere/embed/transformation.py | 2 - litellm/llms/cohere/rerank/transformation.py | 2 +- .../llms/cohere/rerank_v2/transformation.py | 3 +- litellm/llms/custom_httpx/aiohttp_handler.py | 2 - litellm/llms/custom_httpx/http_handler.py | 4 - litellm/llms/custom_httpx/llm_http_handler.py | 5 - litellm/llms/databricks/common_utils.py | 6 +- .../llms/databricks/embed/transformation.py | 6 +- litellm/llms/databricks/streaming_utils.py | 1 - .../audio_transcription/transformation.py | 6 +- litellm/llms/deepseek/chat/transformation.py | 1 - .../llms/deprecated_providers/aleph_alpha.py | 6 +- .../llms/fireworks_ai/chat/transformation.py | 3 - litellm/llms/gemini/chat/transformation.py | 1 - litellm/llms/gemini/common_utils.py | 1 - litellm/llms/groq/chat/transformation.py | 1 - litellm/llms/groq/stt/transformation.py | 1 - .../llms/huggingface/chat/transformation.py | 24 +- litellm/llms/maritalk.py | 1 - .../llms/ollama/completion/transformation.py | 6 +- .../llms/openai/chat/gpt_transformation.py | 1 - litellm/llms/openai/completion/handler.py | 1 - .../llms/openai/completion/transformation.py | 6 +- litellm/llms/openai/fine_tuning/handler.py | 9 +- litellm/llms/openai/openai.py | 25 +- .../transcriptions/whisper_transformation.py | 6 +- .../llms/openrouter/chat/transformation.py | 8 +- .../llms/petals/completion/transformation.py | 6 +- litellm/llms/predibase/chat/handler.py | 1 - litellm/llms/predibase/chat/transformation.py | 12 +- litellm/llms/replicate/chat/handler.py | 1 - litellm/llms/sagemaker/chat/handler.py | 2 - litellm/llms/sagemaker/common_utils.py | 2 - litellm/llms/sagemaker/completion/handler.py | 12 +- .../sagemaker/completion/transformation.py | 6 +- .../llms/together_ai/rerank/transformation.py | 1 - .../topaz/image_variations/transformation.py | 2 - .../llms/triton/completion/transformation.py | 1 - litellm/llms/vertex_ai/batches/handler.py | 7 +- litellm/llms/vertex_ai/common_utils.py | 4 +- litellm/llms/vertex_ai/files/handler.py | 9 +- litellm/llms/vertex_ai/fine_tuning/handler.py | 15 +- .../llms/vertex_ai/gemini/transformation.py | 26 +- .../vertex_and_google_ai_studio_gemini.py | 18 +- .../batch_embed_content_handler.py | 1 - .../batch_embed_content_transformation.py | 1 - .../embedding_handler.py | 1 - .../multimodal_embeddings/transformation.py | 1 - .../llms/vertex_ai/vertex_ai_non_gemini.py | 2 - .../vertex_ai_partner_models/main.py | 1 - .../vertex_embeddings/embedding_handler.py | 12 +- .../vertex_embeddings/transformation.py | 1 - .../vertex_ai/vertex_model_garden/main.py | 1 - litellm/llms/watsonx/chat/transformation.py | 1 - litellm/main.py | 56 ++-- litellm/proxy/_types.py | 68 ++-- litellm/proxy/auth/auth_checks.py | 7 +- .../proxy/auth/auth_checks_organization.py | 14 +- litellm/proxy/auth/auth_exception_handler.py | 5 +- litellm/proxy/auth/auth_utils.py | 2 - litellm/proxy/auth/handle_jwt.py | 23 +- litellm/proxy/auth/litellm_license.py | 1 - litellm/proxy/auth/model_checks.py | 1 - litellm/proxy/auth/route_checks.py | 3 - litellm/proxy/auth/user_api_key_auth.py | 28 +- .../common_utils/encrypt_decrypt_utils.py | 2 - .../proxy/common_utils/http_parsing_utils.py | 8 +- litellm/proxy/db/log_db_metrics.py | 1 - litellm/proxy/db/redis_update_buffer.py | 22 +- litellm/proxy/guardrails/guardrail_helpers.py | 1 - .../guardrail_hooks/bedrock_guardrails.py | 1 - .../guardrails/guardrail_hooks/lakera_ai.py | 6 +- .../guardrails/guardrail_hooks/presidio.py | 10 +- litellm/proxy/hooks/dynamic_rate_limiter.py | 58 ++-- .../proxy/hooks/key_management_event_hooks.py | 1 - .../proxy/hooks/parallel_request_limiter.py | 34 +- .../proxy/hooks/prompt_injection_detection.py | 1 - .../proxy/hooks/proxy_track_cost_callback.py | 8 +- litellm/proxy/litellm_pre_call_utils.py | 16 +- .../budget_management_endpoints.py | 1 - .../management_endpoints/common_utils.py | 1 - .../customer_endpoints.py | 1 - .../internal_user_endpoints.py | 41 ++- .../key_management_endpoints.py | 41 ++- .../model_management_endpoints.py | 3 - .../organization_endpoints.py | 43 +-- .../management_endpoints/team_endpoints.py | 28 +- litellm/proxy/management_endpoints/ui_sso.py | 12 +- litellm/proxy/management_helpers/utils.py | 3 +- .../openai_files_endpoints/files_endpoints.py | 1 - .../anthropic_passthrough_logging_handler.py | 7 +- .../vertex_passthrough_logging_handler.py | 5 +- .../pass_through_endpoints.py | 3 - .../passthrough_endpoint_router.py | 6 +- .../streaming_handler.py | 1 - litellm/proxy/prisma_migration.py | 30 +- litellm/proxy/proxy_server.py | 36 +- .../spend_management_endpoints.py | 6 - litellm/proxy/utils.py | 9 +- .../vertex_ai_endpoints/langfuse_endpoints.py | 8 +- litellm/rerank_api/main.py | 18 +- litellm/responses/main.py | 27 +- litellm/router.py | 94 +++--- .../router_strategy/base_routing_strategy.py | 6 +- litellm/router_strategy/budget_limiter.py | 41 +-- litellm/router_strategy/lowest_latency.py | 2 +- litellm/router_strategy/lowest_tpm_rpm_v2.py | 3 +- litellm/router_utils/cooldown_cache.py | 4 +- litellm/router_utils/cooldown_callbacks.py | 6 +- litellm/router_utils/cooldown_handlers.py | 2 +- litellm/router_utils/handle_error.py | 4 +- .../router_utils/pattern_match_deployments.py | 12 +- litellm/router_utils/prompt_caching_cache.py | 4 +- .../secret_managers/aws_secret_manager_v2.py | 1 - .../hashicorp_secret_manager.py | 6 +- litellm/types/integrations/arize_phoenix.py | 4 +- litellm/types/llms/openai.py | 36 +- litellm/types/rerank.py | 7 +- litellm/types/router.py | 29 +- litellm/types/utils.py | 3 - litellm/utils.py | 53 ++- poetry.lock | 139 +++++++- pyproject.toml | 7 +- tests/litellm_utils_tests/test_hashicorp.py | 36 +- 214 files changed, 1553 insertions(+), 1433 deletions(-) create mode 100644 .github/workflows/test-linting.yml diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml new file mode 100644 index 0000000000..d117d12de4 --- /dev/null +++ b/.github/workflows/test-linting.yml @@ -0,0 +1,53 @@ +name: LiteLLM Linting + +on: + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: | + poetry install --with dev + + - name: Run Black formatting check + run: | + cd litellm + poetry run black . --check + cd .. + + - name: Run Ruff linting + run: | + cd litellm + poetry run ruff check . + cd .. + + - name: Run MyPy type checking + run: | + cd litellm + poetry run mypy . --ignore-missing-imports + cd .. + + - name: Check for circular imports + run: | + cd litellm + poetry run python ../tests/documentation_tests/test_circular_imports.py + cd .. + + - name: Check import safety + run: | + poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1) \ No newline at end of file diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml index 09de0ff110..12d09725ed 100644 --- a/.github/workflows/test-litellm.yml +++ b/.github/workflows/test-litellm.yml @@ -1,4 +1,4 @@ -name: LiteLLM Tests +name: LiteLLM Mock Tests (folder - tests/litellm) on: pull_request: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb37f32524..bceedb41aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,10 +14,12 @@ repos: types: [python] files: litellm/.*\.py exclude: ^litellm/__init__.py$ -- repo: https://github.com/psf/black - rev: 24.2.0 - hooks: - - id: black + - id: black + name: black + entry: poetry run black + language: system + types: [python] + files: litellm/.*\.py - repo: https://github.com/pycqa/flake8 rev: 7.0.0 # The version of flake8 to use hooks: diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index 459fd374d1..158f26efa3 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -444,9 +444,7 @@ class _ENTERPRISE_SecretDetection(CustomGuardrail): detected_secrets = [] for file in secrets.files: - for found_secret in secrets[file]: - if found_secret.secret_value is None: continue detected_secrets.append( @@ -471,14 +469,12 @@ class _ENTERPRISE_SecretDetection(CustomGuardrail): data: dict, call_type: str, # "completion", "embeddings", "image_generation", "moderation" ): - if await self.should_run_check(user_api_key_dict) is False: return if "messages" in data and isinstance(data["messages"], list): for message in data["messages"]: if "content" in message and isinstance(message["content"], str): - detected_secrets = self.scan_message_for_secrets(message["content"]) for secret in detected_secrets: diff --git a/litellm/__init__.py b/litellm/__init__.py index a4903f828c..c2e366e2b1 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -122,19 +122,19 @@ langsmith_batch_size: Optional[int] = None prometheus_initialize_budget_metrics: Optional[bool] = False argilla_batch_size: Optional[int] = None datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload -gcs_pub_sub_use_v1: Optional[bool] = ( - False # if you want to use v1 gcs pubsub logged payload -) +gcs_pub_sub_use_v1: Optional[ + bool +] = False # if you want to use v1 gcs pubsub logged payload argilla_transformation_object: Optional[Dict[str, Any]] = None -_async_input_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. -_async_success_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. -_async_failure_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. +_async_input_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. +_async_success_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. +_async_failure_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. pre_call_rules: List[Callable] = [] post_call_rules: List[Callable] = [] turn_off_message_logging: Optional[bool] = False @@ -142,18 +142,18 @@ log_raw_request_response: bool = False redact_messages_in_exceptions: Optional[bool] = False redact_user_api_key_info: Optional[bool] = False filter_invalid_headers: Optional[bool] = False -add_user_information_to_llm_headers: Optional[bool] = ( - None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers -) +add_user_information_to_llm_headers: Optional[ + bool +] = None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers store_audit_logs = False # Enterprise feature, allow users to see audit logs ### end of callbacks ############# -email: Optional[str] = ( - None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -token: Optional[str] = ( - None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) +email: Optional[ + str +] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +token: Optional[ + str +] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 telemetry = True max_tokens = 256 # OpenAI Defaults drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False)) @@ -229,24 +229,20 @@ enable_loadbalancing_on_batch_endpoints: Optional[bool] = None enable_caching_on_provider_specific_optional_params: bool = ( False # feature-flag for caching on optional params - e.g. 'top_k' ) -caching: bool = ( - False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -caching_with_models: bool = ( - False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -cache: Optional[Cache] = ( - None # cache object <- use this - https://docs.litellm.ai/docs/caching -) +caching: bool = False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +caching_with_models: bool = False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +cache: Optional[ + Cache +] = None # cache object <- use this - https://docs.litellm.ai/docs/caching default_in_memory_ttl: Optional[float] = None default_redis_ttl: Optional[float] = None default_redis_batch_cache_expiry: Optional[float] = None model_alias_map: Dict[str, str] = {} model_group_alias_map: Dict[str, str] = {} max_budget: float = 0.0 # set the max budget across all providers -budget_duration: Optional[str] = ( - None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). -) +budget_duration: Optional[ + str +] = None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). default_soft_budget: float = ( 50.0 # by default all litellm proxy keys have a soft budget of 50.0 ) @@ -255,15 +251,11 @@ forward_traceparent_to_llm_provider: bool = False _current_cost = 0.0 # private variable, used if max budget is set error_logs: Dict = {} -add_function_to_prompt: bool = ( - False # if function calling not supported by api, append function call details to system prompt -) +add_function_to_prompt: bool = False # if function calling not supported by api, append function call details to system prompt client_session: Optional[httpx.Client] = None aclient_session: Optional[httpx.AsyncClient] = None model_fallbacks: Optional[List] = None # Deprecated for 'litellm.fallbacks' -model_cost_map_url: str = ( - "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" -) +model_cost_map_url: str = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" suppress_debug_info = False dynamodb_table_name: Optional[str] = None s3_callback_params: Optional[Dict] = None @@ -285,9 +277,7 @@ disable_end_user_cost_tracking_prometheus_only: Optional[bool] = None custom_prometheus_metadata_labels: List[str] = [] #### REQUEST PRIORITIZATION #### priority_reservation: Optional[Dict[str, float]] = None -force_ipv4: bool = ( - False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. -) +force_ipv4: bool = False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. module_level_aclient = AsyncHTTPHandler( timeout=request_timeout, client_alias="module level aclient" ) @@ -301,13 +291,13 @@ fallbacks: Optional[List] = None context_window_fallbacks: Optional[List] = None content_policy_fallbacks: Optional[List] = None allowed_fails: int = 3 -num_retries_per_request: Optional[int] = ( - None # for the request overall (incl. fallbacks + model retries) -) +num_retries_per_request: Optional[ + int +] = None # for the request overall (incl. fallbacks + model retries) ####### SECRET MANAGERS ##################### -secret_manager_client: Optional[Any] = ( - None # list of instantiated key management clients - e.g. azure kv, infisical, etc. -) +secret_manager_client: Optional[ + Any +] = None # list of instantiated key management clients - e.g. azure kv, infisical, etc. _google_kms_resource_name: Optional[str] = None _key_management_system: Optional[KeyManagementSystem] = None _key_management_settings: KeyManagementSettings = KeyManagementSettings() @@ -1056,10 +1046,10 @@ from .types.llms.custom_llm import CustomLLMItem from .types.utils import GenericStreamingChunk custom_provider_map: List[CustomLLMItem] = [] -_custom_providers: List[str] = ( - [] -) # internal helper util, used to track names of custom providers -disable_hf_tokenizer_download: Optional[bool] = ( - None # disable huggingface tokenizer download. Defaults to openai clk100 -) +_custom_providers: List[ + str +] = [] # internal helper util, used to track names of custom providers +disable_hf_tokenizer_download: Optional[ + bool +] = None # disable huggingface tokenizer download. Defaults to openai clk100 global_disable_no_log_param: bool = False diff --git a/litellm/_service_logger.py b/litellm/_service_logger.py index 0b4f22e210..8f835bea83 100644 --- a/litellm/_service_logger.py +++ b/litellm/_service_logger.py @@ -15,7 +15,7 @@ from .types.services import ServiceLoggerPayload, ServiceTypes if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] OTELClass = OpenTelemetry else: Span = Any diff --git a/litellm/batches/main.py b/litellm/batches/main.py index 1ddcafce4c..f4f74c72fb 100644 --- a/litellm/batches/main.py +++ b/litellm/batches/main.py @@ -153,7 +153,6 @@ def create_batch( ) api_base: Optional[str] = None if custom_llm_provider == "openai": - # for deepinfra/perplexity/anyscale/groq we check in get_llm_provider and pass in the api base from there api_base = ( optional_params.api_base @@ -358,7 +357,6 @@ def retrieve_batch( _is_async = kwargs.pop("aretrieve_batch", False) is True api_base: Optional[str] = None if custom_llm_provider == "openai": - # for deepinfra/perplexity/anyscale/groq we check in get_llm_provider and pass in the api base from there api_base = ( optional_params.api_base diff --git a/litellm/caching/base_cache.py b/litellm/caching/base_cache.py index 7109951d15..5140b390f7 100644 --- a/litellm/caching/base_cache.py +++ b/litellm/caching/base_cache.py @@ -9,12 +9,12 @@ Has 4 methods: """ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/caching/caching_handler.py b/litellm/caching/caching_handler.py index 09fabf1c12..14278de9cd 100644 --- a/litellm/caching/caching_handler.py +++ b/litellm/caching/caching_handler.py @@ -66,9 +66,7 @@ class CachingHandlerResponse(BaseModel): cached_result: Optional[Any] = None final_embedding_cached_response: Optional[EmbeddingResponse] = None - embedding_all_elements_cache_hit: bool = ( - False # this is set to True when all elements in the list have a cache hit in the embedding cache, if true return the final_embedding_cached_response no need to make an API call - ) + embedding_all_elements_cache_hit: bool = False # this is set to True when all elements in the list have a cache hit in the embedding cache, if true return the final_embedding_cached_response no need to make an API call class LLMCachingHandler: @@ -738,7 +736,6 @@ class LLMCachingHandler: if self._should_store_result_in_cache( original_function=self.original_function, kwargs=new_kwargs ): - litellm.cache.add_cache(result, **new_kwargs) return @@ -865,9 +862,9 @@ class LLMCachingHandler: } if litellm.cache is not None: - litellm_params["preset_cache_key"] = ( - litellm.cache._get_preset_cache_key_from_kwargs(**kwargs) - ) + litellm_params[ + "preset_cache_key" + ] = litellm.cache._get_preset_cache_key_from_kwargs(**kwargs) else: litellm_params["preset_cache_key"] = None diff --git a/litellm/caching/disk_cache.py b/litellm/caching/disk_cache.py index abf3203f50..413ac2932d 100644 --- a/litellm/caching/disk_cache.py +++ b/litellm/caching/disk_cache.py @@ -1,12 +1,12 @@ import json -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union from .base_cache import BaseCache if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/caching/dual_cache.py b/litellm/caching/dual_cache.py index 5f598f7d70..8bef333758 100644 --- a/litellm/caching/dual_cache.py +++ b/litellm/caching/dual_cache.py @@ -12,7 +12,7 @@ import asyncio import time import traceback from concurrent.futures import ThreadPoolExecutor -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, Union import litellm from litellm._logging import print_verbose, verbose_logger @@ -24,7 +24,7 @@ from .redis_cache import RedisCache if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/caching/llm_caching_handler.py b/litellm/caching/llm_caching_handler.py index 429634b7b1..3bf1f80d08 100644 --- a/litellm/caching/llm_caching_handler.py +++ b/litellm/caching/llm_caching_handler.py @@ -8,7 +8,6 @@ from .in_memory_cache import InMemoryCache class LLMClientCache(InMemoryCache): - def update_cache_key_with_event_loop(self, key): """ Add the event loop to the cache key, to prevent event loop closed errors. diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index 7378ed878a..63cd4d0959 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: cluster_pipeline = ClusterPipeline async_redis_client = Redis async_redis_cluster_client = RedisCluster - Span = _Span + Span = Union[_Span, Any] else: pipeline = Any cluster_pipeline = Any @@ -57,7 +57,6 @@ class RedisCache(BaseCache): socket_timeout: Optional[float] = 5.0, # default 5 second timeout **kwargs, ): - from litellm._service_logger import ServiceLogging from .._redis import get_redis_client, get_redis_connection_pool diff --git a/litellm/caching/redis_cluster_cache.py b/litellm/caching/redis_cluster_cache.py index 2e7d1de17f..21c3ab0366 100644 --- a/litellm/caching/redis_cluster_cache.py +++ b/litellm/caching/redis_cluster_cache.py @@ -5,7 +5,7 @@ Key differences: - RedisClient NEEDs to be re-used across requests, adds 3000ms latency if it's re-created """ -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, Union from litellm.caching.redis_cache import RedisCache @@ -16,7 +16,7 @@ if TYPE_CHECKING: pipeline = Pipeline async_redis_client = Redis - Span = _Span + Span = Union[_Span, Any] else: pipeline = Any async_redis_client = Any diff --git a/litellm/caching/redis_semantic_cache.py b/litellm/caching/redis_semantic_cache.py index f46bb661ef..c76f27377d 100644 --- a/litellm/caching/redis_semantic_cache.py +++ b/litellm/caching/redis_semantic_cache.py @@ -13,23 +13,27 @@ import ast import asyncio import json import os -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple, cast import litellm from litellm._logging import print_verbose -from litellm.litellm_core_utils.prompt_templates.common_utils import get_str_from_messages +from litellm.litellm_core_utils.prompt_templates.common_utils import ( + get_str_from_messages, +) +from litellm.types.utils import EmbeddingResponse + from .base_cache import BaseCache class RedisSemanticCache(BaseCache): """ - Redis-backed semantic cache for LLM responses. - - This cache uses vector similarity to find semantically similar prompts that have been + Redis-backed semantic cache for LLM responses. + + This cache uses vector similarity to find semantically similar prompts that have been previously sent to the LLM, allowing for cache hits even when prompts are not identical but carry similar meaning. """ - + DEFAULT_REDIS_INDEX_NAME: str = "litellm_semantic_cache_index" def __init__( @@ -57,7 +61,7 @@ class RedisSemanticCache(BaseCache): index_name: Name for the Redis index ttl: Default time-to-live for cache entries in seconds **kwargs: Additional arguments passed to the Redis client - + Raises: Exception: If similarity_threshold is not provided or required Redis connection information is missing @@ -69,14 +73,14 @@ class RedisSemanticCache(BaseCache): index_name = self.DEFAULT_REDIS_INDEX_NAME print_verbose(f"Redis semantic-cache initializing index - {index_name}") - + # Validate similarity threshold if similarity_threshold is None: raise ValueError("similarity_threshold must be provided, passed None") - + # Store configuration self.similarity_threshold = similarity_threshold - + # Convert similarity threshold [0,1] to distance threshold [0,2] # For cosine distance: 0 = most similar, 2 = least similar # While similarity: 1 = most similar, 0 = least similar @@ -87,14 +91,16 @@ class RedisSemanticCache(BaseCache): if redis_url is None: try: # Attempt to use provided parameters or fallback to environment variables - host = host or os.environ['REDIS_HOST'] - port = port or os.environ['REDIS_PORT'] - password = password or os.environ['REDIS_PASSWORD'] + host = host or os.environ["REDIS_HOST"] + port = port or os.environ["REDIS_PORT"] + password = password or os.environ["REDIS_PASSWORD"] except KeyError as e: # Raise a more informative exception if any of the required keys are missing missing_var = e.args[0] - raise ValueError(f"Missing required Redis configuration: {missing_var}. " - f"Provide {missing_var} or redis_url.") from e + raise ValueError( + f"Missing required Redis configuration: {missing_var}. " + f"Provide {missing_var} or redis_url." + ) from e redis_url = f"redis://:{password}@{host}:{port}" @@ -114,7 +120,7 @@ class RedisSemanticCache(BaseCache): def _get_ttl(self, **kwargs) -> Optional[int]: """ Get the TTL (time-to-live) value for cache entries. - + Args: **kwargs: Keyword arguments that may contain a custom TTL @@ -125,22 +131,25 @@ class RedisSemanticCache(BaseCache): if ttl is not None: ttl = int(ttl) return ttl - + def _get_embedding(self, prompt: str) -> List[float]: """ Generate an embedding vector for the given prompt using the configured embedding model. - + Args: prompt: The text to generate an embedding for - + Returns: List[float]: The embedding vector """ # Create an embedding from prompt - embedding_response = litellm.embedding( - model=self.embedding_model, - input=prompt, - cache={"no-store": True, "no-cache": True}, + embedding_response = cast( + EmbeddingResponse, + litellm.embedding( + model=self.embedding_model, + input=prompt, + cache={"no-store": True, "no-cache": True}, + ), ) embedding = embedding_response["data"][0]["embedding"] return embedding @@ -148,10 +157,10 @@ class RedisSemanticCache(BaseCache): def _get_cache_logic(self, cached_response: Any) -> Any: """ Process the cached response to prepare it for use. - + Args: cached_response: The raw cached response - + Returns: The processed cache response, or None if input was None """ @@ -171,13 +180,13 @@ class RedisSemanticCache(BaseCache): except (ValueError, SyntaxError) as e: print_verbose(f"Error parsing cached response: {str(e)}") return None - + return cached_response def set_cache(self, key: str, value: Any, **kwargs) -> None: """ Store a value in the semantic cache. - + Args: key: The cache key (not directly used in semantic caching) value: The response value to cache @@ -186,13 +195,14 @@ class RedisSemanticCache(BaseCache): """ print_verbose(f"Redis semantic-cache set_cache, kwargs: {kwargs}") + value_str: Optional[str] = None try: # Extract the prompt from messages messages = kwargs.get("messages", []) if not messages: print_verbose("No messages provided for semantic caching") return - + prompt = get_str_from_messages(messages) value_str = str(value) @@ -203,16 +213,18 @@ class RedisSemanticCache(BaseCache): else: self.llmcache.store(prompt, value_str) except Exception as e: - print_verbose(f"Error setting {value_str} in the Redis semantic cache: {str(e)}") + print_verbose( + f"Error setting {value_str or value} in the Redis semantic cache: {str(e)}" + ) def get_cache(self, key: str, **kwargs) -> Any: """ Retrieve a semantically similar cached response. - + Args: key: The cache key (not directly used in semantic caching) **kwargs: Additional arguments including 'messages' for the prompt - + Returns: The cached response if a semantically similar prompt is found, else None """ @@ -224,7 +236,7 @@ class RedisSemanticCache(BaseCache): if not messages: print_verbose("No messages provided for semantic cache lookup") return None - + prompt = get_str_from_messages(messages) # Check the cache for semantically similar prompts results = self.llmcache.check(prompt=prompt) @@ -236,12 +248,12 @@ class RedisSemanticCache(BaseCache): # Process the best matching result cache_hit = results[0] vector_distance = float(cache_hit["vector_distance"]) - + # Convert vector distance back to similarity score # For cosine distance: 0 = most similar, 2 = least similar # While similarity: 1 = most similar, 0 = least similar similarity = 1 - vector_distance - + cached_prompt = cache_hit["prompt"] cached_response = cache_hit["response"] @@ -251,19 +263,19 @@ class RedisSemanticCache(BaseCache): f"current prompt: {prompt}, " f"cached prompt: {cached_prompt}" ) - + return self._get_cache_logic(cached_response=cached_response) except Exception as e: print_verbose(f"Error retrieving from Redis semantic cache: {str(e)}") - + async def _get_async_embedding(self, prompt: str, **kwargs) -> List[float]: """ Asynchronously generate an embedding for the given prompt. - + Args: prompt: The text to generate an embedding for **kwargs: Additional arguments that may contain metadata - + Returns: List[float]: The embedding vector """ @@ -275,7 +287,7 @@ class RedisSemanticCache(BaseCache): if llm_model_list is not None else [] ) - + try: if llm_router is not None and self.embedding_model in router_model_names: # Use the router for embedding generation @@ -307,7 +319,7 @@ class RedisSemanticCache(BaseCache): async def async_set_cache(self, key: str, value: Any, **kwargs) -> None: """ Asynchronously store a value in the semantic cache. - + Args: key: The cache key (not directly used in semantic caching) value: The response value to cache @@ -322,13 +334,13 @@ class RedisSemanticCache(BaseCache): if not messages: print_verbose("No messages provided for semantic caching") return - + prompt = get_str_from_messages(messages) value_str = str(value) # Generate embedding for the value (response) to cache prompt_embedding = await self._get_async_embedding(prompt, **kwargs) - + # Get TTL and store in Redis semantic cache ttl = self._get_ttl(**kwargs) if ttl is not None: @@ -336,13 +348,13 @@ class RedisSemanticCache(BaseCache): prompt, value_str, vector=prompt_embedding, # Pass through custom embedding - ttl=ttl + ttl=ttl, ) else: await self.llmcache.astore( prompt, value_str, - vector=prompt_embedding # Pass through custom embedding + vector=prompt_embedding, # Pass through custom embedding ) except Exception as e: print_verbose(f"Error in async_set_cache: {str(e)}") @@ -350,11 +362,11 @@ class RedisSemanticCache(BaseCache): async def async_get_cache(self, key: str, **kwargs) -> Any: """ Asynchronously retrieve a semantically similar cached response. - + Args: key: The cache key (not directly used in semantic caching) **kwargs: Additional arguments including 'messages' for the prompt - + Returns: The cached response if a semantically similar prompt is found, else None """ @@ -367,21 +379,20 @@ class RedisSemanticCache(BaseCache): print_verbose("No messages provided for semantic cache lookup") kwargs.setdefault("metadata", {})["semantic-similarity"] = 0.0 return None - + prompt = get_str_from_messages(messages) - + # Generate embedding for the prompt prompt_embedding = await self._get_async_embedding(prompt, **kwargs) # Check the cache for semantically similar prompts - results = await self.llmcache.acheck( - prompt=prompt, - vector=prompt_embedding - ) + results = await self.llmcache.acheck(prompt=prompt, vector=prompt_embedding) # handle results / cache hit if not results: - kwargs.setdefault("metadata", {})["semantic-similarity"] = 0.0 # TODO why here but not above?? + kwargs.setdefault("metadata", {})[ + "semantic-similarity" + ] = 0.0 # TODO why here but not above?? return None cache_hit = results[0] @@ -404,7 +415,7 @@ class RedisSemanticCache(BaseCache): f"current prompt: {prompt}, " f"cached prompt: {cached_prompt}" ) - + return self._get_cache_logic(cached_response=cached_response) except Exception as e: print_verbose(f"Error in async_get_cache: {str(e)}") @@ -413,17 +424,19 @@ class RedisSemanticCache(BaseCache): async def _index_info(self) -> Dict[str, Any]: """ Get information about the Redis index. - + Returns: Dict[str, Any]: Information about the Redis index """ aindex = await self.llmcache._get_async_index() return await aindex.info() - async def async_set_cache_pipeline(self, cache_list: List[Tuple[str, Any]], **kwargs) -> None: + async def async_set_cache_pipeline( + self, cache_list: List[Tuple[str, Any]], **kwargs + ) -> None: """ Asynchronously store multiple values in the semantic cache. - + Args: cache_list: List of (key, value) tuples to cache **kwargs: Additional arguments diff --git a/litellm/caching/s3_cache.py b/litellm/caching/s3_cache.py index 301591c64f..c02e109136 100644 --- a/litellm/caching/s3_cache.py +++ b/litellm/caching/s3_cache.py @@ -123,7 +123,7 @@ class S3Cache(BaseCache): ) # Convert string to dictionary except Exception: cached_response = ast.literal_eval(cached_response) - if type(cached_response) is not dict: + if not isinstance(cached_response, dict): cached_response = dict(cached_response) verbose_logger.debug( f"Got S3 Cache: key: {key}, cached_response {cached_response}. Type Response {type(cached_response)}" diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index f343591443..a41fc364ab 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -580,7 +580,6 @@ def completion_cost( # noqa: PLR0915 - For un-mapped Replicate models, the cost is calculated based on the total time used for the request. """ try: - call_type = _infer_call_type(call_type, completion_response) or "completion" if ( diff --git a/litellm/fine_tuning/main.py b/litellm/fine_tuning/main.py index b726a394c2..09c070fffb 100644 --- a/litellm/fine_tuning/main.py +++ b/litellm/fine_tuning/main.py @@ -138,7 +138,6 @@ def create_fine_tuning_job( # OpenAI if custom_llm_provider == "openai": - # for deepinfra/perplexity/anyscale/groq we check in get_llm_provider and pass in the api base from there api_base = ( optional_params.api_base @@ -360,7 +359,6 @@ def cancel_fine_tuning_job( # OpenAI if custom_llm_provider == "openai": - # for deepinfra/perplexity/anyscale/groq we check in get_llm_provider and pass in the api base from there api_base = ( optional_params.api_base @@ -522,7 +520,6 @@ def list_fine_tuning_jobs( # OpenAI if custom_llm_provider == "openai": - # for deepinfra/perplexity/anyscale/groq we check in get_llm_provider and pass in the api base from there api_base = ( optional_params.api_base diff --git a/litellm/integrations/SlackAlerting/batching_handler.py b/litellm/integrations/SlackAlerting/batching_handler.py index e35cf61d63..fdce2e0479 100644 --- a/litellm/integrations/SlackAlerting/batching_handler.py +++ b/litellm/integrations/SlackAlerting/batching_handler.py @@ -19,7 +19,6 @@ else: def squash_payloads(queue): - squashed = {} if len(queue) == 0: return squashed diff --git a/litellm/integrations/SlackAlerting/slack_alerting.py b/litellm/integrations/SlackAlerting/slack_alerting.py index a2e6264760..50f0538cfd 100644 --- a/litellm/integrations/SlackAlerting/slack_alerting.py +++ b/litellm/integrations/SlackAlerting/slack_alerting.py @@ -195,12 +195,15 @@ class SlackAlerting(CustomBatchLogger): if self.alerting is None or self.alert_types is None: return - time_difference_float, model, api_base, messages = ( - self._response_taking_too_long_callback_helper( - kwargs=kwargs, - start_time=start_time, - end_time=end_time, - ) + ( + time_difference_float, + model, + api_base, + messages, + ) = self._response_taking_too_long_callback_helper( + kwargs=kwargs, + start_time=start_time, + end_time=end_time, ) if litellm.turn_off_message_logging or litellm.redact_messages_in_exceptions: messages = "Message not logged. litellm.redact_messages_in_exceptions=True" @@ -819,9 +822,9 @@ class SlackAlerting(CustomBatchLogger): ### UNIQUE CACHE KEY ### cache_key = provider + region_name - outage_value: Optional[ProviderRegionOutageModel] = ( - await self.internal_usage_cache.async_get_cache(key=cache_key) - ) + outage_value: Optional[ + ProviderRegionOutageModel + ] = await self.internal_usage_cache.async_get_cache(key=cache_key) if ( getattr(exception, "status_code", None) is None @@ -1402,9 +1405,9 @@ Model Info: self.alert_to_webhook_url is not None and alert_type in self.alert_to_webhook_url ): - slack_webhook_url: Optional[Union[str, List[str]]] = ( - self.alert_to_webhook_url[alert_type] - ) + slack_webhook_url: Optional[ + Union[str, List[str]] + ] = self.alert_to_webhook_url[alert_type] elif self.default_webhook_url is not None: slack_webhook_url = self.default_webhook_url else: @@ -1768,7 +1771,6 @@ Model Info: - Team Created, Updated, Deleted """ try: - message = f"`{event_name}`\n" key_event_dict = key_event.model_dump() diff --git a/litellm/integrations/_types/open_inference.py b/litellm/integrations/_types/open_inference.py index b5076c0e42..bcfabe9b7b 100644 --- a/litellm/integrations/_types/open_inference.py +++ b/litellm/integrations/_types/open_inference.py @@ -283,4 +283,4 @@ class OpenInferenceSpanKindValues(Enum): class OpenInferenceMimeTypeValues(Enum): TEXT = "text/plain" - JSON = "application/json" \ No newline at end of file + JSON = "application/json" diff --git a/litellm/integrations/argilla.py b/litellm/integrations/argilla.py index 055ad90259..a362ce7e4d 100644 --- a/litellm/integrations/argilla.py +++ b/litellm/integrations/argilla.py @@ -98,7 +98,6 @@ class ArgillaLogger(CustomBatchLogger): argilla_dataset_name: Optional[str], argilla_base_url: Optional[str], ) -> ArgillaCredentialsObject: - _credentials_api_key = argilla_api_key or os.getenv("ARGILLA_API_KEY") if _credentials_api_key is None: raise Exception("Invalid Argilla API Key given. _credentials_api_key=None.") diff --git a/litellm/integrations/arize/_utils.py b/litellm/integrations/arize/_utils.py index 487304cce4..5a090968b4 100644 --- a/litellm/integrations/arize/_utils.py +++ b/litellm/integrations/arize/_utils.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union from litellm._logging import verbose_logger from litellm.litellm_core_utils.safe_json_dumps import safe_dumps @@ -7,7 +7,7 @@ from litellm.types.utils import StandardLoggingPayload if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/integrations/arize/arize.py b/litellm/integrations/arize/arize.py index 7a0fb785a7..03b6966809 100644 --- a/litellm/integrations/arize/arize.py +++ b/litellm/integrations/arize/arize.py @@ -19,14 +19,13 @@ if TYPE_CHECKING: from litellm.types.integrations.arize import Protocol as _Protocol Protocol = _Protocol - Span = _Span + Span = Union[_Span, Any] else: Protocol = Any Span = Any class ArizeLogger(OpenTelemetry): - def set_attributes(self, span: Span, kwargs, response_obj: Optional[Any]): ArizeLogger.set_arize_attributes(span, kwargs, response_obj) return diff --git a/litellm/integrations/arize/arize_phoenix.py b/litellm/integrations/arize/arize_phoenix.py index d7b7d5812b..2b4909885a 100644 --- a/litellm/integrations/arize/arize_phoenix.py +++ b/litellm/integrations/arize/arize_phoenix.py @@ -1,17 +1,20 @@ import os -from typing import TYPE_CHECKING, Any -from litellm.integrations.arize import _utils +from typing import TYPE_CHECKING, Any, Union + from litellm._logging import verbose_logger +from litellm.integrations.arize import _utils from litellm.types.integrations.arize_phoenix import ArizePhoenixConfig if TYPE_CHECKING: - from .opentelemetry import OpenTelemetryConfig as _OpenTelemetryConfig - from litellm.types.integrations.arize import Protocol as _Protocol from opentelemetry.trace import Span as _Span + from litellm.types.integrations.arize import Protocol as _Protocol + + from .opentelemetry import OpenTelemetryConfig as _OpenTelemetryConfig + Protocol = _Protocol OpenTelemetryConfig = _OpenTelemetryConfig - Span = _Span + Span = Union[_Span, Any] else: Protocol = Any OpenTelemetryConfig = Any @@ -20,6 +23,7 @@ else: ARIZE_HOSTED_PHOENIX_ENDPOINT = "https://app.phoenix.arize.com/v1/traces" + class ArizePhoenixLogger: @staticmethod def set_arize_phoenix_attributes(span: Span, kwargs, response_obj): @@ -49,7 +53,7 @@ class ArizePhoenixLogger: protocol = "otlp_grpc" else: endpoint = ARIZE_HOSTED_PHOENIX_ENDPOINT - protocol = "otlp_http" + protocol = "otlp_http" verbose_logger.debug( f"No PHOENIX_COLLECTOR_ENDPOINT or PHOENIX_COLLECTOR_HTTP_ENDPOINT found, using default endpoint with http: {ARIZE_HOSTED_PHOENIX_ENDPOINT}" ) @@ -57,17 +61,16 @@ class ArizePhoenixLogger: otlp_auth_headers = None # If the endpoint is the Arize hosted Phoenix endpoint, use the api_key as the auth header as currently it is uses # a slightly different auth header format than self hosted phoenix - if endpoint == ARIZE_HOSTED_PHOENIX_ENDPOINT: + if endpoint == ARIZE_HOSTED_PHOENIX_ENDPOINT: if api_key is None: - raise ValueError("PHOENIX_API_KEY must be set when the Arize hosted Phoenix endpoint is used.") + raise ValueError( + "PHOENIX_API_KEY must be set when the Arize hosted Phoenix endpoint is used." + ) otlp_auth_headers = f"api_key={api_key}" elif api_key is not None: # api_key/auth is optional for self hosted phoenix otlp_auth_headers = f"Authorization=Bearer {api_key}" return ArizePhoenixConfig( - otlp_auth_headers=otlp_auth_headers, - protocol=protocol, - endpoint=endpoint + otlp_auth_headers=otlp_auth_headers, protocol=protocol, endpoint=endpoint ) - diff --git a/litellm/integrations/athina.py b/litellm/integrations/athina.py index 705dc11f1d..49b9e9e687 100644 --- a/litellm/integrations/athina.py +++ b/litellm/integrations/athina.py @@ -12,7 +12,10 @@ class AthinaLogger: "athina-api-key": self.athina_api_key, "Content-Type": "application/json", } - self.athina_logging_url = os.getenv("ATHINA_BASE_URL", "https://log.athina.ai") + "/api/v1/log/inference" + self.athina_logging_url = ( + os.getenv("ATHINA_BASE_URL", "https://log.athina.ai") + + "/api/v1/log/inference" + ) self.additional_keys = [ "environment", "prompt_slug", diff --git a/litellm/integrations/azure_storage/azure_storage.py b/litellm/integrations/azure_storage/azure_storage.py index ddc46b117f..27f5e0e112 100644 --- a/litellm/integrations/azure_storage/azure_storage.py +++ b/litellm/integrations/azure_storage/azure_storage.py @@ -50,12 +50,12 @@ class AzureBlobStorageLogger(CustomBatchLogger): self.azure_storage_file_system: str = _azure_storage_file_system # Internal variables used for Token based authentication - self.azure_auth_token: Optional[str] = ( - None # the Azure AD token to use for Azure Storage API requests - ) - self.token_expiry: Optional[datetime] = ( - None # the expiry time of the currentAzure AD token - ) + self.azure_auth_token: Optional[ + str + ] = None # the Azure AD token to use for Azure Storage API requests + self.token_expiry: Optional[ + datetime + ] = None # the expiry time of the currentAzure AD token asyncio.create_task(self.periodic_flush()) self.flush_lock = asyncio.Lock() @@ -153,7 +153,6 @@ class AzureBlobStorageLogger(CustomBatchLogger): 3. Flush the data """ try: - if self.azure_storage_account_key: await self.upload_to_azure_data_lake_with_azure_account_key( payload=payload diff --git a/litellm/integrations/braintrust_logging.py b/litellm/integrations/braintrust_logging.py index 281fbda01e..0961eab02b 100644 --- a/litellm/integrations/braintrust_logging.py +++ b/litellm/integrations/braintrust_logging.py @@ -4,7 +4,7 @@ import copy import os from datetime import datetime -from typing import Optional, Dict +from typing import Dict, Optional import httpx from pydantic import BaseModel @@ -19,7 +19,9 @@ from litellm.llms.custom_httpx.http_handler import ( ) from litellm.utils import print_verbose -global_braintrust_http_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.LoggingCallback) +global_braintrust_http_handler = get_async_httpx_client( + llm_provider=httpxSpecialProvider.LoggingCallback +) global_braintrust_sync_http_handler = HTTPHandler() API_BASE = "https://api.braintrustdata.com/v1" @@ -35,7 +37,9 @@ def get_utc_datetime(): class BraintrustLogger(CustomLogger): - def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> None: + def __init__( + self, api_key: Optional[str] = None, api_base: Optional[str] = None + ) -> None: super().__init__() self.validate_environment(api_key=api_key) self.api_base = api_base or API_BASE @@ -45,7 +49,9 @@ class BraintrustLogger(CustomLogger): "Authorization": "Bearer " + self.api_key, "Content-Type": "application/json", } - self._project_id_cache: Dict[str, str] = {} # Cache mapping project names to IDs + self._project_id_cache: Dict[ + str, str + ] = {} # Cache mapping project names to IDs def validate_environment(self, api_key: Optional[str]): """ @@ -71,7 +77,9 @@ class BraintrustLogger(CustomLogger): try: response = global_braintrust_sync_http_handler.post( - f"{self.api_base}/project", headers=self.headers, json={"name": project_name} + f"{self.api_base}/project", + headers=self.headers, + json={"name": project_name}, ) project_dict = response.json() project_id = project_dict["id"] @@ -89,7 +97,9 @@ class BraintrustLogger(CustomLogger): try: response = await global_braintrust_http_handler.post( - f"{self.api_base}/project/register", headers=self.headers, json={"name": project_name} + f"{self.api_base}/project/register", + headers=self.headers, + json={"name": project_name}, ) project_dict = response.json() project_id = project_dict["id"] @@ -116,15 +126,21 @@ class BraintrustLogger(CustomLogger): if metadata is None: metadata = {} - proxy_headers = litellm_params.get("proxy_server_request", {}).get("headers", {}) or {} + proxy_headers = ( + litellm_params.get("proxy_server_request", {}).get("headers", {}) or {} + ) for metadata_param_key in proxy_headers: if metadata_param_key.startswith("braintrust"): trace_param_key = metadata_param_key.replace("braintrust", "", 1) if trace_param_key in metadata: - verbose_logger.warning(f"Overwriting Braintrust `{trace_param_key}` from request header") + verbose_logger.warning( + f"Overwriting Braintrust `{trace_param_key}` from request header" + ) else: - verbose_logger.debug(f"Found Braintrust `{trace_param_key}` in request header") + verbose_logger.debug( + f"Found Braintrust `{trace_param_key}` in request header" + ) metadata[trace_param_key] = proxy_headers.get(metadata_param_key) return metadata @@ -157,24 +173,35 @@ class BraintrustLogger(CustomLogger): output = None choices = [] if response_obj is not None and ( - kwargs.get("call_type", None) == "embedding" or isinstance(response_obj, litellm.EmbeddingResponse) + kwargs.get("call_type", None) == "embedding" + or isinstance(response_obj, litellm.EmbeddingResponse) ): output = None - elif response_obj is not None and isinstance(response_obj, litellm.ModelResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.ModelResponse + ): output = response_obj["choices"][0]["message"].json() choices = response_obj["choices"] - elif response_obj is not None and isinstance(response_obj, litellm.TextCompletionResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.TextCompletionResponse + ): output = response_obj.choices[0].text choices = response_obj.choices - elif response_obj is not None and isinstance(response_obj, litellm.ImageResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.ImageResponse + ): output = response_obj["data"] litellm_params = kwargs.get("litellm_params", {}) - metadata = litellm_params.get("metadata", {}) or {} # if litellm_params['metadata'] == None + metadata = ( + litellm_params.get("metadata", {}) or {} + ) # if litellm_params['metadata'] == None metadata = self.add_metadata_from_header(litellm_params, metadata) clean_metadata = {} try: - metadata = copy.deepcopy(metadata) # Avoid modifying the original metadata + metadata = copy.deepcopy( + metadata + ) # Avoid modifying the original metadata except Exception: new_metadata = {} for key, value in metadata.items(): @@ -192,7 +219,9 @@ class BraintrustLogger(CustomLogger): project_id = metadata.get("project_id") if project_id is None: project_name = metadata.get("project_name") - project_id = self.get_project_id_sync(project_name) if project_name else None + project_id = ( + self.get_project_id_sync(project_name) if project_name else None + ) if project_id is None: if self.default_project_id is None: @@ -234,7 +263,8 @@ class BraintrustLogger(CustomLogger): "completion_tokens": usage_obj.completion_tokens, "total_tokens": usage_obj.total_tokens, "total_cost": cost, - "time_to_first_token": end_time.timestamp() - start_time.timestamp(), + "time_to_first_token": end_time.timestamp() + - start_time.timestamp(), "start": start_time.timestamp(), "end": end_time.timestamp(), } @@ -255,7 +285,9 @@ class BraintrustLogger(CustomLogger): request_data["metrics"] = metrics try: - print_verbose(f"global_braintrust_sync_http_handler.post: {global_braintrust_sync_http_handler.post}") + print_verbose( + f"global_braintrust_sync_http_handler.post: {global_braintrust_sync_http_handler.post}" + ) global_braintrust_sync_http_handler.post( url=f"{self.api_base}/project_logs/{project_id}/insert", json={"events": [request_data]}, @@ -276,20 +308,29 @@ class BraintrustLogger(CustomLogger): output = None choices = [] if response_obj is not None and ( - kwargs.get("call_type", None) == "embedding" or isinstance(response_obj, litellm.EmbeddingResponse) + kwargs.get("call_type", None) == "embedding" + or isinstance(response_obj, litellm.EmbeddingResponse) ): output = None - elif response_obj is not None and isinstance(response_obj, litellm.ModelResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.ModelResponse + ): output = response_obj["choices"][0]["message"].json() choices = response_obj["choices"] - elif response_obj is not None and isinstance(response_obj, litellm.TextCompletionResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.TextCompletionResponse + ): output = response_obj.choices[0].text choices = response_obj.choices - elif response_obj is not None and isinstance(response_obj, litellm.ImageResponse): + elif response_obj is not None and isinstance( + response_obj, litellm.ImageResponse + ): output = response_obj["data"] litellm_params = kwargs.get("litellm_params", {}) - metadata = litellm_params.get("metadata", {}) or {} # if litellm_params['metadata'] == None + metadata = ( + litellm_params.get("metadata", {}) or {} + ) # if litellm_params['metadata'] == None metadata = self.add_metadata_from_header(litellm_params, metadata) clean_metadata = {} new_metadata = {} @@ -313,7 +354,11 @@ class BraintrustLogger(CustomLogger): project_id = metadata.get("project_id") if project_id is None: project_name = metadata.get("project_name") - project_id = await self.get_project_id_async(project_name) if project_name else None + project_id = ( + await self.get_project_id_async(project_name) + if project_name + else None + ) if project_id is None: if self.default_project_id is None: @@ -362,8 +407,14 @@ class BraintrustLogger(CustomLogger): api_call_start_time = kwargs.get("api_call_start_time") completion_start_time = kwargs.get("completion_start_time") - if api_call_start_time is not None and completion_start_time is not None: - metrics["time_to_first_token"] = completion_start_time.timestamp() - api_call_start_time.timestamp() + if ( + api_call_start_time is not None + and completion_start_time is not None + ): + metrics["time_to_first_token"] = ( + completion_start_time.timestamp() + - api_call_start_time.timestamp() + ) request_data = { "id": litellm_call_id, diff --git a/litellm/integrations/custom_batch_logger.py b/litellm/integrations/custom_batch_logger.py index 3cfdf82cab..f9d4496c21 100644 --- a/litellm/integrations/custom_batch_logger.py +++ b/litellm/integrations/custom_batch_logger.py @@ -14,7 +14,6 @@ from litellm.integrations.custom_logger import CustomLogger class CustomBatchLogger(CustomLogger): - def __init__( self, flush_lock: Optional[asyncio.Lock] = None, diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 4421664bfc..41a3800116 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -7,7 +7,6 @@ from litellm.types.utils import StandardLoggingGuardrailInformation class CustomGuardrail(CustomLogger): - def __init__( self, guardrail_name: Optional[str] = None, diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index 6f1ec88d01..ddb8094285 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -31,7 +31,7 @@ from litellm.types.utils import ( if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 4f4b05c84e..e9b6b6b164 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -233,7 +233,6 @@ class DataDogLogger( pass async def _log_async_event(self, kwargs, response_obj, start_time, end_time): - dd_payload = self.create_datadog_logging_payload( kwargs=kwargs, response_obj=response_obj, diff --git a/litellm/integrations/gcs_bucket/gcs_bucket_base.py b/litellm/integrations/gcs_bucket/gcs_bucket_base.py index 66995d8482..0ce845ecb2 100644 --- a/litellm/integrations/gcs_bucket/gcs_bucket_base.py +++ b/litellm/integrations/gcs_bucket/gcs_bucket_base.py @@ -125,9 +125,9 @@ class GCSBucketBase(CustomBatchLogger): if kwargs is None: kwargs = {} - standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = ( - kwargs.get("standard_callback_dynamic_params", None) - ) + standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = kwargs.get("standard_callback_dynamic_params", None) bucket_name: str path_service_account: Optional[str] diff --git a/litellm/integrations/gcs_pubsub/pub_sub.py b/litellm/integrations/gcs_pubsub/pub_sub.py index 1b078df7bc..bdaedcd908 100644 --- a/litellm/integrations/gcs_pubsub/pub_sub.py +++ b/litellm/integrations/gcs_pubsub/pub_sub.py @@ -70,12 +70,13 @@ class GcsPubSubLogger(CustomBatchLogger): """Construct authorization headers using Vertex AI auth""" from litellm import vertex_chat_completion - _auth_header, vertex_project = ( - await vertex_chat_completion._ensure_access_token_async( - credentials=self.path_service_account_json, - project_id=None, - custom_llm_provider="vertex_ai", - ) + ( + _auth_header, + vertex_project, + ) = await vertex_chat_completion._ensure_access_token_async( + credentials=self.path_service_account_json, + project_id=None, + custom_llm_provider="vertex_ai", ) auth_header, _ = vertex_chat_completion._get_token_and_url( diff --git a/litellm/integrations/humanloop.py b/litellm/integrations/humanloop.py index fd3463f9e3..4651238af4 100644 --- a/litellm/integrations/humanloop.py +++ b/litellm/integrations/humanloop.py @@ -155,11 +155,7 @@ class HumanloopLogger(CustomLogger): prompt_id: str, prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, - ) -> Tuple[ - str, - List[AllMessageValues], - dict, - ]: + ) -> Tuple[str, List[AllMessageValues], dict,]: humanloop_api_key = dynamic_callback_params.get( "humanloop_api_key" ) or get_secret_str("HUMANLOOP_API_KEY") diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index f990a316c4..d0472ee638 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -471,9 +471,9 @@ class LangFuseLogger: # we clean out all extra litellm metadata params before logging clean_metadata: Dict[str, Any] = {} if prompt_management_metadata is not None: - clean_metadata["prompt_management_metadata"] = ( - prompt_management_metadata - ) + clean_metadata[ + "prompt_management_metadata" + ] = prompt_management_metadata if isinstance(metadata, dict): for key, value in metadata.items(): # generate langfuse tags - Default Tags sent to Langfuse from LiteLLM Proxy diff --git a/litellm/integrations/langfuse/langfuse_handler.py b/litellm/integrations/langfuse/langfuse_handler.py index aebe1461b0..f9d27f6cf0 100644 --- a/litellm/integrations/langfuse/langfuse_handler.py +++ b/litellm/integrations/langfuse/langfuse_handler.py @@ -19,7 +19,6 @@ else: class LangFuseHandler: - @staticmethod def get_langfuse_logger_for_request( standard_callback_dynamic_params: StandardCallbackDynamicParams, @@ -87,7 +86,9 @@ class LangFuseHandler: if globalLangfuseLogger is not None: return globalLangfuseLogger - credentials_dict: Dict[str, Any] = ( + credentials_dict: Dict[ + str, Any + ] = ( {} ) # the global langfuse logger uses Environment Variables, there are no dynamic credentials globalLangfuseLogger = in_memory_dynamic_logger_cache.get_cache( diff --git a/litellm/integrations/langfuse/langfuse_prompt_management.py b/litellm/integrations/langfuse/langfuse_prompt_management.py index 1f4ca84db3..30f991ebd6 100644 --- a/litellm/integrations/langfuse/langfuse_prompt_management.py +++ b/litellm/integrations/langfuse/langfuse_prompt_management.py @@ -172,11 +172,7 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge prompt_id: str, prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, - ) -> Tuple[ - str, - List[AllMessageValues], - dict, - ]: + ) -> Tuple[str, List[AllMessageValues], dict,]: return self.get_chat_completion_prompt( model, messages, diff --git a/litellm/integrations/langsmith.py b/litellm/integrations/langsmith.py index 1ef90c1822..0914150db9 100644 --- a/litellm/integrations/langsmith.py +++ b/litellm/integrations/langsmith.py @@ -75,7 +75,6 @@ class LangsmithLogger(CustomBatchLogger): langsmith_project: Optional[str] = None, langsmith_base_url: Optional[str] = None, ) -> LangsmithCredentialsObject: - _credentials_api_key = langsmith_api_key or os.getenv("LANGSMITH_API_KEY") if _credentials_api_key is None: raise Exception( @@ -443,9 +442,9 @@ class LangsmithLogger(CustomBatchLogger): Otherwise, use the default credentials. """ - standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = ( - kwargs.get("standard_callback_dynamic_params", None) - ) + standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = kwargs.get("standard_callback_dynamic_params", None) if standard_callback_dynamic_params is not None: credentials = self.get_credentials_from_env( langsmith_api_key=standard_callback_dynamic_params.get( @@ -481,7 +480,6 @@ class LangsmithLogger(CustomBatchLogger): asyncio.run(self.async_send_batch()) def get_run_by_id(self, run_id): - langsmith_api_key = self.default_credentials["LANGSMITH_API_KEY"] langsmith_api_base = self.default_credentials["LANGSMITH_BASE_URL"] diff --git a/litellm/integrations/langtrace.py b/litellm/integrations/langtrace.py index 51cd272ff1..ac1069f440 100644 --- a/litellm/integrations/langtrace.py +++ b/litellm/integrations/langtrace.py @@ -1,12 +1,12 @@ import json -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Union from litellm.proxy._types import SpanAttributes if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/integrations/lunary.py b/litellm/integrations/lunary.py index fcd781e44e..b24a24e088 100644 --- a/litellm/integrations/lunary.py +++ b/litellm/integrations/lunary.py @@ -20,7 +20,6 @@ def parse_tool_calls(tool_calls): return None def clean_tool_call(tool_call): - serialized = { "type": tool_call.type, "id": tool_call.id, @@ -36,7 +35,6 @@ def parse_tool_calls(tool_calls): def parse_messages(input): - if input is None: return None diff --git a/litellm/integrations/mlflow.py b/litellm/integrations/mlflow.py index 193d1c4ea2..e7a458accf 100644 --- a/litellm/integrations/mlflow.py +++ b/litellm/integrations/mlflow.py @@ -48,14 +48,17 @@ class MlflowLogger(CustomLogger): def _extract_and_set_chat_attributes(self, span, kwargs, response_obj): try: - from mlflow.tracing.utils import set_span_chat_messages, set_span_chat_tools + from mlflow.tracing.utils import set_span_chat_messages # type: ignore + from mlflow.tracing.utils import set_span_chat_tools # type: ignore except ImportError: return inputs = self._construct_input(kwargs) input_messages = inputs.get("messages", []) - output_messages = [c.message.model_dump(exclude_none=True) - for c in getattr(response_obj, "choices", [])] + output_messages = [ + c.message.model_dump(exclude_none=True) + for c in getattr(response_obj, "choices", []) + ] if messages := [*input_messages, *output_messages]: set_span_chat_messages(span, messages) if tools := inputs.get("tools"): diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index 1572eb81f5..f4fe40738b 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -1,7 +1,7 @@ import os from dataclasses import dataclass from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast import litellm from litellm._logging import verbose_logger @@ -23,10 +23,10 @@ if TYPE_CHECKING: ) from litellm.proxy.proxy_server import UserAPIKeyAuth as _UserAPIKeyAuth - Span = _Span - SpanExporter = _SpanExporter - UserAPIKeyAuth = _UserAPIKeyAuth - ManagementEndpointLoggingPayload = _ManagementEndpointLoggingPayload + Span = Union[_Span, Any] + SpanExporter = Union[_SpanExporter, Any] + UserAPIKeyAuth = Union[_UserAPIKeyAuth, Any] + ManagementEndpointLoggingPayload = Union[_ManagementEndpointLoggingPayload, Any] else: Span = Any SpanExporter = Any @@ -46,7 +46,6 @@ LITELLM_REQUEST_SPAN_NAME = "litellm_request" @dataclass class OpenTelemetryConfig: - exporter: Union[str, SpanExporter] = "console" endpoint: Optional[str] = None headers: Optional[str] = None @@ -154,7 +153,6 @@ class OpenTelemetry(CustomLogger): end_time: Optional[Union[datetime, float]] = None, event_metadata: Optional[dict] = None, ): - from opentelemetry import trace from opentelemetry.trace import Status, StatusCode @@ -215,7 +213,6 @@ class OpenTelemetry(CustomLogger): end_time: Optional[Union[float, datetime]] = None, event_metadata: Optional[dict] = None, ): - from opentelemetry import trace from opentelemetry.trace import Status, StatusCode @@ -353,9 +350,9 @@ class OpenTelemetry(CustomLogger): """ from opentelemetry import trace - standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = ( - kwargs.get("standard_callback_dynamic_params") - ) + standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = kwargs.get("standard_callback_dynamic_params") if not standard_callback_dynamic_params: return @@ -722,7 +719,6 @@ class OpenTelemetry(CustomLogger): span.set_attribute(key, primitive_value) def set_raw_request_attributes(self, span: Span, kwargs, response_obj): - kwargs.get("optional_params", {}) litellm_params = kwargs.get("litellm_params", {}) or {} custom_llm_provider = litellm_params.get("custom_llm_provider", "Unknown") @@ -843,12 +839,14 @@ class OpenTelemetry(CustomLogger): headers=dynamic_headers or self.OTEL_HEADERS ) - if isinstance(self.OTEL_EXPORTER, SpanExporter): + if hasattr( + self.OTEL_EXPORTER, "export" + ): # Check if it has the export method that SpanExporter requires verbose_logger.debug( "OpenTelemetry: intiializing SpanExporter. Value of OTEL_EXPORTER: %s", self.OTEL_EXPORTER, ) - return SimpleSpanProcessor(self.OTEL_EXPORTER) + return SimpleSpanProcessor(cast(SpanExporter, self.OTEL_EXPORTER)) if self.OTEL_EXPORTER == "console": verbose_logger.debug( @@ -907,7 +905,6 @@ class OpenTelemetry(CustomLogger): logging_payload: ManagementEndpointLoggingPayload, parent_otel_span: Optional[Span] = None, ): - from opentelemetry import trace from opentelemetry.trace import Status, StatusCode @@ -961,7 +958,6 @@ class OpenTelemetry(CustomLogger): logging_payload: ManagementEndpointLoggingPayload, parent_otel_span: Optional[Span] = None, ): - from opentelemetry import trace from opentelemetry.trace import Status, StatusCode diff --git a/litellm/integrations/opik/opik.py b/litellm/integrations/opik/opik.py index 1f7f18f336..8cbfb9e653 100644 --- a/litellm/integrations/opik/opik.py +++ b/litellm/integrations/opik/opik.py @@ -185,7 +185,6 @@ class OpikLogger(CustomBatchLogger): def _create_opik_payload( # noqa: PLR0915 self, kwargs, response_obj, start_time, end_time ) -> List[Dict]: - # Get metadata _litellm_params = kwargs.get("litellm_params", {}) or {} litellm_params_metadata = _litellm_params.get("metadata", {}) or {} diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index d6e47b87ce..5ac8c80eb3 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -988,9 +988,9 @@ class PrometheusLogger(CustomLogger): ): try: verbose_logger.debug("setting remaining tokens requests metric") - standard_logging_payload: Optional[StandardLoggingPayload] = ( - request_kwargs.get("standard_logging_object") - ) + standard_logging_payload: Optional[ + StandardLoggingPayload + ] = request_kwargs.get("standard_logging_object") if standard_logging_payload is None: return diff --git a/litellm/integrations/prompt_management_base.py b/litellm/integrations/prompt_management_base.py index 3fe3b31ed8..07b6720ffe 100644 --- a/litellm/integrations/prompt_management_base.py +++ b/litellm/integrations/prompt_management_base.py @@ -14,7 +14,6 @@ class PromptManagementClient(TypedDict): class PromptManagementBase(ABC): - @property @abstractmethod def integration_name(self) -> str: @@ -83,11 +82,7 @@ class PromptManagementBase(ABC): prompt_id: str, prompt_variables: Optional[dict], dynamic_callback_params: StandardCallbackDynamicParams, - ) -> Tuple[ - str, - List[AllMessageValues], - dict, - ]: + ) -> Tuple[str, List[AllMessageValues], dict,]: if not self.should_run_prompt_management( prompt_id=prompt_id, dynamic_callback_params=dynamic_callback_params ): diff --git a/litellm/integrations/s3.py b/litellm/integrations/s3.py index 4a0c27354f..01b9248e03 100644 --- a/litellm/integrations/s3.py +++ b/litellm/integrations/s3.py @@ -38,7 +38,7 @@ class S3Logger: if litellm.s3_callback_params is not None: # read in .env variables - example os.environ/AWS_BUCKET_NAME for key, value in litellm.s3_callback_params.items(): - if type(value) is str and value.startswith("os.environ/"): + if isinstance(value, str) and value.startswith("os.environ/"): litellm.s3_callback_params[key] = litellm.get_secret(value) # now set s3 params from litellm.s3_logger_params s3_bucket_name = litellm.s3_callback_params.get("s3_bucket_name") diff --git a/litellm/integrations/weights_biases.py b/litellm/integrations/weights_biases.py index 5fcbab04b3..63d87c9bd9 100644 --- a/litellm/integrations/weights_biases.py +++ b/litellm/integrations/weights_biases.py @@ -21,11 +21,11 @@ try: # contains a (known) object attribute object: Literal["chat.completion", "edit", "text_completion"] - def __getitem__(self, key: K) -> V: ... # noqa + def __getitem__(self, key: K) -> V: + ... # noqa - def get( # noqa - self, key: K, default: Optional[V] = None - ) -> Optional[V]: ... # pragma: no cover + def get(self, key: K, default: Optional[V] = None) -> Optional[V]: # noqa + ... # pragma: no cover class OpenAIRequestResponseResolver: def __call__( diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index 2036b93692..275c53ad30 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -10,7 +10,7 @@ from litellm.types.llms.openai import AllMessageValues if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/litellm_core_utils/default_encoding.py b/litellm/litellm_core_utils/default_encoding.py index 05bf78a6a9..93b3132912 100644 --- a/litellm/litellm_core_utils/default_encoding.py +++ b/litellm/litellm_core_utils/default_encoding.py @@ -11,7 +11,9 @@ except (ImportError, AttributeError): # Old way to access resources, which setuptools deprecated some time ago import pkg_resources # type: ignore - filename = pkg_resources.resource_filename(__name__, "litellm_core_utils/tokenizers") + filename = pkg_resources.resource_filename( + __name__, "litellm_core_utils/tokenizers" + ) os.environ["TIKTOKEN_CACHE_DIR"] = os.getenv( "CUSTOM_TIKTOKEN_CACHE_DIR", filename diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 3565c4468c..dcd3ae3a64 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -239,9 +239,9 @@ class Logging(LiteLLMLoggingBaseClass): self.litellm_trace_id = litellm_trace_id self.function_id = function_id self.streaming_chunks: List[Any] = [] # for generating complete stream response - self.sync_streaming_chunks: List[Any] = ( - [] - ) # for generating complete stream response + self.sync_streaming_chunks: List[ + Any + ] = [] # for generating complete stream response self.log_raw_request_response = log_raw_request_response # Initialize dynamic callbacks @@ -452,18 +452,19 @@ class Logging(LiteLLMLoggingBaseClass): prompt_id: str, prompt_variables: Optional[dict], ) -> Tuple[str, List[AllMessageValues], dict]: - custom_logger = self.get_custom_logger_for_prompt_management(model) if custom_logger: - model, messages, non_default_params = ( - custom_logger.get_chat_completion_prompt( - model=model, - messages=messages, - non_default_params=non_default_params, - prompt_id=prompt_id, - prompt_variables=prompt_variables, - dynamic_callback_params=self.standard_callback_dynamic_params, - ) + ( + model, + messages, + non_default_params, + ) = custom_logger.get_chat_completion_prompt( + model=model, + messages=messages, + non_default_params=non_default_params, + prompt_id=prompt_id, + prompt_variables=prompt_variables, + dynamic_callback_params=self.standard_callback_dynamic_params, ) self.messages = messages return model, messages, non_default_params @@ -541,12 +542,11 @@ class Logging(LiteLLMLoggingBaseClass): model ): # if model name was changes pre-call, overwrite the initial model call name with the new one self.model_call_details["model"] = model - self.model_call_details["litellm_params"]["api_base"] = ( - self._get_masked_api_base(additional_args.get("api_base", "")) - ) + self.model_call_details["litellm_params"][ + "api_base" + ] = self._get_masked_api_base(additional_args.get("api_base", "")) def pre_call(self, input, api_key, model=None, additional_args={}): # noqa: PLR0915 - # Log the exact input to the LLM API litellm.error_logs["PRE_CALL"] = locals() try: @@ -568,19 +568,16 @@ class Logging(LiteLLMLoggingBaseClass): self.log_raw_request_response is True or log_raw_request_response is True ): - _litellm_params = self.model_call_details.get("litellm_params", {}) _metadata = _litellm_params.get("metadata", {}) or {} try: # [Non-blocking Extra Debug Information in metadata] if turn_off_message_logging is True: - - _metadata["raw_request"] = ( - "redacted by litellm. \ + _metadata[ + "raw_request" + ] = "redacted by litellm. \ 'litellm.turn_off_message_logging=True'" - ) else: - curl_command = self._get_request_curl_command( api_base=additional_args.get("api_base", ""), headers=additional_args.get("headers", {}), @@ -590,33 +587,33 @@ class Logging(LiteLLMLoggingBaseClass): _metadata["raw_request"] = str(curl_command) # split up, so it's easier to parse in the UI - self.model_call_details["raw_request_typed_dict"] = ( - RawRequestTypedDict( - raw_request_api_base=str( - additional_args.get("api_base") or "" - ), - raw_request_body=self._get_raw_request_body( - additional_args.get("complete_input_dict", {}) - ), - raw_request_headers=self._get_masked_headers( - additional_args.get("headers", {}) or {}, - ignore_sensitive_headers=True, - ), - error=None, - ) + self.model_call_details[ + "raw_request_typed_dict" + ] = RawRequestTypedDict( + raw_request_api_base=str( + additional_args.get("api_base") or "" + ), + raw_request_body=self._get_raw_request_body( + additional_args.get("complete_input_dict", {}) + ), + raw_request_headers=self._get_masked_headers( + additional_args.get("headers", {}) or {}, + ignore_sensitive_headers=True, + ), + error=None, ) except Exception as e: - self.model_call_details["raw_request_typed_dict"] = ( - RawRequestTypedDict( - error=str(e), - ) + self.model_call_details[ + "raw_request_typed_dict" + ] = RawRequestTypedDict( + error=str(e), ) traceback.print_exc() - _metadata["raw_request"] = ( - "Unable to Log \ + _metadata[ + "raw_request" + ] = "Unable to Log \ raw request: {}".format( - str(e) - ) + str(e) ) if self.logger_fn and callable(self.logger_fn): try: @@ -941,9 +938,9 @@ class Logging(LiteLLMLoggingBaseClass): verbose_logger.debug( f"response_cost_failure_debug_information: {debug_info}" ) - self.model_call_details["response_cost_failure_debug_information"] = ( - debug_info - ) + self.model_call_details[ + "response_cost_failure_debug_information" + ] = debug_info return None try: @@ -968,9 +965,9 @@ class Logging(LiteLLMLoggingBaseClass): verbose_logger.debug( f"response_cost_failure_debug_information: {debug_info}" ) - self.model_call_details["response_cost_failure_debug_information"] = ( - debug_info - ) + self.model_call_details[ + "response_cost_failure_debug_information" + ] = debug_info return None @@ -995,7 +992,6 @@ class Logging(LiteLLMLoggingBaseClass): def should_run_callback( self, callback: litellm.CALLBACK_TYPES, litellm_params: dict, event_hook: str ) -> bool: - if litellm.global_disable_no_log_param: return True @@ -1027,9 +1023,9 @@ class Logging(LiteLLMLoggingBaseClass): end_time = datetime.datetime.now() if self.completion_start_time is None: self.completion_start_time = end_time - self.model_call_details["completion_start_time"] = ( - self.completion_start_time - ) + self.model_call_details[ + "completion_start_time" + ] = self.completion_start_time self.model_call_details["log_event_type"] = "successful_api_call" self.model_call_details["end_time"] = end_time self.model_call_details["cache_hit"] = cache_hit @@ -1083,39 +1079,39 @@ class Logging(LiteLLMLoggingBaseClass): "response_cost" ] else: - self.model_call_details["response_cost"] = ( - self._response_cost_calculator(result=result) - ) + self.model_call_details[ + "response_cost" + ] = self._response_cost_calculator(result=result) ## STANDARDIZED LOGGING PAYLOAD - self.model_call_details["standard_logging_object"] = ( - get_standard_logging_object_payload( - kwargs=self.model_call_details, - init_response_obj=result, - start_time=start_time, - end_time=end_time, - logging_obj=self, - status="success", - standard_built_in_tools_params=self.standard_built_in_tools_params, - ) + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj=result, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="success", + standard_built_in_tools_params=self.standard_built_in_tools_params, ) elif isinstance(result, dict): # pass-through endpoints ## STANDARDIZED LOGGING PAYLOAD - self.model_call_details["standard_logging_object"] = ( - get_standard_logging_object_payload( - kwargs=self.model_call_details, - init_response_obj=result, - start_time=start_time, - end_time=end_time, - logging_obj=self, - status="success", - standard_built_in_tools_params=self.standard_built_in_tools_params, - ) + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj=result, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="success", + standard_built_in_tools_params=self.standard_built_in_tools_params, ) elif standard_logging_object is not None: - self.model_call_details["standard_logging_object"] = ( - standard_logging_object - ) + self.model_call_details[ + "standard_logging_object" + ] = standard_logging_object else: # streaming chunks + image gen. self.model_call_details["response_cost"] = None @@ -1154,7 +1150,6 @@ class Logging(LiteLLMLoggingBaseClass): standard_logging_object=kwargs.get("standard_logging_object", None), ) try: - ## BUILD COMPLETE STREAMED RESPONSE complete_streaming_response: Optional[ Union[ModelResponse, TextCompletionResponse, ResponsesAPIResponse] @@ -1172,23 +1167,23 @@ class Logging(LiteLLMLoggingBaseClass): verbose_logger.debug( "Logging Details LiteLLM-Success Call streaming complete" ) - self.model_call_details["complete_streaming_response"] = ( - complete_streaming_response - ) - self.model_call_details["response_cost"] = ( - self._response_cost_calculator(result=complete_streaming_response) - ) + self.model_call_details[ + "complete_streaming_response" + ] = complete_streaming_response + self.model_call_details[ + "response_cost" + ] = self._response_cost_calculator(result=complete_streaming_response) ## STANDARDIZED LOGGING PAYLOAD - self.model_call_details["standard_logging_object"] = ( - get_standard_logging_object_payload( - kwargs=self.model_call_details, - init_response_obj=complete_streaming_response, - start_time=start_time, - end_time=end_time, - logging_obj=self, - status="success", - standard_built_in_tools_params=self.standard_built_in_tools_params, - ) + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj=complete_streaming_response, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="success", + standard_built_in_tools_params=self.standard_built_in_tools_params, ) callbacks = self.get_combined_callback_list( dynamic_success_callbacks=self.dynamic_success_callbacks, @@ -1207,7 +1202,6 @@ class Logging(LiteLLMLoggingBaseClass): ## LOGGING HOOK ## for callback in callbacks: if isinstance(callback, CustomLogger): - self.model_call_details, result = callback.logging_hook( kwargs=self.model_call_details, result=result, @@ -1538,10 +1532,10 @@ class Logging(LiteLLMLoggingBaseClass): ) else: if self.stream and complete_streaming_response: - self.model_call_details["complete_response"] = ( - self.model_call_details.get( - "complete_streaming_response", {} - ) + self.model_call_details[ + "complete_response" + ] = self.model_call_details.get( + "complete_streaming_response", {} ) result = self.model_call_details["complete_response"] openMeterLogger.log_success_event( @@ -1581,10 +1575,10 @@ class Logging(LiteLLMLoggingBaseClass): ) else: if self.stream and complete_streaming_response: - self.model_call_details["complete_response"] = ( - self.model_call_details.get( - "complete_streaming_response", {} - ) + self.model_call_details[ + "complete_response" + ] = self.model_call_details.get( + "complete_streaming_response", {} ) result = self.model_call_details["complete_response"] @@ -1659,7 +1653,6 @@ class Logging(LiteLLMLoggingBaseClass): if self.call_type == CallTypes.aretrieve_batch.value and isinstance( result, LiteLLMBatch ): - response_cost, batch_usage, batch_models = await _handle_completed_batch( batch=result, custom_llm_provider=self.custom_llm_provider ) @@ -1692,9 +1685,9 @@ class Logging(LiteLLMLoggingBaseClass): if complete_streaming_response is not None: print_verbose("Async success callbacks: Got a complete streaming response") - self.model_call_details["async_complete_streaming_response"] = ( - complete_streaming_response - ) + self.model_call_details[ + "async_complete_streaming_response" + ] = complete_streaming_response try: if self.model_call_details.get("cache_hit", False) is True: self.model_call_details["response_cost"] = 0.0 @@ -1704,10 +1697,10 @@ class Logging(LiteLLMLoggingBaseClass): model_call_details=self.model_call_details ) # base_model defaults to None if not set on model_info - self.model_call_details["response_cost"] = ( - self._response_cost_calculator( - result=complete_streaming_response - ) + self.model_call_details[ + "response_cost" + ] = self._response_cost_calculator( + result=complete_streaming_response ) verbose_logger.debug( @@ -1720,16 +1713,16 @@ class Logging(LiteLLMLoggingBaseClass): self.model_call_details["response_cost"] = None ## STANDARDIZED LOGGING PAYLOAD - self.model_call_details["standard_logging_object"] = ( - get_standard_logging_object_payload( - kwargs=self.model_call_details, - init_response_obj=complete_streaming_response, - start_time=start_time, - end_time=end_time, - logging_obj=self, - status="success", - standard_built_in_tools_params=self.standard_built_in_tools_params, - ) + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj=complete_streaming_response, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="success", + standard_built_in_tools_params=self.standard_built_in_tools_params, ) callbacks = self.get_combined_callback_list( dynamic_success_callbacks=self.dynamic_async_success_callbacks, @@ -1935,18 +1928,18 @@ class Logging(LiteLLMLoggingBaseClass): ## STANDARDIZED LOGGING PAYLOAD - self.model_call_details["standard_logging_object"] = ( - get_standard_logging_object_payload( - kwargs=self.model_call_details, - init_response_obj={}, - start_time=start_time, - end_time=end_time, - logging_obj=self, - status="failure", - error_str=str(exception), - original_exception=exception, - standard_built_in_tools_params=self.standard_built_in_tools_params, - ) + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj={}, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="failure", + error_str=str(exception), + original_exception=exception, + standard_built_in_tools_params=self.standard_built_in_tools_params, ) return start_time, end_time @@ -2084,7 +2077,6 @@ class Logging(LiteLLMLoggingBaseClass): ) is not True ): # custom logger class - callback.log_failure_event( start_time=start_time, end_time=end_time, @@ -2713,9 +2705,9 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 endpoint=arize_config.endpoint, ) - os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = ( - f"space_key={arize_config.space_key},api_key={arize_config.api_key}" - ) + os.environ[ + "OTEL_EXPORTER_OTLP_TRACES_HEADERS" + ] = f"space_key={arize_config.space_key},api_key={arize_config.api_key}" for callback in _in_memory_loggers: if ( isinstance(callback, ArizeLogger) @@ -2739,9 +2731,9 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 # auth can be disabled on local deployments of arize phoenix if arize_phoenix_config.otlp_auth_headers is not None: - os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = ( - arize_phoenix_config.otlp_auth_headers - ) + os.environ[ + "OTEL_EXPORTER_OTLP_TRACES_HEADERS" + ] = arize_phoenix_config.otlp_auth_headers for callback in _in_memory_loggers: if ( @@ -2832,9 +2824,9 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 exporter="otlp_http", endpoint="https://langtrace.ai/api/trace", ) - os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = ( - f"api_key={os.getenv('LANGTRACE_API_KEY')}" - ) + os.environ[ + "OTEL_EXPORTER_OTLP_TRACES_HEADERS" + ] = f"api_key={os.getenv('LANGTRACE_API_KEY')}" for callback in _in_memory_loggers: if ( isinstance(callback, OpenTelemetry) @@ -3223,7 +3215,6 @@ class StandardLoggingPayloadSetup: custom_llm_provider: Optional[str], init_response_obj: Union[Any, BaseModel, dict], ) -> StandardLoggingModelInformation: - model_cost_name = _select_model_name_for_cost_calc( model=None, completion_response=init_response_obj, # type: ignore @@ -3286,7 +3277,6 @@ class StandardLoggingPayloadSetup: def get_additional_headers( additiona_headers: Optional[dict], ) -> Optional[StandardLoggingAdditionalHeaders]: - if additiona_headers is None: return None @@ -3322,10 +3312,10 @@ class StandardLoggingPayloadSetup: for key in StandardLoggingHiddenParams.__annotations__.keys(): if key in hidden_params: if key == "additional_headers": - clean_hidden_params["additional_headers"] = ( - StandardLoggingPayloadSetup.get_additional_headers( - hidden_params[key] - ) + clean_hidden_params[ + "additional_headers" + ] = StandardLoggingPayloadSetup.get_additional_headers( + hidden_params[key] ) else: clean_hidden_params[key] = hidden_params[key] # type: ignore @@ -3463,12 +3453,14 @@ def get_standard_logging_object_payload( ) # cleanup timestamps - start_time_float, end_time_float, completion_start_time_float = ( - StandardLoggingPayloadSetup.cleanup_timestamps( - start_time=start_time, - end_time=end_time, - completion_start_time=completion_start_time, - ) + ( + start_time_float, + end_time_float, + completion_start_time_float, + ) = StandardLoggingPayloadSetup.cleanup_timestamps( + start_time=start_time, + end_time=end_time, + completion_start_time=completion_start_time, ) response_time = StandardLoggingPayloadSetup.get_response_time( start_time_float=start_time_float, @@ -3495,7 +3487,6 @@ def get_standard_logging_object_payload( saved_cache_cost: float = 0.0 if cache_hit is True: - id = f"{id}_cache_hit{time.time()}" # do not duplicate the request id saved_cache_cost = ( logging_obj._response_cost_calculator( @@ -3658,9 +3649,9 @@ def scrub_sensitive_keys_in_metadata(litellm_params: Optional[dict]): ): for k, v in metadata["user_api_key_metadata"].items(): if k == "logging": # prevent logging user logging keys - cleaned_user_api_key_metadata[k] = ( - "scrubbed_by_litellm_for_sensitive_keys" - ) + cleaned_user_api_key_metadata[ + k + ] = "scrubbed_by_litellm_for_sensitive_keys" else: cleaned_user_api_key_metadata[k] = v diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index d33af2a477..f3f4ce6ef4 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -258,14 +258,12 @@ def _extract_reasoning_content(message: dict) -> Tuple[Optional[str], Optional[s class LiteLLMResponseObjectHandler: - @staticmethod def convert_to_image_response( response_object: dict, model_response_object: Optional[ImageResponse] = None, hidden_params: Optional[dict] = None, ) -> ImageResponse: - response_object.update({"hidden_params": hidden_params}) if model_response_object is None: @@ -481,9 +479,9 @@ def convert_to_model_response_object( # noqa: PLR0915 provider_specific_fields["thinking_blocks"] = thinking_blocks if reasoning_content: - provider_specific_fields["reasoning_content"] = ( - reasoning_content - ) + provider_specific_fields[ + "reasoning_content" + ] = reasoning_content message = Message( content=content, diff --git a/litellm/litellm_core_utils/model_param_helper.py b/litellm/litellm_core_utils/model_param_helper.py index b7d8fc19d1..c96b4a3f5b 100644 --- a/litellm/litellm_core_utils/model_param_helper.py +++ b/litellm/litellm_core_utils/model_param_helper.py @@ -17,7 +17,6 @@ from litellm.types.rerank import RerankRequest class ModelParamHelper: - @staticmethod def get_standard_logging_model_parameters( model_parameters: dict, diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index c8745f5119..4170d3c1e1 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -257,7 +257,6 @@ def _insert_assistant_continue_message( and message.get("role") == "user" # Current is user and messages[i + 1].get("role") == "user" ): # Next is user - # Insert assistant message continue_message = ( assistant_continue_message or DEFAULT_ASSISTANT_CONTINUE_MESSAGE diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 28e09d7ac8..3c89141b5e 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1042,10 +1042,10 @@ def convert_to_gemini_tool_call_invoke( if tool_calls is not None: for tool in tool_calls: if "function" in tool: - gemini_function_call: Optional[VertexFunctionCall] = ( - _gemini_tool_call_invoke_helper( - function_call_params=tool["function"] - ) + gemini_function_call: Optional[ + VertexFunctionCall + ] = _gemini_tool_call_invoke_helper( + function_call_params=tool["function"] ) if gemini_function_call is not None: _parts_list.append( @@ -1432,9 +1432,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_content_element[ + "cache_control" + ] = _content_element["cache_control"] user_content.append(_anthropic_content_element) elif m.get("type", "") == "text": m = cast(ChatCompletionTextObject, m) @@ -1466,9 +1466,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_text_element["cache_control"] = ( - _content_element["cache_control"] - ) + _anthropic_content_text_element[ + "cache_control" + ] = _content_element["cache_control"] user_content.append(_anthropic_content_text_element) @@ -1533,7 +1533,6 @@ def anthropic_messages_pt( # noqa: PLR0915 "content" ] # don't pass empty text blocks. anthropic api raises errors. ): - _anthropic_text_content_element = AnthropicMessagesTextParam( type="text", text=assistant_content_block["content"], @@ -1569,7 +1568,6 @@ def anthropic_messages_pt( # noqa: PLR0915 msg_i += 1 if assistant_content: - new_messages.append({"role": "assistant", "content": assistant_content}) if msg_i == init_msg_i: # prevent infinite loops @@ -2245,7 +2243,6 @@ class BedrockImageProcessor: @staticmethod async def get_image_details_async(image_url) -> Tuple[str, str]: try: - client = get_async_httpx_client( llm_provider=httpxSpecialProvider.PromptFactory, params={"concurrent_limit": 1}, @@ -2612,7 +2609,6 @@ def get_user_message_block_or_continue_message( for item in modified_content_block: # Check if the list is empty if item["type"] == "text": - if not item["text"].strip(): # Replace empty text with continue message _user_continue_message = ChatCompletionUserMessage( @@ -3207,7 +3203,6 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 assistant_content: List[BedrockContentBlock] = [] ## MERGE CONSECUTIVE ASSISTANT CONTENT ## while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": - assistant_message_block = get_assistant_message_block_or_continue_message( message=messages[msg_i], assistant_continue_message=assistant_continue_message, @@ -3410,7 +3405,6 @@ def response_schema_prompt(model: str, response_schema: dict) -> str: {"role": "user", "content": "{}".format(response_schema)} ] if f"{model}/response_schema_prompt" in litellm.custom_prompt_dict: - custom_prompt_details = litellm.custom_prompt_dict[ f"{model}/response_schema_prompt" ] # allow user to define custom response schema prompt by model diff --git a/litellm/litellm_core_utils/realtime_streaming.py b/litellm/litellm_core_utils/realtime_streaming.py index aebd049692..e84c720441 100644 --- a/litellm/litellm_core_utils/realtime_streaming.py +++ b/litellm/litellm_core_utils/realtime_streaming.py @@ -122,7 +122,6 @@ class RealTimeStreaming: pass async def bidirectional_forward(self): - forward_task = asyncio.create_task(self.backend_to_client_send_messages()) try: await self.client_ack_messages() diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index 50e0e0b575..a62031a9c9 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -135,9 +135,9 @@ def _get_turn_off_message_logging_from_dynamic_params( handles boolean and string values of `turn_off_message_logging` """ - standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = ( - model_call_details.get("standard_callback_dynamic_params", None) - ) + standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = model_call_details.get("standard_callback_dynamic_params", None) if standard_callback_dynamic_params: _turn_off_message_logging = standard_callback_dynamic_params.get( "turn_off_message_logging" diff --git a/litellm/litellm_core_utils/sensitive_data_masker.py b/litellm/litellm_core_utils/sensitive_data_masker.py index 7800e5304f..23b9ec32fc 100644 --- a/litellm/litellm_core_utils/sensitive_data_masker.py +++ b/litellm/litellm_core_utils/sensitive_data_masker.py @@ -1,4 +1,5 @@ from typing import Any, Dict, Optional, Set + from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH @@ -40,7 +41,10 @@ class SensitiveDataMasker: return result def mask_dict( - self, data: Dict[str, Any], depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH + self, + data: Dict[str, Any], + depth: int = 0, + max_depth: int = DEFAULT_MAX_RECURSE_DEPTH, ) -> Dict[str, Any]: if depth >= max_depth: return data diff --git a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py index 7a5ee3e41e..1ca2bfe45e 100644 --- a/litellm/litellm_core_utils/streaming_chunk_builder_utils.py +++ b/litellm/litellm_core_utils/streaming_chunk_builder_utils.py @@ -104,7 +104,6 @@ class ChunkProcessor: def get_combined_tool_content( self, tool_call_chunks: List[Dict[str, Any]] ) -> List[ChatCompletionMessageToolCall]: - argument_list: List[str] = [] delta = tool_call_chunks[0]["choices"][0]["delta"] id = None diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index a11e5af12b..42106135cc 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -84,9 +84,9 @@ class CustomStreamWrapper: self.system_fingerprint: Optional[str] = None self.received_finish_reason: Optional[str] = None - self.intermittent_finish_reason: Optional[str] = ( - None # finish reasons that show up mid-stream - ) + self.intermittent_finish_reason: Optional[ + str + ] = None # finish reasons that show up mid-stream self.special_tokens = [ "<|assistant|>", "<|system|>", @@ -814,7 +814,6 @@ class CustomStreamWrapper: model_response: ModelResponseStream, response_obj: Dict[str, Any], ): - print_verbose( f"completion_obj: {completion_obj}, model_response.choices[0]: {model_response.choices[0]}, response_obj: {response_obj}" ) @@ -1008,7 +1007,6 @@ class CustomStreamWrapper: self.custom_llm_provider and self.custom_llm_provider in litellm._custom_providers ): - if self.received_finish_reason is not None: if "provider_specific_fields" not in chunk: raise StopIteration @@ -1379,9 +1377,9 @@ class CustomStreamWrapper: _json_delta = delta.model_dump() print_verbose(f"_json_delta: {_json_delta}") if "role" not in _json_delta or _json_delta["role"] is None: - _json_delta["role"] = ( - "assistant" # mistral's api returns role as None - ) + _json_delta[ + "role" + ] = "assistant" # mistral's api returns role as None if "tool_calls" in _json_delta and isinstance( _json_delta["tool_calls"], list ): @@ -1758,9 +1756,9 @@ class CustomStreamWrapper: chunk = next(self.completion_stream) if chunk is not None and chunk != b"": print_verbose(f"PROCESSED CHUNK PRE CHUNK CREATOR: {chunk}") - processed_chunk: Optional[ModelResponseStream] = ( - self.chunk_creator(chunk=chunk) - ) + processed_chunk: Optional[ + ModelResponseStream + ] = self.chunk_creator(chunk=chunk) print_verbose( f"PROCESSED CHUNK POST CHUNK CREATOR: {processed_chunk}" ) diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index f2c5f390d7..7625292e6e 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -290,7 +290,6 @@ class AnthropicChatCompletion(BaseLLM): headers={}, client=None, ): - optional_params = copy.deepcopy(optional_params) stream = optional_params.pop("stream", None) json_mode: bool = optional_params.pop("json_mode", False) @@ -491,7 +490,6 @@ class ModelResponseIterator: def _handle_usage( self, anthropic_usage_chunk: Union[dict, UsageDelta] ) -> AnthropicChatCompletionUsageBlock: - usage_block = AnthropicChatCompletionUsageBlock( prompt_tokens=anthropic_usage_chunk.get("input_tokens", 0), completion_tokens=anthropic_usage_chunk.get("output_tokens", 0), @@ -515,7 +513,9 @@ class ModelResponseIterator: return usage_block - def _content_block_delta_helper(self, chunk: dict) -> Tuple[ + def _content_block_delta_helper( + self, chunk: dict + ) -> Tuple[ str, Optional[ChatCompletionToolCallChunk], List[ChatCompletionThinkingBlock], @@ -592,9 +592,12 @@ class ModelResponseIterator: Anthropic content chunk chunk = {'type': 'content_block_delta', 'index': 0, 'delta': {'type': 'text_delta', 'text': 'Hello'}} """ - text, tool_use, thinking_blocks, provider_specific_fields = ( - self._content_block_delta_helper(chunk=chunk) - ) + ( + text, + tool_use, + thinking_blocks, + provider_specific_fields, + ) = self._content_block_delta_helper(chunk=chunk) if thinking_blocks: reasoning_content = self._handle_reasoning_content( thinking_blocks=thinking_blocks @@ -620,7 +623,6 @@ class ModelResponseIterator: "index": self.tool_index, } elif type_chunk == "content_block_stop": - ContentBlockStop(**chunk) # type: ignore # check if tool call content block is_empty = self.check_empty_tool_call_args() diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index dcbc6775dc..a8f36cdcad 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -49,9 +49,9 @@ class AnthropicConfig(BaseConfig): to pass metadata to anthropic, it's {"user_id": "any-relevant-information"} """ - max_tokens: Optional[int] = ( - 4096 # anthropic requires a default value (Opus, Sonnet, and Haiku have the same default) - ) + max_tokens: Optional[ + int + ] = 4096 # anthropic requires a default value (Opus, Sonnet, and Haiku have the same default) stop_sequences: Optional[list] = None temperature: Optional[int] = None top_p: Optional[int] = None @@ -104,7 +104,6 @@ class AnthropicConfig(BaseConfig): def get_json_schema_from_pydantic_object( self, response_format: Union[Any, Dict, None] ) -> Optional[dict]: - return type_to_response_format_param( response_format, ref_template="/$defs/{model}" ) # Relevant issue: https://github.com/BerriAI/litellm/issues/7755 @@ -125,7 +124,6 @@ class AnthropicConfig(BaseConfig): is_vertex_request: bool = False, user_anthropic_beta_headers: Optional[List[str]] = None, ) -> dict: - betas = set() if prompt_caching_set: betas.add("prompt-caching-2024-07-31") @@ -300,7 +298,6 @@ class AnthropicConfig(BaseConfig): model: str, drop_params: bool, ) -> dict: - is_thinking_enabled = self.is_thinking_enabled( non_default_params=non_default_params ) @@ -321,11 +318,11 @@ class AnthropicConfig(BaseConfig): optional_params=optional_params, tools=tool_value ) if param == "tool_choice" or param == "parallel_tool_calls": - _tool_choice: Optional[AnthropicMessagesToolChoice] = ( - self._map_tool_choice( - tool_choice=non_default_params.get("tool_choice"), - parallel_tool_use=non_default_params.get("parallel_tool_calls"), - ) + _tool_choice: Optional[ + AnthropicMessagesToolChoice + ] = self._map_tool_choice( + tool_choice=non_default_params.get("tool_choice"), + parallel_tool_use=non_default_params.get("parallel_tool_calls"), ) if _tool_choice is not None: @@ -341,7 +338,6 @@ class AnthropicConfig(BaseConfig): if param == "top_p": optional_params["top_p"] = value if param == "response_format" and isinstance(value, dict): - ignore_response_format_types = ["text"] if value["type"] in ignore_response_format_types: # value is a no-op continue @@ -470,9 +466,9 @@ class AnthropicConfig(BaseConfig): text=system_message_block["content"], ) if "cache_control" in system_message_block: - anthropic_system_message_content["cache_control"] = ( - system_message_block["cache_control"] - ) + anthropic_system_message_content[ + "cache_control" + ] = system_message_block["cache_control"] anthropic_system_message_list.append( anthropic_system_message_content ) @@ -486,9 +482,9 @@ class AnthropicConfig(BaseConfig): ) ) if "cache_control" in _content: - anthropic_system_message_content["cache_control"] = ( - _content["cache_control"] - ) + anthropic_system_message_content[ + "cache_control" + ] = _content["cache_control"] anthropic_system_message_list.append( anthropic_system_message_content @@ -597,7 +593,9 @@ class AnthropicConfig(BaseConfig): ) return _message - def extract_response_content(self, completion_response: dict) -> Tuple[ + def extract_response_content( + self, completion_response: dict + ) -> Tuple[ str, Optional[List[Any]], Optional[List[ChatCompletionThinkingBlock]], @@ -693,9 +691,13 @@ class AnthropicConfig(BaseConfig): reasoning_content: Optional[str] = None tool_calls: List[ChatCompletionToolCallChunk] = [] - text_content, citations, thinking_blocks, reasoning_content, tool_calls = ( - self.extract_response_content(completion_response=completion_response) - ) + ( + text_content, + citations, + thinking_blocks, + reasoning_content, + tool_calls, + ) = self.extract_response_content(completion_response=completion_response) _message = litellm.Message( tool_calls=tool_calls, diff --git a/litellm/llms/anthropic/completion/transformation.py b/litellm/llms/anthropic/completion/transformation.py index 7a260b6f94..5cbc0b5fd8 100644 --- a/litellm/llms/anthropic/completion/transformation.py +++ b/litellm/llms/anthropic/completion/transformation.py @@ -54,9 +54,9 @@ class AnthropicTextConfig(BaseConfig): to pass metadata to anthropic, it's {"user_id": "any-relevant-information"} """ - max_tokens_to_sample: Optional[int] = ( - litellm.max_tokens - ) # anthropic requires a default + max_tokens_to_sample: Optional[ + int + ] = litellm.max_tokens # anthropic requires a default stop_sequences: Optional[list] = None temperature: Optional[int] = None top_p: Optional[int] = None diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py index a7dfff74d9..099a2acdae 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py @@ -25,7 +25,6 @@ from litellm.utils import ProviderConfigManager, client class AnthropicMessagesHandler: - @staticmethod async def _handle_anthropic_streaming( response: httpx.Response, @@ -74,19 +73,22 @@ async def anthropic_messages( """ # Use provided client or create a new one optional_params = GenericLiteLLMParams(**kwargs) - model, _custom_llm_provider, dynamic_api_key, dynamic_api_base = ( - litellm.get_llm_provider( - model=model, - custom_llm_provider=custom_llm_provider, - api_base=optional_params.api_base, - api_key=optional_params.api_key, - ) + ( + model, + _custom_llm_provider, + dynamic_api_key, + dynamic_api_base, + ) = litellm.get_llm_provider( + model=model, + custom_llm_provider=custom_llm_provider, + api_base=optional_params.api_base, + api_key=optional_params.api_key, ) - anthropic_messages_provider_config: Optional[BaseAnthropicMessagesConfig] = ( - ProviderConfigManager.get_provider_anthropic_messages_config( - model=model, - provider=litellm.LlmProviders(_custom_llm_provider), - ) + anthropic_messages_provider_config: Optional[ + BaseAnthropicMessagesConfig + ] = ProviderConfigManager.get_provider_anthropic_messages_config( + model=model, + provider=litellm.LlmProviders(_custom_llm_provider), ) if anthropic_messages_provider_config is None: raise ValueError( diff --git a/litellm/llms/azure/azure.py b/litellm/llms/azure/azure.py index 03c5cc09eb..aed813fdab 100644 --- a/litellm/llms/azure/azure.py +++ b/litellm/llms/azure/azure.py @@ -654,7 +654,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): ) -> EmbeddingResponse: response = None try: - openai_aclient = self.get_azure_openai_client( api_version=api_version, api_base=api_base, @@ -835,7 +834,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): "2023-10-01-preview", ] ): # CREATE + POLL for azure dall-e-2 calls - api_base = modify_url( original_url=api_base, new_path="/openai/images/generations:submit" ) @@ -867,7 +865,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): ) while response.json()["status"] not in ["succeeded", "failed"]: if time.time() - start_time > timeout_secs: - raise AzureOpenAIError( status_code=408, message="Operation polling timed out." ) @@ -935,7 +932,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): "2023-10-01-preview", ] ): # CREATE + POLL for azure dall-e-2 calls - api_base = modify_url( original_url=api_base, new_path="/openai/images/generations:submit" ) @@ -1199,7 +1195,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): client=None, litellm_params: Optional[dict] = None, ) -> HttpxBinaryResponseContent: - max_retries = optional_params.pop("max_retries", 2) if aspeech is not None and aspeech is True: @@ -1253,7 +1248,6 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): client=None, litellm_params: Optional[dict] = None, ) -> HttpxBinaryResponseContent: - azure_client: AsyncAzureOpenAI = self.get_azure_openai_client( api_base=api_base, api_version=api_version, diff --git a/litellm/llms/azure/batches/handler.py b/litellm/llms/azure/batches/handler.py index 1b93c526d5..7fc6388ba8 100644 --- a/litellm/llms/azure/batches/handler.py +++ b/litellm/llms/azure/batches/handler.py @@ -50,15 +50,15 @@ class AzureBatchesAPI(BaseAzureLLM): client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = None, litellm_params: Optional[dict] = None, ) -> Union[LiteLLMBatch, Coroutine[Any, Any, LiteLLMBatch]]: - azure_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - litellm_params=litellm_params or {}, - ) + azure_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, + litellm_params=litellm_params or {}, ) if azure_client is None: raise ValueError( @@ -96,15 +96,15 @@ class AzureBatchesAPI(BaseAzureLLM): client: Optional[AzureOpenAI] = None, litellm_params: Optional[dict] = None, ): - azure_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - litellm_params=litellm_params or {}, - ) + azure_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, + litellm_params=litellm_params or {}, ) if azure_client is None: raise ValueError( @@ -144,15 +144,15 @@ class AzureBatchesAPI(BaseAzureLLM): client: Optional[AzureOpenAI] = None, litellm_params: Optional[dict] = None, ): - azure_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - litellm_params=litellm_params or {}, - ) + azure_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, + litellm_params=litellm_params or {}, ) if azure_client is None: raise ValueError( @@ -183,15 +183,15 @@ class AzureBatchesAPI(BaseAzureLLM): client: Optional[AzureOpenAI] = None, litellm_params: Optional[dict] = None, ): - azure_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - litellm_params=litellm_params or {}, - ) + azure_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, + litellm_params=litellm_params or {}, ) if azure_client is None: raise ValueError( diff --git a/litellm/llms/azure/common_utils.py b/litellm/llms/azure/common_utils.py index 71092c8b99..5d61557c21 100644 --- a/litellm/llms/azure/common_utils.py +++ b/litellm/llms/azure/common_utils.py @@ -306,7 +306,6 @@ class BaseAzureLLM(BaseOpenAILLM): api_version: Optional[str], is_async: bool, ) -> dict: - azure_ad_token_provider: Optional[Callable[[], str]] = None # If we have api_key, then we have higher priority azure_ad_token = litellm_params.get("azure_ad_token") diff --git a/litellm/llms/azure/files/handler.py b/litellm/llms/azure/files/handler.py index d45ac9a315..98407d05d5 100644 --- a/litellm/llms/azure/files/handler.py +++ b/litellm/llms/azure/files/handler.py @@ -46,16 +46,15 @@ class AzureOpenAIFilesAPI(BaseAzureLLM): client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = None, litellm_params: Optional[dict] = None, ) -> Union[FileObject, Coroutine[Any, Any, FileObject]]: - - openai_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - litellm_params=litellm_params or {}, - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - ) + openai_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + litellm_params=litellm_params or {}, + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, ) if openai_client is None: raise ValueError( @@ -95,15 +94,15 @@ class AzureOpenAIFilesAPI(BaseAzureLLM): ) -> Union[ HttpxBinaryResponseContent, Coroutine[Any, Any, HttpxBinaryResponseContent] ]: - openai_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - litellm_params=litellm_params or {}, - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - ) + openai_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + litellm_params=litellm_params or {}, + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, ) if openai_client is None: raise ValueError( @@ -145,15 +144,15 @@ class AzureOpenAIFilesAPI(BaseAzureLLM): client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = None, litellm_params: Optional[dict] = None, ): - openai_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - litellm_params=litellm_params or {}, - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - ) + openai_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + litellm_params=litellm_params or {}, + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, ) if openai_client is None: raise ValueError( @@ -197,15 +196,15 @@ class AzureOpenAIFilesAPI(BaseAzureLLM): client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = None, litellm_params: Optional[dict] = None, ): - openai_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - litellm_params=litellm_params or {}, - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - ) + openai_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + litellm_params=litellm_params or {}, + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, ) if openai_client is None: raise ValueError( @@ -251,15 +250,15 @@ class AzureOpenAIFilesAPI(BaseAzureLLM): client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = None, litellm_params: Optional[dict] = None, ): - openai_client: Optional[Union[AzureOpenAI, AsyncAzureOpenAI]] = ( - self.get_azure_openai_client( - litellm_params=litellm_params or {}, - api_key=api_key, - api_base=api_base, - api_version=api_version, - client=client, - _is_async=_is_async, - ) + openai_client: Optional[ + Union[AzureOpenAI, AsyncAzureOpenAI] + ] = self.get_azure_openai_client( + litellm_params=litellm_params or {}, + api_key=api_key, + api_base=api_base, + api_version=api_version, + client=client, + _is_async=_is_async, ) if openai_client is None: raise ValueError( diff --git a/litellm/llms/azure/fine_tuning/handler.py b/litellm/llms/azure/fine_tuning/handler.py index 3d7cc336fb..429b834989 100644 --- a/litellm/llms/azure/fine_tuning/handler.py +++ b/litellm/llms/azure/fine_tuning/handler.py @@ -25,14 +25,7 @@ class AzureOpenAIFineTuningAPI(OpenAIFineTuningAPI, BaseAzureLLM): _is_async: bool = False, api_version: Optional[str] = None, litellm_params: Optional[dict] = None, - ) -> Optional[ - Union[ - OpenAI, - AsyncOpenAI, - AzureOpenAI, - AsyncAzureOpenAI, - ] - ]: + ) -> Optional[Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI,]]: # Override to use Azure-specific client initialization if isinstance(client, OpenAI) or isinstance(client, AsyncOpenAI): client = None diff --git a/litellm/llms/azure_ai/chat/transformation.py b/litellm/llms/azure_ai/chat/transformation.py index 154f345537..a1fd24efa1 100644 --- a/litellm/llms/azure_ai/chat/transformation.py +++ b/litellm/llms/azure_ai/chat/transformation.py @@ -145,7 +145,6 @@ class AzureAIStudioConfig(OpenAIConfig): 2. If message contains an image or audio, send as is (user-intended) """ for message in messages: - # Do nothing if the message contains an image or audio if _audio_or_image_in_message_content(message): continue diff --git a/litellm/llms/azure_ai/embed/cohere_transformation.py b/litellm/llms/azure_ai/embed/cohere_transformation.py index 38b0dbbe23..64433c21b6 100644 --- a/litellm/llms/azure_ai/embed/cohere_transformation.py +++ b/litellm/llms/azure_ai/embed/cohere_transformation.py @@ -22,7 +22,6 @@ class AzureAICohereConfig: pass def _map_azure_model_group(self, model: str) -> str: - if model == "offer-cohere-embed-multili-paygo": return "Cohere-embed-v3-multilingual" elif model == "offer-cohere-embed-english-paygo": diff --git a/litellm/llms/azure_ai/embed/handler.py b/litellm/llms/azure_ai/embed/handler.py index f33c979ca2..da39c5f3b8 100644 --- a/litellm/llms/azure_ai/embed/handler.py +++ b/litellm/llms/azure_ai/embed/handler.py @@ -17,7 +17,6 @@ from .cohere_transformation import AzureAICohereConfig class AzureAIEmbedding(OpenAIChatCompletion): - def _process_response( self, image_embedding_responses: Optional[List], @@ -145,7 +144,6 @@ class AzureAIEmbedding(OpenAIChatCompletion): api_base: Optional[str] = None, client=None, ) -> EmbeddingResponse: - ( image_embeddings_request, v1_embeddings_request, diff --git a/litellm/llms/azure_ai/rerank/transformation.py b/litellm/llms/azure_ai/rerank/transformation.py index 842511f30d..4465e0d70a 100644 --- a/litellm/llms/azure_ai/rerank/transformation.py +++ b/litellm/llms/azure_ai/rerank/transformation.py @@ -17,6 +17,7 @@ class AzureAIRerankConfig(CohereRerankConfig): """ Azure AI Rerank - Follows the same Spec as Cohere Rerank """ + def get_complete_url(self, api_base: Optional[str], model: str) -> str: if api_base is None: raise ValueError( diff --git a/litellm/llms/base.py b/litellm/llms/base.py index deced222ca..abc314bba0 100644 --- a/litellm/llms/base.py +++ b/litellm/llms/base.py @@ -9,7 +9,6 @@ from litellm.types.utils import ModelResponse, TextCompletionResponse class BaseLLM: - _client_session: Optional[httpx.Client] = None def process_response( diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index 45ea06b9e4..b4b120776c 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -218,7 +218,6 @@ class BaseConfig(ABC): json_schema = value["json_schema"]["schema"] if json_schema and not is_response_format_supported: - _tool_choice = ChatCompletionToolChoiceObjectParam( type="function", function=ChatCompletionToolChoiceFunctionParam( diff --git a/litellm/llms/base_llm/responses/transformation.py b/litellm/llms/base_llm/responses/transformation.py index 29555c55da..e98a579845 100644 --- a/litellm/llms/base_llm/responses/transformation.py +++ b/litellm/llms/base_llm/responses/transformation.py @@ -58,7 +58,6 @@ class BaseResponsesAPIConfig(ABC): model: str, drop_params: bool, ) -> Dict: - pass @abstractmethod diff --git a/litellm/llms/bedrock/chat/converse_handler.py b/litellm/llms/bedrock/chat/converse_handler.py index a4230177b5..7f529c637a 100644 --- a/litellm/llms/bedrock/chat/converse_handler.py +++ b/litellm/llms/bedrock/chat/converse_handler.py @@ -81,7 +81,6 @@ def make_sync_call( class BedrockConverseLLM(BaseAWSLLM): - def __init__(self) -> None: super().__init__() @@ -114,7 +113,6 @@ class BedrockConverseLLM(BaseAWSLLM): fake_stream: bool = False, json_mode: Optional[bool] = False, ) -> CustomStreamWrapper: - request_data = await litellm.AmazonConverseConfig()._async_transform_request( model=model, messages=messages, @@ -179,7 +177,6 @@ class BedrockConverseLLM(BaseAWSLLM): headers: dict = {}, client: Optional[AsyncHTTPHandler] = None, ) -> Union[ModelResponse, CustomStreamWrapper]: - request_data = await litellm.AmazonConverseConfig()._async_transform_request( model=model, messages=messages, @@ -265,7 +262,6 @@ class BedrockConverseLLM(BaseAWSLLM): extra_headers: Optional[dict] = None, client: Optional[Union[AsyncHTTPHandler, HTTPHandler]] = None, ): - ## SETUP ## stream = optional_params.pop("stream", None) unencoded_model_id = optional_params.pop("model_id", None) @@ -301,9 +297,9 @@ class BedrockConverseLLM(BaseAWSLLM): aws_sts_endpoint = optional_params.pop("aws_sts_endpoint", None) optional_params.pop("aws_region_name", None) - litellm_params["aws_region_name"] = ( - aws_region_name # [DO NOT DELETE] important for async calls - ) + litellm_params[ + "aws_region_name" + ] = aws_region_name # [DO NOT DELETE] important for async calls credentials: Credentials = self.get_credentials( aws_access_key_id=aws_access_key_id, diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index ced9c469b3..05386c62b5 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -223,7 +223,6 @@ class AmazonConverseConfig(BaseConfig): ) for param, value in non_default_params.items(): if param == "response_format" and isinstance(value, dict): - ignore_response_format_types = ["text"] if value["type"] in ignore_response_format_types: # value is a no-op continue @@ -715,9 +714,9 @@ class AmazonConverseConfig(BaseConfig): chat_completion_message: ChatCompletionResponseMessage = {"role": "assistant"} content_str = "" tools: List[ChatCompletionToolCallChunk] = [] - reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] = ( - None - ) + reasoningContentBlocks: Optional[ + List[BedrockConverseReasoningContentBlock] + ] = None if message is not None: for idx, content in enumerate(message["content"]): @@ -727,7 +726,6 @@ class AmazonConverseConfig(BaseConfig): if "text" in content: content_str += content["text"] if "toolUse" in content: - ## check tool name was formatted by litellm _response_tool_name = content["toolUse"]["name"] response_tool_name = get_bedrock_tool_name( @@ -754,12 +752,12 @@ class AmazonConverseConfig(BaseConfig): chat_completion_message["provider_specific_fields"] = { "reasoningContentBlocks": reasoningContentBlocks, } - chat_completion_message["reasoning_content"] = ( - self._transform_reasoning_content(reasoningContentBlocks) - ) - chat_completion_message["thinking_blocks"] = ( - self._transform_thinking_blocks(reasoningContentBlocks) - ) + chat_completion_message[ + "reasoning_content" + ] = self._transform_reasoning_content(reasoningContentBlocks) + chat_completion_message[ + "thinking_blocks" + ] = self._transform_thinking_blocks(reasoningContentBlocks) chat_completion_message["content"] = content_str if json_mode is True and tools is not None and len(tools) == 1: # to support 'json_schema' logic on bedrock models diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 5b02fd3158..09bdd63572 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -496,9 +496,9 @@ class BedrockLLM(BaseAWSLLM): content=None, ) model_response.choices[0].message = _message # type: ignore - model_response._hidden_params["original_response"] = ( - outputText # allow user to access raw anthropic tool calling response - ) + model_response._hidden_params[ + "original_response" + ] = outputText # allow user to access raw anthropic tool calling response if ( _is_function_call is True and stream is not None @@ -806,9 +806,9 @@ class BedrockLLM(BaseAWSLLM): ): # completion(top_k=3) > anthropic_config(top_k=3) <- allows for dynamic variables to be passed in inference_params[k] = v if stream is True: - inference_params["stream"] = ( - True # cohere requires stream = True in inference params - ) + inference_params[ + "stream" + ] = True # cohere requires stream = True in inference params data = json.dumps({"prompt": prompt, **inference_params}) elif provider == "anthropic": if model.startswith("anthropic.claude-3"): @@ -1205,7 +1205,6 @@ class BedrockLLM(BaseAWSLLM): def get_response_stream_shape(): global _response_stream_shape_cache if _response_stream_shape_cache is None: - from botocore.loaders import Loader from botocore.model import ServiceModel @@ -1539,7 +1538,6 @@ class AmazonDeepSeekR1StreamDecoder(AWSEventStreamDecoder): model: str, sync_stream: bool, ) -> None: - super().__init__(model=model) from litellm.llms.bedrock.chat.invoke_transformations.amazon_deepseek_transformation import ( AmazonDeepseekR1ResponseIterator, diff --git a/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py index 133eb659df..d1212705d8 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py @@ -225,9 +225,9 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM): ): # completion(top_k=3) > anthropic_config(top_k=3) <- allows for dynamic variables to be passed in inference_params[k] = v if stream is True: - inference_params["stream"] = ( - True # cohere requires stream = True in inference params - ) + inference_params[ + "stream" + ] = True # cohere requires stream = True in inference params request_data = {"prompt": prompt, **inference_params} elif provider == "anthropic": return litellm.AmazonAnthropicClaude3Config().transform_request( @@ -311,7 +311,6 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM): api_key: Optional[str] = None, json_mode: Optional[bool] = None, ) -> ModelResponse: - try: completion_response = raw_response.json() except Exception: diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 4677a579ed..f4a1170660 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -314,7 +314,6 @@ def get_bedrock_tool_name(response_tool_name: str) -> str: class BedrockModelInfo(BaseLLMModelInfo): - global_config = AmazonBedrockGlobalConfig() all_global_regions = global_config.get_all_regions() diff --git a/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py b/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py index 6c1147f24a..338029adc3 100644 --- a/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py +++ b/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py @@ -33,9 +33,9 @@ class AmazonTitanMultimodalEmbeddingG1Config: ) -> dict: for k, v in non_default_params.items(): if k == "dimensions": - optional_params["embeddingConfig"] = ( - AmazonTitanMultimodalEmbeddingConfig(outputEmbeddingLength=v) - ) + optional_params[ + "embeddingConfig" + ] = AmazonTitanMultimodalEmbeddingConfig(outputEmbeddingLength=v) return optional_params def _transform_request( @@ -58,7 +58,6 @@ class AmazonTitanMultimodalEmbeddingG1Config: def _transform_response( self, response_list: List[dict], model: str ) -> EmbeddingResponse: - total_prompt_tokens = 0 transformed_responses: List[Embedding] = [] for index, response in enumerate(response_list): diff --git a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py index 33249f9af8..b331dd1b1d 100644 --- a/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py +++ b/litellm/llms/bedrock/image/amazon_nova_canvas_transformation.py @@ -1,12 +1,16 @@ import types -from typing import List, Optional +from typing import Any, Dict, List, Optional from openai.types.image import Image from litellm.types.llms.bedrock import ( - AmazonNovaCanvasTextToImageRequest, AmazonNovaCanvasTextToImageResponse, - AmazonNovaCanvasTextToImageParams, AmazonNovaCanvasRequestBase, AmazonNovaCanvasColorGuidedGenerationParams, + AmazonNovaCanvasColorGuidedGenerationParams, AmazonNovaCanvasColorGuidedRequest, + AmazonNovaCanvasImageGenerationConfig, + AmazonNovaCanvasRequestBase, + AmazonNovaCanvasTextToImageParams, + AmazonNovaCanvasTextToImageRequest, + AmazonNovaCanvasTextToImageResponse, ) from litellm.types.utils import ImageResponse @@ -23,7 +27,7 @@ class AmazonNovaCanvasConfig: k: v for k, v in cls.__dict__.items() if not k.startswith("__") - and not isinstance( + and not isinstance( v, ( types.FunctionType, @@ -32,13 +36,12 @@ class AmazonNovaCanvasConfig: staticmethod, ), ) - and v is not None + and v is not None } @classmethod def get_supported_openai_params(cls, model: Optional[str] = None) -> List: - """ - """ + """ """ return ["n", "size", "quality"] @classmethod @@ -56,7 +59,7 @@ class AmazonNovaCanvasConfig: @classmethod def transform_request_body( - cls, text: str, optional_params: dict + cls, text: str, optional_params: dict ) -> AmazonNovaCanvasRequestBase: """ Transform the request body for Amazon Nova Canvas model @@ -65,18 +68,64 @@ class AmazonNovaCanvasConfig: image_generation_config = optional_params.pop("imageGenerationConfig", {}) image_generation_config = {**image_generation_config, **optional_params} if task_type == "TEXT_IMAGE": - text_to_image_params = image_generation_config.pop("textToImageParams", {}) - text_to_image_params = {"text" :text, **text_to_image_params} - text_to_image_params = AmazonNovaCanvasTextToImageParams(**text_to_image_params) - return AmazonNovaCanvasTextToImageRequest(textToImageParams=text_to_image_params, taskType=task_type, - imageGenerationConfig=image_generation_config) + text_to_image_params: Dict[str, Any] = image_generation_config.pop( + "textToImageParams", {} + ) + text_to_image_params = {"text": text, **text_to_image_params} + try: + text_to_image_params_typed = AmazonNovaCanvasTextToImageParams( + **text_to_image_params # type: ignore + ) + except Exception as e: + raise ValueError( + f"Error transforming text to image params: {e}. Got params: {text_to_image_params}, Expected params: {AmazonNovaCanvasTextToImageParams.__annotations__}" + ) + + try: + image_generation_config_typed = AmazonNovaCanvasImageGenerationConfig( + **image_generation_config + ) + except Exception as e: + raise ValueError( + f"Error transforming image generation config: {e}. Got params: {image_generation_config}, Expected params: {AmazonNovaCanvasImageGenerationConfig.__annotations__}" + ) + + return AmazonNovaCanvasTextToImageRequest( + textToImageParams=text_to_image_params_typed, + taskType=task_type, + imageGenerationConfig=image_generation_config_typed, + ) if task_type == "COLOR_GUIDED_GENERATION": - color_guided_generation_params = image_generation_config.pop("colorGuidedGenerationParams", {}) - color_guided_generation_params = {"text": text, **color_guided_generation_params} - color_guided_generation_params = AmazonNovaCanvasColorGuidedGenerationParams(**color_guided_generation_params) - return AmazonNovaCanvasColorGuidedRequest(taskType=task_type, - colorGuidedGenerationParams=color_guided_generation_params, - imageGenerationConfig=image_generation_config) + color_guided_generation_params: Dict[ + str, Any + ] = image_generation_config.pop("colorGuidedGenerationParams", {}) + color_guided_generation_params = { + "text": text, + **color_guided_generation_params, + } + try: + color_guided_generation_params_typed = AmazonNovaCanvasColorGuidedGenerationParams( + **color_guided_generation_params # type: ignore + ) + except Exception as e: + raise ValueError( + f"Error transforming color guided generation params: {e}. Got params: {color_guided_generation_params}, Expected params: {AmazonNovaCanvasColorGuidedGenerationParams.__annotations__}" + ) + + try: + image_generation_config_typed = AmazonNovaCanvasImageGenerationConfig( + **image_generation_config + ) + except Exception as e: + raise ValueError( + f"Error transforming image generation config: {e}. Got params: {image_generation_config}, Expected params: {AmazonNovaCanvasImageGenerationConfig.__annotations__}" + ) + + return AmazonNovaCanvasColorGuidedRequest( + taskType=task_type, + colorGuidedGenerationParams=color_guided_generation_params_typed, + imageGenerationConfig=image_generation_config_typed, + ) raise NotImplementedError(f"Task type {task_type} is not supported") @classmethod @@ -87,7 +136,9 @@ class AmazonNovaCanvasConfig: _size = non_default_params.get("size") if _size is not None: width, height = _size.split("x") - optional_params["width"], optional_params["height"] = int(width), int(height) + optional_params["width"], optional_params["height"] = int(width), int( + height + ) if non_default_params.get("n") is not None: optional_params["numberOfImages"] = non_default_params.get("n") if non_default_params.get("quality") is not None: @@ -99,7 +150,7 @@ class AmazonNovaCanvasConfig: @classmethod def transform_response_dict_to_openai_response( - cls, model_response: ImageResponse, response_dict: dict + cls, model_response: ImageResponse, response_dict: dict ) -> ImageResponse: """ Transform the response dict to the OpenAI response diff --git a/litellm/llms/bedrock/image/image_handler.py b/litellm/llms/bedrock/image/image_handler.py index 8f7762e547..27258aa20f 100644 --- a/litellm/llms/bedrock/image/image_handler.py +++ b/litellm/llms/bedrock/image/image_handler.py @@ -267,7 +267,11 @@ class BedrockImageGeneration(BaseAWSLLM): **inference_params, } elif provider == "amazon": - return dict(litellm.AmazonNovaCanvasConfig.transform_request_body(text=prompt, optional_params=optional_params)) + return dict( + litellm.AmazonNovaCanvasConfig.transform_request_body( + text=prompt, optional_params=optional_params + ) + ) else: raise BedrockError( status_code=422, message=f"Unsupported model={model}, passed in" @@ -303,8 +307,11 @@ class BedrockImageGeneration(BaseAWSLLM): config_class = ( litellm.AmazonStability3Config if litellm.AmazonStability3Config._is_stability_3_model(model=model) - else litellm.AmazonNovaCanvasConfig if litellm.AmazonNovaCanvasConfig._is_nova_model(model=model) - else litellm.AmazonStabilityConfig + else ( + litellm.AmazonNovaCanvasConfig + if litellm.AmazonNovaCanvasConfig._is_nova_model(model=model) + else litellm.AmazonStabilityConfig + ) ) config_class.transform_response_dict_to_openai_response( model_response=model_response, diff --git a/litellm/llms/bedrock/rerank/handler.py b/litellm/llms/bedrock/rerank/handler.py index cd8be6912c..f5a532bec1 100644 --- a/litellm/llms/bedrock/rerank/handler.py +++ b/litellm/llms/bedrock/rerank/handler.py @@ -60,7 +60,6 @@ class BedrockRerankHandler(BaseAWSLLM): extra_headers: Optional[dict] = None, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, ) -> RerankResponse: - request_data = RerankRequest( model=model, query=query, diff --git a/litellm/llms/bedrock/rerank/transformation.py b/litellm/llms/bedrock/rerank/transformation.py index a5380febe9..be8250a967 100644 --- a/litellm/llms/bedrock/rerank/transformation.py +++ b/litellm/llms/bedrock/rerank/transformation.py @@ -29,7 +29,6 @@ from litellm.types.rerank import ( class BedrockRerankConfig: - def _transform_sources( self, documents: List[Union[str, dict]] ) -> List[BedrockRerankSource]: diff --git a/litellm/llms/codestral/completion/handler.py b/litellm/llms/codestral/completion/handler.py index fc6d2886a9..555f7fccfb 100644 --- a/litellm/llms/codestral/completion/handler.py +++ b/litellm/llms/codestral/completion/handler.py @@ -314,7 +314,6 @@ class CodestralTextCompletion: return _response ### SYNC COMPLETION else: - response = litellm.module_level_client.post( url=completion_url, headers=headers, @@ -352,13 +351,11 @@ class CodestralTextCompletion: logger_fn=None, headers={}, ) -> TextCompletionResponse: - async_handler = get_async_httpx_client( llm_provider=litellm.LlmProviders.TEXT_COMPLETION_CODESTRAL, params={"timeout": timeout}, ) try: - response = await async_handler.post( api_base, headers=headers, data=json.dumps(data) ) diff --git a/litellm/llms/codestral/completion/transformation.py b/litellm/llms/codestral/completion/transformation.py index 5955e91deb..fc7b6f5dbb 100644 --- a/litellm/llms/codestral/completion/transformation.py +++ b/litellm/llms/codestral/completion/transformation.py @@ -78,7 +78,6 @@ class CodestralTextCompletionConfig(OpenAITextCompletionConfig): return optional_params def _chunk_parser(self, chunk_data: str) -> GenericStreamingChunk: - text = "" is_finished = False finish_reason = None diff --git a/litellm/llms/cohere/chat/transformation.py b/litellm/llms/cohere/chat/transformation.py index 3ceec2dbba..fbaedca8f6 100644 --- a/litellm/llms/cohere/chat/transformation.py +++ b/litellm/llms/cohere/chat/transformation.py @@ -180,7 +180,6 @@ class CohereChatConfig(BaseConfig): litellm_params: dict, headers: dict, ) -> dict: - ## Load Config for k, v in litellm.CohereChatConfig.get_config().items(): if ( @@ -222,7 +221,6 @@ class CohereChatConfig(BaseConfig): api_key: Optional[str] = None, json_mode: Optional[bool] = None, ) -> ModelResponse: - try: raw_response_json = raw_response.json() model_response.choices[0].message.content = raw_response_json["text"] # type: ignore diff --git a/litellm/llms/cohere/embed/handler.py b/litellm/llms/cohere/embed/handler.py index e7f22ea72a..7a25bf7e54 100644 --- a/litellm/llms/cohere/embed/handler.py +++ b/litellm/llms/cohere/embed/handler.py @@ -56,7 +56,6 @@ async def async_embedding( encoding: Callable, client: Optional[AsyncHTTPHandler] = None, ): - ## LOGGING logging_obj.pre_call( input=input, diff --git a/litellm/llms/cohere/embed/transformation.py b/litellm/llms/cohere/embed/transformation.py index 22e157a0fd..837dd5e006 100644 --- a/litellm/llms/cohere/embed/transformation.py +++ b/litellm/llms/cohere/embed/transformation.py @@ -72,7 +72,6 @@ class CohereEmbeddingConfig: return transformed_request def _calculate_usage(self, input: List[str], encoding: Any, meta: dict) -> Usage: - input_tokens = 0 text_tokens: Optional[int] = meta.get("billed_units", {}).get("input_tokens") @@ -111,7 +110,6 @@ class CohereEmbeddingConfig: encoding: Any, input: list, ) -> EmbeddingResponse: - response_json = response.json() ## LOGGING logging_obj.post_call( diff --git a/litellm/llms/cohere/rerank/transformation.py b/litellm/llms/cohere/rerank/transformation.py index f3624d9216..22782c1300 100644 --- a/litellm/llms/cohere/rerank/transformation.py +++ b/litellm/llms/cohere/rerank/transformation.py @@ -148,4 +148,4 @@ class CohereRerankConfig(BaseRerankConfig): def get_error_class( self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] ) -> BaseLLMException: - return CohereError(message=error_message, status_code=status_code) \ No newline at end of file + return CohereError(message=error_message, status_code=status_code) diff --git a/litellm/llms/cohere/rerank_v2/transformation.py b/litellm/llms/cohere/rerank_v2/transformation.py index a93cb982a7..74e760460d 100644 --- a/litellm/llms/cohere/rerank_v2/transformation.py +++ b/litellm/llms/cohere/rerank_v2/transformation.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional, Union from litellm.llms.cohere.rerank.transformation import CohereRerankConfig from litellm.types.rerank import OptionalRerankParams, RerankRequest + class CohereRerankV2Config(CohereRerankConfig): """ Reference: https://docs.cohere.com/v2/reference/rerank @@ -77,4 +78,4 @@ class CohereRerankV2Config(CohereRerankConfig): return_documents=optional_rerank_params.get("return_documents", None), max_tokens_per_doc=optional_rerank_params.get("max_tokens_per_doc", None), ) - return rerank_request.model_dump(exclude_none=True) \ No newline at end of file + return rerank_request.model_dump(exclude_none=True) diff --git a/litellm/llms/custom_httpx/aiohttp_handler.py b/litellm/llms/custom_httpx/aiohttp_handler.py index c865fee17e..9568ce7185 100644 --- a/litellm/llms/custom_httpx/aiohttp_handler.py +++ b/litellm/llms/custom_httpx/aiohttp_handler.py @@ -32,7 +32,6 @@ DEFAULT_TIMEOUT = 600 class BaseLLMAIOHTTPHandler: - def __init__(self): self.client_session: Optional[aiohttp.ClientSession] = None @@ -110,7 +109,6 @@ class BaseLLMAIOHTTPHandler: content: Any = None, params: Optional[dict] = None, ) -> httpx.Response: - max_retry_on_unprocessable_entity_error = ( provider_config.max_retry_on_unprocessable_entity_error ) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 34d70434d5..23d7fe4b4d 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -114,7 +114,6 @@ class AsyncHTTPHandler: event_hooks: Optional[Mapping[str, List[Callable[..., Any]]]], ssl_verify: Optional[VerifyTypes] = None, ) -> httpx.AsyncClient: - # SSL certificates (a.k.a CA bundle) used to verify the identity of requested hosts. # /path/to/certificate.pem if ssl_verify is None: @@ -590,7 +589,6 @@ class HTTPHandler: timeout: Optional[Union[float, httpx.Timeout]] = None, ): try: - if timeout is not None: req = self.client.build_request( "PATCH", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore @@ -609,7 +607,6 @@ class HTTPHandler: llm_provider="litellm-httpx-handler", ) except httpx.HTTPStatusError as e: - if stream is True: setattr(e, "message", mask_sensitive_info(e.response.read())) setattr(e, "text", mask_sensitive_info(e.response.read())) @@ -635,7 +632,6 @@ class HTTPHandler: timeout: Optional[Union[float, httpx.Timeout]] = None, ): try: - if timeout is not None: req = self.client.build_request( "PUT", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 872626c747..12736640f1 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -41,7 +41,6 @@ else: class BaseLLMHTTPHandler: - async def _make_common_async_call( self, async_httpx_client: AsyncHTTPHandler, @@ -109,7 +108,6 @@ class BaseLLMHTTPHandler: logging_obj: LiteLLMLoggingObj, stream: bool = False, ) -> httpx.Response: - max_retry_on_unprocessable_entity_error = ( provider_config.max_retry_on_unprocessable_entity_error ) @@ -599,7 +597,6 @@ class BaseLLMHTTPHandler: aembedding: bool = False, headers={}, ) -> EmbeddingResponse: - provider_config = ProviderConfigManager.get_provider_embedding_config( model=model, provider=litellm.LlmProviders(custom_llm_provider) ) @@ -742,7 +739,6 @@ class BaseLLMHTTPHandler: api_base: Optional[str] = None, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, ) -> RerankResponse: - # get config from model, custom llm provider headers = provider_config.validate_environment( api_key=api_key, @@ -828,7 +824,6 @@ class BaseLLMHTTPHandler: timeout: Optional[Union[float, httpx.Timeout]] = None, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, ) -> RerankResponse: - if client is None or not isinstance(client, AsyncHTTPHandler): async_httpx_client = get_async_httpx_client( llm_provider=litellm.LlmProviders(custom_llm_provider) diff --git a/litellm/llms/databricks/common_utils.py b/litellm/llms/databricks/common_utils.py index e8481e25b2..76bd281d4d 100644 --- a/litellm/llms/databricks/common_utils.py +++ b/litellm/llms/databricks/common_utils.py @@ -16,9 +16,9 @@ class DatabricksBase: api_base = api_base or f"{databricks_client.config.host}/serving-endpoints" if api_key is None: - databricks_auth_headers: dict[str, str] = ( - databricks_client.config.authenticate() - ) + databricks_auth_headers: dict[ + str, str + ] = databricks_client.config.authenticate() headers = {**databricks_auth_headers, **headers} return api_base, headers diff --git a/litellm/llms/databricks/embed/transformation.py b/litellm/llms/databricks/embed/transformation.py index 53e3b30dd2..a113a349cc 100644 --- a/litellm/llms/databricks/embed/transformation.py +++ b/litellm/llms/databricks/embed/transformation.py @@ -11,9 +11,9 @@ class DatabricksEmbeddingConfig: Reference: https://learn.microsoft.com/en-us/azure/databricks/machine-learning/foundation-models/api-reference#--embedding-task """ - instruction: Optional[str] = ( - None # An optional instruction to pass to the embedding model. BGE Authors recommend 'Represent this sentence for searching relevant passages:' for retrieval queries - ) + instruction: Optional[ + str + ] = None # An optional instruction to pass to the embedding model. BGE Authors recommend 'Represent this sentence for searching relevant passages:' for retrieval queries def __init__(self, instruction: Optional[str] = None) -> None: locals_ = locals().copy() diff --git a/litellm/llms/databricks/streaming_utils.py b/litellm/llms/databricks/streaming_utils.py index 2db53df908..eebe318288 100644 --- a/litellm/llms/databricks/streaming_utils.py +++ b/litellm/llms/databricks/streaming_utils.py @@ -55,7 +55,6 @@ class ModelResponseIterator: usage_chunk: Optional[Usage] = getattr(processed_chunk, "usage", None) if usage_chunk is not None: - usage = ChatCompletionUsageBlock( prompt_tokens=usage_chunk.prompt_tokens, completion_tokens=usage_chunk.completion_tokens, diff --git a/litellm/llms/deepgram/audio_transcription/transformation.py b/litellm/llms/deepgram/audio_transcription/transformation.py index 90720a77f7..20599e3994 100644 --- a/litellm/llms/deepgram/audio_transcription/transformation.py +++ b/litellm/llms/deepgram/audio_transcription/transformation.py @@ -126,9 +126,9 @@ class DeepgramAudioTranscriptionConfig(BaseAudioTranscriptionConfig): # Add additional metadata matching OpenAI format response["task"] = "transcribe" - response["language"] = ( - "english" # Deepgram auto-detects but doesn't return language - ) + response[ + "language" + ] = "english" # Deepgram auto-detects but doesn't return language response["duration"] = response_json["metadata"]["duration"] # Transform words to match OpenAI format diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index 180cf7dc69..fe70ebe77e 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -14,7 +14,6 @@ from ...openai.chat.gpt_transformation import OpenAIGPTConfig class DeepSeekChatConfig(OpenAIGPTConfig): - def _transform_messages( self, messages: List[AllMessageValues], model: str ) -> List[AllMessageValues]: diff --git a/litellm/llms/deprecated_providers/aleph_alpha.py b/litellm/llms/deprecated_providers/aleph_alpha.py index 81ad134641..4cfede2a1b 100644 --- a/litellm/llms/deprecated_providers/aleph_alpha.py +++ b/litellm/llms/deprecated_providers/aleph_alpha.py @@ -77,9 +77,9 @@ class AlephAlphaConfig: - `control_log_additive` (boolean; default value: true): Method of applying control to attention scores. """ - maximum_tokens: Optional[int] = ( - litellm.max_tokens - ) # aleph alpha requires max tokens + maximum_tokens: Optional[ + int + ] = litellm.max_tokens # aleph alpha requires max tokens minimum_tokens: Optional[int] = None echo: Optional[bool] = None temperature: Optional[int] = None diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index 1c82f24ac0..4def12adb7 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -88,7 +88,6 @@ class FireworksAIConfig(OpenAIGPTConfig): model: str, drop_params: bool, ) -> dict: - supported_openai_params = self.get_supported_openai_params(model=model) is_tools_set = any( param == "tools" and value is not None @@ -104,7 +103,6 @@ class FireworksAIConfig(OpenAIGPTConfig): # pass through the value of tool choice optional_params["tool_choice"] = value elif param == "response_format": - if ( is_tools_set ): # fireworks ai doesn't support tools and response_format together @@ -223,7 +221,6 @@ class FireworksAIConfig(OpenAIGPTConfig): return api_base, dynamic_api_key def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None): - api_base, api_key = self._get_openai_compatible_provider_info( api_base=api_base, api_key=api_key ) diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index fbc1916dcc..0d5956122e 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -90,7 +90,6 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): model: str, drop_params: bool, ) -> Dict: - if litellm.vertex_ai_safety_settings is not None: optional_params["safety_settings"] = litellm.vertex_ai_safety_settings return super().map_openai_params( diff --git a/litellm/llms/gemini/common_utils.py b/litellm/llms/gemini/common_utils.py index 7f266c0536..4c3357a500 100644 --- a/litellm/llms/gemini/common_utils.py +++ b/litellm/llms/gemini/common_utils.py @@ -25,7 +25,6 @@ class GeminiModelInfo(BaseLLMModelInfo): def get_models( self, api_key: Optional[str] = None, api_base: Optional[str] = None ) -> List[str]: - api_base = GeminiModelInfo.get_api_base(api_base) api_key = GeminiModelInfo.get_api_key(api_key) if api_base is None or api_key is None: diff --git a/litellm/llms/groq/chat/transformation.py b/litellm/llms/groq/chat/transformation.py index 5b24f7d112..b0ee69bed2 100644 --- a/litellm/llms/groq/chat/transformation.py +++ b/litellm/llms/groq/chat/transformation.py @@ -18,7 +18,6 @@ from ...openai.chat.gpt_transformation import OpenAIGPTConfig class GroqChatConfig(OpenAIGPTConfig): - frequency_penalty: Optional[int] = None function_call: Optional[Union[str, dict]] = None functions: Optional[list] = None diff --git a/litellm/llms/groq/stt/transformation.py b/litellm/llms/groq/stt/transformation.py index c4dbd8d0ca..b467fab14f 100644 --- a/litellm/llms/groq/stt/transformation.py +++ b/litellm/llms/groq/stt/transformation.py @@ -9,7 +9,6 @@ import litellm class GroqSTTConfig: - frequency_penalty: Optional[int] = None function_call: Optional[Union[str, dict]] = None functions: Optional[list] = None diff --git a/litellm/llms/huggingface/chat/transformation.py b/litellm/llms/huggingface/chat/transformation.py index 858fda473e..082960b2c2 100644 --- a/litellm/llms/huggingface/chat/transformation.py +++ b/litellm/llms/huggingface/chat/transformation.py @@ -40,17 +40,17 @@ class HuggingfaceChatConfig(BaseConfig): Reference: https://huggingface.github.io/text-generation-inference/#/Text%20Generation%20Inference/compat_generate """ - hf_task: Optional[hf_tasks] = ( - None # litellm-specific param, used to know the api spec to use when calling huggingface api - ) + hf_task: Optional[ + hf_tasks + ] = None # litellm-specific param, used to know the api spec to use when calling huggingface api best_of: Optional[int] = None decoder_input_details: Optional[bool] = None details: Optional[bool] = True # enables returning logprobs + best of max_new_tokens: Optional[int] = None repetition_penalty: Optional[float] = None - return_full_text: Optional[bool] = ( - False # by default don't return the input as part of the output - ) + return_full_text: Optional[ + bool + ] = False # by default don't return the input as part of the output seed: Optional[int] = None temperature: Optional[float] = None top_k: Optional[int] = None @@ -120,9 +120,9 @@ class HuggingfaceChatConfig(BaseConfig): optional_params["top_p"] = value if param == "n": optional_params["best_of"] = value - optional_params["do_sample"] = ( - True # Need to sample if you want best of for hf inference endpoints - ) + optional_params[ + "do_sample" + ] = True # Need to sample if you want best of for hf inference endpoints if param == "stream": optional_params["stream"] = value if param == "stop": @@ -362,9 +362,9 @@ class HuggingfaceChatConfig(BaseConfig): "content-type": "application/json", } if api_key is not None: - default_headers["Authorization"] = ( - f"Bearer {api_key}" # Huggingface Inference Endpoint default is to accept bearer tokens - ) + default_headers[ + "Authorization" + ] = f"Bearer {api_key}" # Huggingface Inference Endpoint default is to accept bearer tokens headers = {**headers, **default_headers} return headers diff --git a/litellm/llms/maritalk.py b/litellm/llms/maritalk.py index 5f2b8d71bc..418d13b344 100644 --- a/litellm/llms/maritalk.py +++ b/litellm/llms/maritalk.py @@ -17,7 +17,6 @@ class MaritalkError(BaseLLMException): class MaritalkConfig(OpenAIGPTConfig): - def __init__( self, frequency_penalty: Optional[float] = None, diff --git a/litellm/llms/ollama/completion/transformation.py b/litellm/llms/ollama/completion/transformation.py index b4db95cfa1..b007bbb2bc 100644 --- a/litellm/llms/ollama/completion/transformation.py +++ b/litellm/llms/ollama/completion/transformation.py @@ -89,9 +89,9 @@ class OllamaConfig(BaseConfig): repeat_penalty: Optional[float] = None temperature: Optional[float] = None seed: Optional[int] = None - stop: Optional[list] = ( - None # stop is a list based on this - https://github.com/ollama/ollama/pull/442 - ) + stop: Optional[ + list + ] = None # stop is a list based on this - https://github.com/ollama/ollama/pull/442 tfs_z: Optional[float] = None num_predict: Optional[int] = None top_k: Optional[int] = None diff --git a/litellm/llms/openai/chat/gpt_transformation.py b/litellm/llms/openai/chat/gpt_transformation.py index c765f97979..1fde23c9c2 100644 --- a/litellm/llms/openai/chat/gpt_transformation.py +++ b/litellm/llms/openai/chat/gpt_transformation.py @@ -391,7 +391,6 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig): class OpenAIChatCompletionStreamingHandler(BaseModelResponseIterator): - def chunk_parser(self, chunk: dict) -> ModelResponseStream: try: return ModelResponseStream( diff --git a/litellm/llms/openai/completion/handler.py b/litellm/llms/openai/completion/handler.py index 2e60f55b57..fa31c487cd 100644 --- a/litellm/llms/openai/completion/handler.py +++ b/litellm/llms/openai/completion/handler.py @@ -220,7 +220,6 @@ class OpenAITextCompletion(BaseLLM): client=None, organization=None, ): - if client is None: openai_client = OpenAI( api_key=api_key, diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index 1aef72d3fa..43fbc1f219 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -111,9 +111,9 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): if "model" in response_object: model_response_object.model = response_object["model"] - model_response_object._hidden_params["original_response"] = ( - response_object # track original response, if users make a litellm.text_completion() request, we can return the original response - ) + model_response_object._hidden_params[ + "original_response" + ] = response_object # track original response, if users make a litellm.text_completion() request, we can return the original response return model_response_object except Exception as e: raise e diff --git a/litellm/llms/openai/fine_tuning/handler.py b/litellm/llms/openai/fine_tuning/handler.py index 97b237c757..2b697f85d2 100644 --- a/litellm/llms/openai/fine_tuning/handler.py +++ b/litellm/llms/openai/fine_tuning/handler.py @@ -28,14 +28,7 @@ class OpenAIFineTuningAPI: _is_async: bool = False, api_version: Optional[str] = None, litellm_params: Optional[dict] = None, - ) -> Optional[ - Union[ - OpenAI, - AsyncOpenAI, - AzureOpenAI, - AsyncAzureOpenAI, - ] - ]: + ) -> Optional[Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI,]]: received_args = locals() openai_client: Optional[ Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI] diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index deb70b481e..0545542ead 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -266,7 +266,6 @@ class OpenAIConfig(BaseConfig): api_key: Optional[str] = None, json_mode: Optional[bool] = None, ) -> ModelResponse: - logging_obj.post_call(original_response=raw_response.text) logging_obj.model_call_details["response_headers"] = raw_response.headers final_response_obj = cast( @@ -320,7 +319,6 @@ class OpenAIChatCompletionResponseIterator(BaseModelResponseIterator): class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): - def __init__(self) -> None: super().__init__() @@ -513,7 +511,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): custom_llm_provider: Optional[str] = None, drop_params: Optional[bool] = None, ): - super().completion() try: fake_stream: bool = False @@ -553,7 +550,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): for _ in range( 2 ): # if call fails due to alternating messages, retry with reformatted message - if provider_config is not None: data = provider_config.transform_request( model=model, @@ -649,13 +645,14 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): }, ) - headers, response = ( - self.make_sync_openai_chat_completion_request( - openai_client=openai_client, - data=data, - timeout=timeout, - logging_obj=logging_obj, - ) + ( + headers, + response, + ) = self.make_sync_openai_chat_completion_request( + openai_client=openai_client, + data=data, + timeout=timeout, + logging_obj=logging_obj, ) logging_obj.model_call_details["response_headers"] = headers @@ -763,7 +760,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): for _ in range( 2 ): # if call fails due to alternating messages, retry with reformatted message - try: openai_aclient: AsyncOpenAI = self._get_openai_client( # type: ignore is_async=True, @@ -973,7 +969,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): except ( Exception ) as e: # need to exception handle here. async exceptions don't get caught in sync functions. - if isinstance(e, OpenAIError): raise e @@ -1246,7 +1241,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): ): response = None try: - openai_aclient = self._get_openai_client( is_async=True, api_key=api_key, @@ -1333,7 +1327,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): ) return convert_to_model_response_object(response_object=response, model_response_object=model_response, response_type="image_generation") # type: ignore except OpenAIError as e: - ## LOGGING logging_obj.post_call( input=prompt, @@ -1372,7 +1365,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): aspeech: Optional[bool] = None, client=None, ) -> HttpxBinaryResponseContent: - if aspeech is not None and aspeech is True: return self.async_audio_speech( model=model, @@ -1419,7 +1411,6 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): timeout: Union[float, httpx.Timeout], client=None, ) -> HttpxBinaryResponseContent: - openai_client = cast( AsyncOpenAI, self._get_openai_client( diff --git a/litellm/llms/openai/transcriptions/whisper_transformation.py b/litellm/llms/openai/transcriptions/whisper_transformation.py index 5a7d6481a8..2d3d611dac 100644 --- a/litellm/llms/openai/transcriptions/whisper_transformation.py +++ b/litellm/llms/openai/transcriptions/whisper_transformation.py @@ -81,9 +81,9 @@ class OpenAIWhisperAudioTranscriptionConfig(BaseAudioTranscriptionConfig): if "response_format" not in data or ( data["response_format"] == "text" or data["response_format"] == "json" ): - data["response_format"] = ( - "verbose_json" # ensures 'duration' is received - used for cost calculation - ) + data[ + "response_format" + ] = "verbose_json" # ensures 'duration' is received - used for cost calculation return data diff --git a/litellm/llms/openrouter/chat/transformation.py b/litellm/llms/openrouter/chat/transformation.py index ab4d3c52b9..0b47167524 100644 --- a/litellm/llms/openrouter/chat/transformation.py +++ b/litellm/llms/openrouter/chat/transformation.py @@ -19,7 +19,6 @@ from ..common_utils import OpenRouterException class OpenrouterConfig(OpenAIGPTConfig): - def map_openai_params( self, non_default_params: dict, @@ -42,9 +41,9 @@ class OpenrouterConfig(OpenAIGPTConfig): extra_body["models"] = models if route is not None: extra_body["route"] = route - mapped_openai_params["extra_body"] = ( - extra_body # openai client supports `extra_body` param - ) + mapped_openai_params[ + "extra_body" + ] = extra_body # openai client supports `extra_body` param return mapped_openai_params def get_error_class( @@ -70,7 +69,6 @@ class OpenrouterConfig(OpenAIGPTConfig): class OpenRouterChatCompletionStreamingHandler(BaseModelResponseIterator): - def chunk_parser(self, chunk: dict) -> ModelResponseStream: try: new_choices = [] diff --git a/litellm/llms/petals/completion/transformation.py b/litellm/llms/petals/completion/transformation.py index 08ec15de33..a9e37d27fc 100644 --- a/litellm/llms/petals/completion/transformation.py +++ b/litellm/llms/petals/completion/transformation.py @@ -37,9 +37,9 @@ class PetalsConfig(BaseConfig): """ max_length: Optional[int] = None - max_new_tokens: Optional[int] = ( - litellm.max_tokens - ) # petals requires max tokens to be set + max_new_tokens: Optional[ + int + ] = litellm.max_tokens # petals requires max tokens to be set do_sample: Optional[bool] = None temperature: Optional[float] = None top_k: Optional[int] = None diff --git a/litellm/llms/predibase/chat/handler.py b/litellm/llms/predibase/chat/handler.py index 43f4b06745..cd80fa53e4 100644 --- a/litellm/llms/predibase/chat/handler.py +++ b/litellm/llms/predibase/chat/handler.py @@ -394,7 +394,6 @@ class PredibaseChatCompletion: logger_fn=None, headers={}, ) -> ModelResponse: - async_handler = get_async_httpx_client( llm_provider=litellm.LlmProviders.PREDIBASE, params={"timeout": timeout}, diff --git a/litellm/llms/predibase/chat/transformation.py b/litellm/llms/predibase/chat/transformation.py index f574238696..f1a2163d24 100644 --- a/litellm/llms/predibase/chat/transformation.py +++ b/litellm/llms/predibase/chat/transformation.py @@ -30,9 +30,9 @@ class PredibaseConfig(BaseConfig): 256 # openai default - requests hang if max_new_tokens not given ) repetition_penalty: Optional[float] = None - return_full_text: Optional[bool] = ( - False # by default don't return the input as part of the output - ) + return_full_text: Optional[ + bool + ] = False # by default don't return the input as part of the output seed: Optional[int] = None stop: Optional[List[str]] = None temperature: Optional[float] = None @@ -99,9 +99,9 @@ class PredibaseConfig(BaseConfig): optional_params["top_p"] = value if param == "n": optional_params["best_of"] = value - optional_params["do_sample"] = ( - True # Need to sample if you want best of for hf inference endpoints - ) + optional_params[ + "do_sample" + ] = True # Need to sample if you want best of for hf inference endpoints if param == "stream": optional_params["stream"] = value if param == "stop": diff --git a/litellm/llms/replicate/chat/handler.py b/litellm/llms/replicate/chat/handler.py index f52eb2ee05..526f376b89 100644 --- a/litellm/llms/replicate/chat/handler.py +++ b/litellm/llms/replicate/chat/handler.py @@ -244,7 +244,6 @@ async def async_completion( print_verbose, headers: dict, ) -> Union[ModelResponse, CustomStreamWrapper]: - prediction_url = replicate_config.get_complete_url( api_base=api_base, model=model, diff --git a/litellm/llms/sagemaker/chat/handler.py b/litellm/llms/sagemaker/chat/handler.py index c827a8a5f7..b86cda7aea 100644 --- a/litellm/llms/sagemaker/chat/handler.py +++ b/litellm/llms/sagemaker/chat/handler.py @@ -13,7 +13,6 @@ from .transformation import SagemakerChatConfig class SagemakerChatHandler(BaseAWSLLM): - def _load_credentials( self, optional_params: dict, @@ -128,7 +127,6 @@ class SagemakerChatHandler(BaseAWSLLM): headers: dict = {}, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, ): - # pop streaming if it's in the optional params as 'stream' raises an error with sagemaker credentials, aws_region_name = self._load_credentials(optional_params) inference_params = deepcopy(optional_params) diff --git a/litellm/llms/sagemaker/common_utils.py b/litellm/llms/sagemaker/common_utils.py index 9884f420c3..ce0c6c9506 100644 --- a/litellm/llms/sagemaker/common_utils.py +++ b/litellm/llms/sagemaker/common_utils.py @@ -34,7 +34,6 @@ class AWSEventStreamDecoder: def _chunk_parser_messages_api( self, chunk_data: dict ) -> StreamingChatCompletionChunk: - openai_chunk = StreamingChatCompletionChunk(**chunk_data) return openai_chunk @@ -192,7 +191,6 @@ class AWSEventStreamDecoder: def get_response_stream_shape(): global _response_stream_shape_cache if _response_stream_shape_cache is None: - from botocore.loaders import Loader from botocore.model import ServiceModel diff --git a/litellm/llms/sagemaker/completion/handler.py b/litellm/llms/sagemaker/completion/handler.py index 909caf73c3..296689c31c 100644 --- a/litellm/llms/sagemaker/completion/handler.py +++ b/litellm/llms/sagemaker/completion/handler.py @@ -1,6 +1,6 @@ import json from copy import deepcopy -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable, List, Optional, Union, cast import httpx @@ -35,7 +35,6 @@ os.environ['AWS_SECRET_ACCESS_KEY'] = "" # set os.environ['AWS_REGION_NAME'] = class SagemakerLLM(BaseAWSLLM): - def _load_credentials( self, optional_params: dict, @@ -154,7 +153,6 @@ class SagemakerLLM(BaseAWSLLM): acompletion: bool = False, headers: dict = {}, ): - # pop streaming if it's in the optional params as 'stream' raises an error with sagemaker credentials, aws_region_name = self._load_credentials(optional_params) inference_params = deepcopy(optional_params) @@ -437,10 +435,14 @@ class SagemakerLLM(BaseAWSLLM): prepared_request.headers.update( {"X-Amzn-SageMaker-Inference-Component": model_id} ) + + if not prepared_request.body: + raise ValueError("Prepared request body is empty") + completion_stream = await self.make_async_call( api_base=prepared_request.url, headers=prepared_request.headers, # type: ignore - data=prepared_request.body, + data=cast(str, prepared_request.body), logging_obj=logging_obj, ) streaming_response = CustomStreamWrapper( @@ -625,7 +627,7 @@ class SagemakerLLM(BaseAWSLLM): response = client.invoke_endpoint( EndpointName={model}, ContentType="application/json", - Body={data}, # type: ignore + Body=f"{data!r}", # Use !r for safe representation CustomAttributes="accept_eula=true", )""" # type: ignore logging_obj.pre_call( diff --git a/litellm/llms/sagemaker/completion/transformation.py b/litellm/llms/sagemaker/completion/transformation.py index d0ab5d0697..9923c0e45d 100644 --- a/litellm/llms/sagemaker/completion/transformation.py +++ b/litellm/llms/sagemaker/completion/transformation.py @@ -88,9 +88,9 @@ class SagemakerConfig(BaseConfig): optional_params["top_p"] = value if param == "n": optional_params["best_of"] = value - optional_params["do_sample"] = ( - True # Need to sample if you want best of for hf inference endpoints - ) + optional_params[ + "do_sample" + ] = True # Need to sample if you want best of for hf inference endpoints if param == "stream": optional_params["stream"] = value if param == "stop": diff --git a/litellm/llms/together_ai/rerank/transformation.py b/litellm/llms/together_ai/rerank/transformation.py index 4714376979..1fdb772add 100644 --- a/litellm/llms/together_ai/rerank/transformation.py +++ b/litellm/llms/together_ai/rerank/transformation.py @@ -19,7 +19,6 @@ from litellm.types.rerank import ( class TogetherAIRerankConfig: def _transform_response(self, response: dict) -> RerankResponse: - _billed_units = RerankBilledUnits(**response.get("usage", {})) _tokens = RerankTokens(**response.get("usage", {})) rerank_meta = RerankResponseMeta(billed_units=_billed_units, tokens=_tokens) diff --git a/litellm/llms/topaz/image_variations/transformation.py b/litellm/llms/topaz/image_variations/transformation.py index 8b95deed04..6188101015 100644 --- a/litellm/llms/topaz/image_variations/transformation.py +++ b/litellm/llms/topaz/image_variations/transformation.py @@ -121,7 +121,6 @@ class TopazImageVariationConfig(BaseImageVariationConfig): optional_params: dict, headers: dict, ) -> HttpHandlerRequestFields: - request_params = HttpHandlerRequestFields( files={"image": self.prepare_file_tuple(image)}, data=optional_params, @@ -134,7 +133,6 @@ class TopazImageVariationConfig(BaseImageVariationConfig): image_content: bytes, response_ms: float, ) -> ImageResponse: - # Convert to base64 base64_image = base64.b64encode(image_content).decode("utf-8") diff --git a/litellm/llms/triton/completion/transformation.py b/litellm/llms/triton/completion/transformation.py index 56151f89ef..46b607d455 100644 --- a/litellm/llms/triton/completion/transformation.py +++ b/litellm/llms/triton/completion/transformation.py @@ -244,7 +244,6 @@ class TritonInferConfig(TritonConfig): litellm_params: dict, headers: dict, ) -> dict: - text_input = messages[0].get("content", "") data_for_triton = { "inputs": [ diff --git a/litellm/llms/vertex_ai/batches/handler.py b/litellm/llms/vertex_ai/batches/handler.py index b82268bef6..dc3f93857a 100644 --- a/litellm/llms/vertex_ai/batches/handler.py +++ b/litellm/llms/vertex_ai/batches/handler.py @@ -35,7 +35,6 @@ class VertexAIBatchPrediction(VertexLLM): timeout: Union[float, httpx.Timeout], max_retries: Optional[int], ) -> Union[LiteLLMBatch, Coroutine[Any, Any, LiteLLMBatch]]: - sync_handler = _get_httpx_client() access_token, project_id = self._ensure_access_token( @@ -69,10 +68,8 @@ class VertexAIBatchPrediction(VertexLLM): "Authorization": f"Bearer {access_token}", } - vertex_batch_request: VertexAIBatchPredictionJob = ( - VertexAIBatchTransformation.transform_openai_batch_request_to_vertex_ai_batch_request( - request=create_batch_data - ) + vertex_batch_request: VertexAIBatchPredictionJob = VertexAIBatchTransformation.transform_openai_batch_request_to_vertex_ai_batch_request( + request=create_batch_data ) if _is_async is True: diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 337445777a..f2cd1ef557 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -243,7 +243,7 @@ def convert_anyof_null_to_nullable(schema, depth=0): # remove null type anyof.remove(atype) contains_null = True - + if len(anyof) == 0: # Edge case: response schema with only null type present is invalid in Vertex AI raise ValueError( @@ -251,12 +251,10 @@ def convert_anyof_null_to_nullable(schema, depth=0): "Please provide a non-null type." ) - if contains_null: # set all types to nullable following guidance found here: https://cloud.google.com/vertex-ai/generative-ai/docs/samples/generativeaionvertexai-gemini-controlled-generation-response-schema-3#generativeaionvertexai_gemini_controlled_generation_response_schema_3-python for atype in anyof: atype["nullable"] = True - properties = schema.get("properties", None) if properties is not None: diff --git a/litellm/llms/vertex_ai/files/handler.py b/litellm/llms/vertex_ai/files/handler.py index 266169cdfb..7000cf151d 100644 --- a/litellm/llms/vertex_ai/files/handler.py +++ b/litellm/llms/vertex_ai/files/handler.py @@ -49,10 +49,11 @@ class VertexAIFilesHandler(GCSBucketBase): service_account_json=gcs_logging_config["path_service_account"], ) bucket_name = gcs_logging_config["bucket_name"] - logging_payload, object_name = ( - vertex_ai_files_transformation.transform_openai_file_content_to_vertex_ai_file_content( - openai_file_content=create_file_data.get("file") - ) + ( + logging_payload, + object_name, + ) = vertex_ai_files_transformation.transform_openai_file_content_to_vertex_ai_file_content( + openai_file_content=create_file_data.get("file") ) gcs_upload_response = await self._log_json_data_on_gcs( headers=headers, diff --git a/litellm/llms/vertex_ai/fine_tuning/handler.py b/litellm/llms/vertex_ai/fine_tuning/handler.py index 3cf409c78e..7ea8527fd4 100644 --- a/litellm/llms/vertex_ai/fine_tuning/handler.py +++ b/litellm/llms/vertex_ai/fine_tuning/handler.py @@ -36,7 +36,6 @@ class VertexFineTuningAPI(VertexLLM): def convert_response_created_at(self, response: ResponseTuningJob): try: - create_time_str = response.get("createTime", "") or "" create_time_datetime = datetime.fromisoformat( create_time_str.replace("Z", "+00:00") @@ -65,9 +64,9 @@ class VertexFineTuningAPI(VertexLLM): ) if create_fine_tuning_job_data.validation_file: - supervised_tuning_spec["validation_dataset"] = ( - create_fine_tuning_job_data.validation_file - ) + supervised_tuning_spec[ + "validation_dataset" + ] = create_fine_tuning_job_data.validation_file _vertex_hyperparameters = ( self._transform_openai_hyperparameters_to_vertex_hyperparameters( @@ -175,7 +174,6 @@ class VertexFineTuningAPI(VertexLLM): headers: dict, request_data: FineTuneJobCreate, ): - try: verbose_logger.debug( "about to create fine tuning job: %s, request_data: %s", @@ -229,7 +227,6 @@ class VertexFineTuningAPI(VertexLLM): kwargs: Optional[dict] = None, original_hyperparameters: Optional[dict] = {}, ): - verbose_logger.debug( "creating fine tuning job, args= %s", create_fine_tuning_job_data ) @@ -346,9 +343,9 @@ class VertexFineTuningAPI(VertexLLM): elif "cachedContents" in request_route: _model = request_data.get("model") if _model is not None and "/publishers/google/models/" not in _model: - request_data["model"] = ( - f"projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{_model}" - ) + request_data[ + "model" + ] = f"projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{_model}" url = f"https://{vertex_location}-aiplatform.googleapis.com/v1beta1/projects/{vertex_project}/locations/{vertex_location}{request_route}" else: diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index d6bafc7c60..96b33ee187 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -85,7 +85,6 @@ def _process_gemini_image(image_url: str, format: Optional[str] = None) -> PartT and (image_type := format or _get_image_mime_type_from_url(image_url)) is not None ): - file_data = FileDataType(file_uri=image_url, mime_type=image_type) return PartType(file_data=file_data) elif "http://" in image_url or "https://" in image_url or "base64" in image_url: @@ -414,18 +413,19 @@ async def async_transform_request_body( context_caching_endpoints = ContextCachingEndpoints() if gemini_api_key is not None: - messages, cached_content = ( - await context_caching_endpoints.async_check_and_create_cache( - messages=messages, - api_key=gemini_api_key, - api_base=api_base, - model=model, - client=client, - timeout=timeout, - extra_headers=extra_headers, - cached_content=optional_params.pop("cached_content", None), - logging_obj=logging_obj, - ) + ( + messages, + cached_content, + ) = await context_caching_endpoints.async_check_and_create_cache( + messages=messages, + api_key=gemini_api_key, + api_base=api_base, + model=model, + client=client, + timeout=timeout, + extra_headers=extra_headers, + cached_content=optional_params.pop("cached_content", None), + logging_obj=logging_obj, ) else: # [TODO] implement context caching for gemini as well cached_content = optional_params.pop("cached_content", None) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 90c66f69a3..860dec9eb2 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -246,9 +246,9 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): value = _remove_strict_from_schema(value) for tool in value: - openai_function_object: Optional[ChatCompletionToolParamFunctionChunk] = ( - None - ) + openai_function_object: Optional[ + ChatCompletionToolParamFunctionChunk + ] = None if "function" in tool: # tools list _openai_function_object = ChatCompletionToolParamFunctionChunk( # type: ignore **tool["function"] @@ -813,15 +813,15 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ## ADD SAFETY RATINGS ## setattr(model_response, "vertex_ai_safety_results", safety_ratings) - model_response._hidden_params["vertex_ai_safety_results"] = ( - safety_ratings # older approach - maintaining to prevent regressions - ) + model_response._hidden_params[ + "vertex_ai_safety_results" + ] = safety_ratings # older approach - maintaining to prevent regressions ## ADD CITATION METADATA ## setattr(model_response, "vertex_ai_citation_metadata", citation_metadata) - model_response._hidden_params["vertex_ai_citation_metadata"] = ( - citation_metadata # older approach - maintaining to prevent regressions - ) + model_response._hidden_params[ + "vertex_ai_citation_metadata" + ] = citation_metadata # older approach - maintaining to prevent regressions except Exception as e: raise VertexAIError( diff --git a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py index 0fe5145a14..ecfe2ee8b4 100644 --- a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py +++ b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_handler.py @@ -47,7 +47,6 @@ class GoogleBatchEmbeddings(VertexLLM): timeout=300, client=None, ) -> EmbeddingResponse: - _auth_header, vertex_project = self._ensure_access_token( credentials=vertex_credentials, project_id=vertex_project, diff --git a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py index 592dac5846..2c0f5dad22 100644 --- a/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py +++ b/litellm/llms/vertex_ai/gemini_embeddings/batch_embed_content_transformation.py @@ -52,7 +52,6 @@ def process_response( model: str, _predictions: VertexAIBatchEmbeddingsResponseObject, ) -> EmbeddingResponse: - openai_embeddings: List[Embedding] = [] for embedding in _predictions["embeddings"]: openai_embedding = Embedding( diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py index 34879ae9ac..88d7339449 100644 --- a/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/multimodal_embeddings/embedding_handler.py @@ -50,7 +50,6 @@ class VertexMultimodalEmbedding(VertexLLM): timeout=300, client=None, ) -> EmbeddingResponse: - _auth_header, vertex_project = self._ensure_access_token( credentials=vertex_credentials, project_id=vertex_project, diff --git a/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py b/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py index 8f96ca2bb0..afa58c7e5c 100644 --- a/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py +++ b/litellm/llms/vertex_ai/multimodal_embeddings/transformation.py @@ -260,7 +260,6 @@ class VertexAIMultimodalEmbeddingConfig(BaseEmbeddingConfig): def transform_embedding_response_to_openai( self, predictions: MultimodalPredictions ) -> List[Embedding]: - openai_embeddings: List[Embedding] = [] if "predictions" in predictions: for idx, _prediction in enumerate(predictions["predictions"]): diff --git a/litellm/llms/vertex_ai/vertex_ai_non_gemini.py b/litellm/llms/vertex_ai/vertex_ai_non_gemini.py index 744e1eb317..df267d9623 100644 --- a/litellm/llms/vertex_ai/vertex_ai_non_gemini.py +++ b/litellm/llms/vertex_ai/vertex_ai_non_gemini.py @@ -323,7 +323,6 @@ def completion( # noqa: PLR0915 ) completion_response = chat.send_message(prompt, **optional_params).text elif mode == "text": - if fake_stream is not True and stream is True: request_str += ( f"llm_model.predict_streaming({prompt}, **{optional_params})\n" @@ -506,7 +505,6 @@ async def async_completion( # noqa: PLR0915 Add support for acompletion calls for gemini-pro """ try: - response_obj = None completion_response = None if mode == "chat": diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py index fb2393631b..b8d2658f80 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py @@ -110,7 +110,6 @@ class VertexAIPartnerModels(VertexBase): message="""Upgrade vertex ai. Run `pip install "google-cloud-aiplatform>=1.38"`""", ) try: - vertex_httpx_logic = VertexLLM() access_token, project_id = vertex_httpx_logic._ensure_access_token( diff --git a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py index 2e8051d4d2..1167ca285f 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/embedding_handler.py @@ -86,10 +86,8 @@ class VertexEmbedding(VertexBase): mode="embedding", ) headers = self.set_headers(auth_header=auth_header, extra_headers=extra_headers) - vertex_request: VertexEmbeddingRequest = ( - litellm.vertexAITextEmbeddingConfig.transform_openai_request_to_vertex_embedding_request( - input=input, optional_params=optional_params, model=model - ) + vertex_request: VertexEmbeddingRequest = litellm.vertexAITextEmbeddingConfig.transform_openai_request_to_vertex_embedding_request( + input=input, optional_params=optional_params, model=model ) _client_params = {} @@ -178,10 +176,8 @@ class VertexEmbedding(VertexBase): mode="embedding", ) headers = self.set_headers(auth_header=auth_header, extra_headers=extra_headers) - vertex_request: VertexEmbeddingRequest = ( - litellm.vertexAITextEmbeddingConfig.transform_openai_request_to_vertex_embedding_request( - input=input, optional_params=optional_params, model=model - ) + vertex_request: VertexEmbeddingRequest = litellm.vertexAITextEmbeddingConfig.transform_openai_request_to_vertex_embedding_request( + input=input, optional_params=optional_params, model=model ) _async_client_params = {} diff --git a/litellm/llms/vertex_ai/vertex_embeddings/transformation.py b/litellm/llms/vertex_ai/vertex_embeddings/transformation.py index d9e84fca03..97af558041 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/transformation.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/transformation.py @@ -212,7 +212,6 @@ class VertexAITextEmbeddingConfig(BaseModel): embedding_response = [] input_tokens: int = 0 for idx, element in enumerate(_predictions): - embedding = element["embeddings"] embedding_response.append( { diff --git a/litellm/llms/vertex_ai/vertex_model_garden/main.py b/litellm/llms/vertex_ai/vertex_model_garden/main.py index 7b54d4e34b..1c57096734 100644 --- a/litellm/llms/vertex_ai/vertex_model_garden/main.py +++ b/litellm/llms/vertex_ai/vertex_model_garden/main.py @@ -76,7 +76,6 @@ class VertexAIModelGardenModels(VertexBase): VertexLLM, ) except Exception as e: - raise VertexAIError( status_code=400, message=f"""vertexai import failed please run `pip install -U "google-cloud-aiplatform>=1.38"`. Got error: {e}""", diff --git a/litellm/llms/watsonx/chat/transformation.py b/litellm/llms/watsonx/chat/transformation.py index f253da6f5b..2ff1dd6a68 100644 --- a/litellm/llms/watsonx/chat/transformation.py +++ b/litellm/llms/watsonx/chat/transformation.py @@ -15,7 +15,6 @@ from ..common_utils import IBMWatsonXMixin class IBMWatsonXChatConfig(IBMWatsonXMixin, OpenAIGPTConfig): - def get_supported_openai_params(self, model: str) -> List: return [ "temperature", # equivalent to temperature diff --git a/litellm/main.py b/litellm/main.py index 1e6c36aa6c..f69454aaad 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -946,14 +946,16 @@ def completion( # type: ignore # noqa: PLR0915 ## PROMPT MANAGEMENT HOOKS ## if isinstance(litellm_logging_obj, LiteLLMLoggingObj) and prompt_id is not None: - model, messages, optional_params = ( - litellm_logging_obj.get_chat_completion_prompt( - model=model, - messages=messages, - non_default_params=non_default_params, - prompt_id=prompt_id, - prompt_variables=prompt_variables, - ) + ( + model, + messages, + optional_params, + ) = litellm_logging_obj.get_chat_completion_prompt( + model=model, + messages=messages, + non_default_params=non_default_params, + prompt_id=prompt_id, + prompt_variables=prompt_variables, ) try: @@ -1246,7 +1248,6 @@ def completion( # type: ignore # noqa: PLR0915 optional_params["max_retries"] = max_retries if litellm.AzureOpenAIO1Config().is_o_series_model(model=model): - ## LOAD CONFIG - if set config = litellm.AzureOpenAIO1Config.get_config() for k, v in config.items(): @@ -2654,9 +2655,9 @@ def completion( # type: ignore # noqa: PLR0915 "aws_region_name" not in optional_params or optional_params["aws_region_name"] is None ): - optional_params["aws_region_name"] = ( - aws_bedrock_client.meta.region_name - ) + optional_params[ + "aws_region_name" + ] = aws_bedrock_client.meta.region_name bedrock_route = BedrockModelInfo.get_bedrock_route(model) if bedrock_route == "converse": @@ -4362,9 +4363,9 @@ def adapter_completion( new_kwargs = translation_obj.translate_completion_input_params(kwargs=kwargs) response: Union[ModelResponse, CustomStreamWrapper] = completion(**new_kwargs) # type: ignore - translated_response: Optional[Union[BaseModel, AdapterCompletionStreamWrapper]] = ( - None - ) + translated_response: Optional[ + Union[BaseModel, AdapterCompletionStreamWrapper] + ] = None if isinstance(response, ModelResponse): translated_response = translation_obj.translate_completion_output_params( response=response @@ -4436,13 +4437,16 @@ async def amoderation( optional_params = GenericLiteLLMParams(**kwargs) try: - model, _custom_llm_provider, _dynamic_api_key, _dynamic_api_base = ( - litellm.get_llm_provider( - model=model or "", - custom_llm_provider=custom_llm_provider, - api_base=optional_params.api_base, - api_key=optional_params.api_key, - ) + ( + model, + _custom_llm_provider, + _dynamic_api_key, + _dynamic_api_base, + ) = litellm.get_llm_provider( + model=model or "", + custom_llm_provider=custom_llm_provider, + api_base=optional_params.api_base, + api_key=optional_params.api_key, ) except litellm.BadRequestError: # `model` is optional field for moderation - get_llm_provider will throw BadRequestError if model is not set / not recognized @@ -5405,7 +5409,6 @@ def speech( # noqa: PLR0915 litellm_params=litellm_params_dict, ) elif custom_llm_provider == "vertex_ai" or custom_llm_provider == "vertex_ai_beta": - generic_optional_params = GenericLiteLLMParams(**kwargs) api_base = generic_optional_params.api_base or "" @@ -5460,7 +5463,6 @@ def speech( # noqa: PLR0915 async def ahealth_check_wildcard_models( model: str, custom_llm_provider: str, model_params: dict ) -> dict: - # this is a wildcard model, we need to pick a random model from the provider cheapest_models = pick_cheapest_chat_models_from_llm_provider( custom_llm_provider=custom_llm_provider, n=3 @@ -5783,9 +5785,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(content_chunks) > 0: - response["choices"][0]["message"]["content"] = ( - processor.get_combined_content(content_chunks) - ) + response["choices"][0]["message"][ + "content" + ] = processor.get_combined_content(content_chunks) audio_chunks = [ chunk diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e7d6bec004..16b45f3837 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -38,7 +38,7 @@ from .types_utils.utils import get_instance_fn, validate_custom_validate_return_ if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any @@ -615,9 +615,9 @@ class GenerateRequestBase(LiteLLMPydanticObjectBase): allowed_cache_controls: Optional[list] = [] config: Optional[dict] = {} permissions: Optional[dict] = {} - model_max_budget: Optional[dict] = ( - {} - ) # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {} + model_max_budget: Optional[ + dict + ] = {} # {"gpt-4": 5.0, "gpt-3.5-turbo": 5.0}, defaults to {} model_config = ConfigDict(protected_namespaces=()) model_rpm_limit: Optional[dict] = None @@ -873,12 +873,12 @@ class NewCustomerRequest(BudgetNewRequest): alias: Optional[str] = None # human-friendly alias blocked: bool = False # allow/disallow requests for this end-user budget_id: Optional[str] = None # give either a budget_id or max_budget - allowed_model_region: Optional[AllowedModelRegion] = ( - None # require all user requests to use models in this specific region - ) - default_model: Optional[str] = ( - None # if no equivalent model in allowed region - default all requests to this model - ) + allowed_model_region: Optional[ + AllowedModelRegion + ] = None # require all user requests to use models in this specific region + default_model: Optional[ + str + ] = None # if no equivalent model in allowed region - default all requests to this model @model_validator(mode="before") @classmethod @@ -900,12 +900,12 @@ class UpdateCustomerRequest(LiteLLMPydanticObjectBase): blocked: bool = False # allow/disallow requests for this end-user max_budget: Optional[float] = None budget_id: Optional[str] = None # give either a budget_id or max_budget - allowed_model_region: Optional[AllowedModelRegion] = ( - None # require all user requests to use models in this specific region - ) - default_model: Optional[str] = ( - None # if no equivalent model in allowed region - default all requests to this model - ) + allowed_model_region: Optional[ + AllowedModelRegion + ] = None # require all user requests to use models in this specific region + default_model: Optional[ + str + ] = None # if no equivalent model in allowed region - default all requests to this model class DeleteCustomerRequest(LiteLLMPydanticObjectBase): @@ -1040,9 +1040,9 @@ class BlockKeyRequest(LiteLLMPydanticObjectBase): class AddTeamCallback(LiteLLMPydanticObjectBase): callback_name: str - callback_type: Optional[Literal["success", "failure", "success_and_failure"]] = ( - "success_and_failure" - ) + callback_type: Optional[ + Literal["success", "failure", "success_and_failure"] + ] = "success_and_failure" callback_vars: Dict[str, str] @model_validator(mode="before") @@ -1299,9 +1299,9 @@ class ConfigList(LiteLLMPydanticObjectBase): stored_in_db: Optional[bool] field_default_value: Any premium_field: bool = False - nested_fields: Optional[List[FieldDetail]] = ( - None # For nested dictionary or Pydantic fields - ) + nested_fields: Optional[ + List[FieldDetail] + ] = None # For nested dictionary or Pydantic fields class ConfigGeneralSettings(LiteLLMPydanticObjectBase): @@ -1567,9 +1567,9 @@ class LiteLLM_OrganizationMembershipTable(LiteLLMPydanticObjectBase): budget_id: Optional[str] = None created_at: datetime updated_at: datetime - user: Optional[Any] = ( - None # You might want to replace 'Any' with a more specific type if available - ) + user: Optional[ + Any + ] = None # You might want to replace 'Any' with a more specific type if available litellm_budget_table: Optional[LiteLLM_BudgetTable] = None model_config = ConfigDict(protected_namespaces=()) @@ -2306,9 +2306,9 @@ class TeamModelDeleteRequest(BaseModel): # Organization Member Requests class OrganizationMemberAddRequest(OrgMemberAddRequest): organization_id: str - max_budget_in_organization: Optional[float] = ( - None # Users max budget within the organization - ) + max_budget_in_organization: Optional[ + float + ] = None # Users max budget within the organization class OrganizationMemberDeleteRequest(MemberDeleteRequest): @@ -2497,9 +2497,9 @@ class ProviderBudgetResponse(LiteLLMPydanticObjectBase): Maps provider names to their budget configs. """ - providers: Dict[str, ProviderBudgetResponseObject] = ( - {} - ) # Dictionary mapping provider names to their budget configurations + providers: Dict[ + str, ProviderBudgetResponseObject + ] = {} # Dictionary mapping provider names to their budget configurations class ProxyStateVariables(TypedDict): @@ -2627,9 +2627,9 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): enforce_rbac: bool = False roles_jwt_field: Optional[str] = None # v2 on role mappings role_mappings: Optional[List[RoleMapping]] = None - object_id_jwt_field: Optional[str] = ( - None # can be either user / team, inferred from the role mapping - ) + object_id_jwt_field: Optional[ + str + ] = None # can be either user / team, inferred from the role mapping scope_mappings: Optional[List[ScopeMapping]] = None enforce_scope_based_access: bool = False enforce_team_based_model_access: bool = False diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index efbfe8d90c..ddd1008bd0 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -11,7 +11,7 @@ Run checks for: import asyncio import re import time -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast from fastapi import Request, status from pydantic import BaseModel @@ -49,7 +49,7 @@ from .auth_checks_organization import organization_role_based_access_check if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any @@ -551,7 +551,6 @@ def _get_role_based_permissions( return None for role_based_permission in role_based_permissions: - if role_based_permission.role == rbac_role: return getattr(role_based_permission, key) @@ -867,7 +866,6 @@ async def _get_team_object_from_cache( proxy_logging_obj is not None and proxy_logging_obj.internal_usage_cache.dual_cache ): - cached_team_obj = ( await proxy_logging_obj.internal_usage_cache.dual_cache.async_get_cache( key=key, parent_otel_span=parent_otel_span @@ -1202,7 +1200,6 @@ async def can_user_call_model( llm_router: Optional[Router], user_object: Optional[LiteLLM_UserTable], ) -> Literal[True]: - if user_object is None: return True diff --git a/litellm/proxy/auth/auth_checks_organization.py b/litellm/proxy/auth/auth_checks_organization.py index 3da3d8ddd1..e96a5c61fc 100644 --- a/litellm/proxy/auth/auth_checks_organization.py +++ b/litellm/proxy/auth/auth_checks_organization.py @@ -44,9 +44,10 @@ def organization_role_based_access_check( # Checks if route is an Org Admin Only Route if route in LiteLLMRoutes.org_admin_only_routes.value: - _user_organizations, _user_organization_role_mapping = ( - get_user_organization_info(user_object) - ) + ( + _user_organizations, + _user_organization_role_mapping, + ) = get_user_organization_info(user_object) if user_object.organization_memberships is None: raise ProxyException( @@ -84,9 +85,10 @@ def organization_role_based_access_check( ) elif route == "/team/new": # if user is part of multiple teams, then they need to specify the organization_id - _user_organizations, _user_organization_role_mapping = ( - get_user_organization_info(user_object) - ) + ( + _user_organizations, + _user_organization_role_mapping, + ) = get_user_organization_info(user_object) if ( user_object.organization_memberships is not None and len(user_object.organization_memberships) > 0 diff --git a/litellm/proxy/auth/auth_exception_handler.py b/litellm/proxy/auth/auth_exception_handler.py index 05797381c6..7c97655141 100644 --- a/litellm/proxy/auth/auth_exception_handler.py +++ b/litellm/proxy/auth/auth_exception_handler.py @@ -3,7 +3,7 @@ Handles Authentication Errors """ import asyncio -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union from fastapi import HTTPException, Request, status @@ -17,13 +17,12 @@ from litellm.types.services import ServiceTypes if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any class UserAPIKeyAuthExceptionHandler: - @staticmethod async def _handle_authentication_error( e: Exception, diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index 2c4b122d3a..0200457ef9 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -14,7 +14,6 @@ from litellm.types.router import CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS def _get_request_ip_address( request: Request, use_x_forwarded_for: Optional[bool] = False ) -> Optional[str]: - client_ip = None if use_x_forwarded_for is True and "x-forwarded-for" in request.headers: client_ip = request.headers["x-forwarded-for"] @@ -469,7 +468,6 @@ def should_run_auth_on_pass_through_provider_route(route: str) -> bool: from litellm.proxy.proxy_server import general_settings, premium_user if premium_user is not True: - return False # premium use has opted into using client credentials diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index cc41050198..783c2f1553 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -166,7 +166,6 @@ class JWTHandler: self, token: dict, default_value: Optional[str] ) -> Optional[str]: try: - if self.litellm_jwtauth.end_user_id_jwt_field is not None: user_id = token[self.litellm_jwtauth.end_user_id_jwt_field] else: @@ -339,7 +338,6 @@ class JWTHandler: return scopes async def get_public_key(self, kid: Optional[str]) -> dict: - keys_url = os.getenv("JWT_PUBLIC_KEY_URL") if keys_url is None: @@ -348,7 +346,6 @@ class JWTHandler: keys_url_list = [url.strip() for url in keys_url.split(",")] for key_url in keys_url_list: - cache_key = f"litellm_jwt_auth_keys_{key_url}" cached_keys = await self.user_api_key_cache.async_get_cache(cache_key) @@ -923,7 +920,6 @@ class JWTAuthManager: object_id = jwt_handler.get_object_id(token=jwt_valid_token, default_value=None) if rbac_role and object_id: - if rbac_role == LitellmUserRoles.TEAM: team_id = object_id elif rbac_role == LitellmUserRoles.INTERNAL_USER: @@ -940,15 +936,16 @@ class JWTAuthManager: ## SPECIFIC TEAM ID if not team_id: - team_id, team_object = ( - await JWTAuthManager.find_and_validate_specific_team_id( - jwt_handler, - jwt_valid_token, - prisma_client, - user_api_key_cache, - parent_otel_span, - proxy_logging_obj, - ) + ( + team_id, + team_object, + ) = await JWTAuthManager.find_and_validate_specific_team_id( + jwt_handler, + jwt_valid_token, + prisma_client, + user_api_key_cache, + parent_otel_span, + proxy_logging_obj, ) if not team_object and not team_id: diff --git a/litellm/proxy/auth/litellm_license.py b/litellm/proxy/auth/litellm_license.py index 67ec91f51a..d962aad2c0 100644 --- a/litellm/proxy/auth/litellm_license.py +++ b/litellm/proxy/auth/litellm_license.py @@ -45,7 +45,6 @@ class LicenseCheck: verbose_proxy_logger.error(f"Error reading public key: {str(e)}") def _verify(self, license_str: str) -> bool: - verbose_proxy_logger.debug( "litellm.proxy.auth.litellm_license.py::_verify - Checking license against {}/verify_license - {}".format( self.base_url, license_str diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index a48ef6ae87..f0f730138f 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -178,7 +178,6 @@ def _get_wildcard_models( all_wildcard_models = [] for model in unique_models: if _check_wildcard_routing(model=model): - if ( return_wildcard_routes ): # will add the wildcard route to the list eg: anthropic/*. diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 8f956abb72..41529512b6 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -16,7 +16,6 @@ from .auth_checks_organization import _user_is_org_admin class RouteChecks: - @staticmethod def non_proxy_admin_allowed_routes_check( user_obj: Optional[LiteLLM_UserTable], @@ -67,7 +66,6 @@ class RouteChecks: and getattr(valid_token, "permissions", None) is not None and "get_spend_routes" in getattr(valid_token, "permissions", []) ): - pass elif _user_role == LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value: if RouteChecks.is_llm_api_route(route=route): @@ -80,7 +78,6 @@ class RouteChecks: ): # the Admin Viewer is only allowed to call /user/update for their own user_id and can only update if route == "/user/update": - # Check the Request params are valid for PROXY_ADMIN_VIEW_ONLY if request_data is not None and isinstance(request_data, dict): _params_updated = request_data.keys() diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index b58353bf05..eddbf4e0d9 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -206,7 +206,6 @@ def get_rbac_role(jwt_handler: JWTHandler, scopes: List[str]) -> str: def get_model_from_request(request_data: dict, route: str) -> Optional[str]: - # First try to get model from request_data model = request_data.get("model") @@ -229,7 +228,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 azure_apim_header: Optional[str], request_data: dict, ) -> UserAPIKeyAuth: - from litellm.proxy.proxy_server import ( general_settings, jwt_handler, @@ -251,7 +249,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 valid_token: Optional[UserAPIKeyAuth] = None try: - # get the request body await pre_db_read_auth_checks( @@ -514,23 +511,23 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 proxy_logging_obj=proxy_logging_obj, ) if _end_user_object is not None: - end_user_params["allowed_model_region"] = ( - _end_user_object.allowed_model_region - ) + end_user_params[ + "allowed_model_region" + ] = _end_user_object.allowed_model_region if _end_user_object.litellm_budget_table is not None: budget_info = _end_user_object.litellm_budget_table if budget_info.tpm_limit is not None: - end_user_params["end_user_tpm_limit"] = ( - budget_info.tpm_limit - ) + end_user_params[ + "end_user_tpm_limit" + ] = budget_info.tpm_limit if budget_info.rpm_limit is not None: - end_user_params["end_user_rpm_limit"] = ( - budget_info.rpm_limit - ) + end_user_params[ + "end_user_rpm_limit" + ] = budget_info.rpm_limit if budget_info.max_budget is not None: - end_user_params["end_user_max_budget"] = ( - budget_info.max_budget - ) + end_user_params[ + "end_user_max_budget" + ] = budget_info.max_budget except Exception as e: if isinstance(e, litellm.BudgetExceededError): raise e @@ -801,7 +798,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 # Check 3. Check if user is in their team budget if valid_token.team_member_spend is not None: if prisma_client is not None: - _cache_key = f"{valid_token.team_id}_{valid_token.user_id}" team_member_info = await user_api_key_cache.async_get_cache( diff --git a/litellm/proxy/common_utils/encrypt_decrypt_utils.py b/litellm/proxy/common_utils/encrypt_decrypt_utils.py index ec9279a089..3452734867 100644 --- a/litellm/proxy/common_utils/encrypt_decrypt_utils.py +++ b/litellm/proxy/common_utils/encrypt_decrypt_utils.py @@ -21,7 +21,6 @@ def _get_salt_key(): def encrypt_value_helper(value: str, new_encryption_key: Optional[str] = None): - signing_key = new_encryption_key or _get_salt_key() try: @@ -41,7 +40,6 @@ def encrypt_value_helper(value: str, new_encryption_key: Optional[str] = None): def decrypt_value_helper(value: str): - signing_key = _get_salt_key() try: diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 5736ee2152..7220ccaa65 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -142,10 +142,10 @@ def check_file_size_under_limit( if llm_router is not None and request_data["model"] in router_model_names: try: - deployment: Optional[Deployment] = ( - llm_router.get_deployment_by_model_group_name( - model_group_name=request_data["model"] - ) + deployment: Optional[ + Deployment + ] = llm_router.get_deployment_by_model_group_name( + model_group_name=request_data["model"] ) if ( deployment diff --git a/litellm/proxy/db/log_db_metrics.py b/litellm/proxy/db/log_db_metrics.py index 9bd3350793..5c79515532 100644 --- a/litellm/proxy/db/log_db_metrics.py +++ b/litellm/proxy/db/log_db_metrics.py @@ -36,7 +36,6 @@ def log_db_metrics(func): @wraps(func) async def wrapper(*args, **kwargs): - start_time: datetime = datetime.now() try: diff --git a/litellm/proxy/db/redis_update_buffer.py b/litellm/proxy/db/redis_update_buffer.py index f77c839aaf..f98fc9300f 100644 --- a/litellm/proxy/db/redis_update_buffer.py +++ b/litellm/proxy/db/redis_update_buffer.py @@ -43,9 +43,9 @@ class RedisUpdateBuffer: """ from litellm.proxy.proxy_server import general_settings - _use_redis_transaction_buffer: Optional[Union[bool, str]] = ( - general_settings.get("use_redis_transaction_buffer", False) - ) + _use_redis_transaction_buffer: Optional[ + Union[bool, str] + ] = general_settings.get("use_redis_transaction_buffer", False) if isinstance(_use_redis_transaction_buffer, str): _use_redis_transaction_buffer = str_to_bool(_use_redis_transaction_buffer) if _use_redis_transaction_buffer is None: @@ -78,15 +78,13 @@ class RedisUpdateBuffer: "redis_cache is None, skipping store_in_memory_spend_updates_in_redis" ) return - db_spend_update_transactions: DBSpendUpdateTransactions = ( - DBSpendUpdateTransactions( - user_list_transactions=prisma_client.user_list_transactions, - end_user_list_transactions=prisma_client.end_user_list_transactions, - key_list_transactions=prisma_client.key_list_transactions, - team_list_transactions=prisma_client.team_list_transactions, - team_member_list_transactions=prisma_client.team_member_list_transactions, - org_list_transactions=prisma_client.org_list_transactions, - ) + db_spend_update_transactions: DBSpendUpdateTransactions = DBSpendUpdateTransactions( + user_list_transactions=prisma_client.user_list_transactions, + end_user_list_transactions=prisma_client.end_user_list_transactions, + key_list_transactions=prisma_client.key_list_transactions, + team_list_transactions=prisma_client.team_list_transactions, + team_member_list_transactions=prisma_client.team_member_list_transactions, + org_list_transactions=prisma_client.org_list_transactions, ) # only store in redis if there are any updates to commit diff --git a/litellm/proxy/guardrails/guardrail_helpers.py b/litellm/proxy/guardrails/guardrail_helpers.py index c351f9f762..e970311460 100644 --- a/litellm/proxy/guardrails/guardrail_helpers.py +++ b/litellm/proxy/guardrails/guardrail_helpers.py @@ -45,7 +45,6 @@ async def should_proceed_based_on_metadata(data: dict, guardrail_name: str) -> b # v1 implementation of this if isinstance(request_guardrails, dict): - # get guardrail configs from `init_guardrails.py` # for all requested guardrails -> get their associated callbacks for _guardrail_name, should_run in request_guardrails.items(): diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index 7686fba7cf..5c6b53be25 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -192,7 +192,6 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): async def make_bedrock_api_request( self, kwargs: dict, response: Optional[Union[Any, litellm.ModelResponse]] = None ): - credentials, aws_region_name = self._load_credentials() bedrock_request_data: dict = dict( self.convert_to_bedrock_format( diff --git a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai.py b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai.py index 5d3b8be334..2dd8a3154a 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/lakera_ai.py +++ b/litellm/proxy/guardrails/guardrail_hooks/lakera_ai.py @@ -148,9 +148,9 @@ class lakeraAI_Moderation(CustomGuardrail): text = "" _json_data: str = "" if "messages" in data and isinstance(data["messages"], list): - prompt_injection_obj: Optional[GuardrailItem] = ( - litellm.guardrail_name_config_map.get("prompt_injection") - ) + prompt_injection_obj: Optional[ + GuardrailItem + ] = litellm.guardrail_name_config_map.get("prompt_injection") if prompt_injection_obj is not None: enabled_roles = prompt_injection_obj.enabled_roles else: diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index 86d2c8b25a..0c7d2a1fe6 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -95,9 +95,11 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): presidio_analyzer_api_base: Optional[str] = None, presidio_anonymizer_api_base: Optional[str] = None, ): - self.presidio_analyzer_api_base: Optional[str] = ( - presidio_analyzer_api_base or get_secret("PRESIDIO_ANALYZER_API_BASE", None) # type: ignore - ) + self.presidio_analyzer_api_base: Optional[ + str + ] = presidio_analyzer_api_base or get_secret( + "PRESIDIO_ANALYZER_API_BASE", None + ) # type: ignore self.presidio_anonymizer_api_base: Optional[ str ] = presidio_anonymizer_api_base or litellm.get_secret( @@ -168,7 +170,6 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): async with session.post( analyze_url, json=analyze_payload ) as response: - analyze_results = await response.json() # Make the second request to /anonymize @@ -228,7 +229,6 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): """ try: - content_safety = data.get("content_safety", None) verbose_proxy_logger.debug("content_safety: %s", content_safety) presidio_config = self.get_presidio_settings_from_request_data(data) diff --git a/litellm/proxy/hooks/dynamic_rate_limiter.py b/litellm/proxy/hooks/dynamic_rate_limiter.py index 15a9bc1ba8..e06366d02b 100644 --- a/litellm/proxy/hooks/dynamic_rate_limiter.py +++ b/litellm/proxy/hooks/dynamic_rate_limiter.py @@ -71,7 +71,6 @@ class DynamicRateLimiterCache: class _PROXY_DynamicRateLimitHandler(CustomLogger): - # Class variables or attributes def __init__(self, internal_usage_cache: DualCache): self.internal_usage_cache = DynamicRateLimiterCache(cache=internal_usage_cache) @@ -121,12 +120,13 @@ class _PROXY_DynamicRateLimitHandler(CustomLogger): active_projects = await self.internal_usage_cache.async_get_cache( model=model ) - current_model_tpm, current_model_rpm = ( - await self.llm_router.get_model_group_usage(model_group=model) - ) - model_group_info: Optional[ModelGroupInfo] = ( - self.llm_router.get_model_group_info(model_group=model) - ) + ( + current_model_tpm, + current_model_rpm, + ) = await self.llm_router.get_model_group_usage(model_group=model) + model_group_info: Optional[ + ModelGroupInfo + ] = self.llm_router.get_model_group_info(model_group=model) total_model_tpm: Optional[int] = None total_model_rpm: Optional[int] = None if model_group_info is not None: @@ -210,10 +210,14 @@ class _PROXY_DynamicRateLimitHandler(CustomLogger): key_priority: Optional[str] = user_api_key_dict.metadata.get( "priority", None ) - available_tpm, available_rpm, model_tpm, model_rpm, active_projects = ( - await self.check_available_usage( - model=data["model"], priority=key_priority - ) + ( + available_tpm, + available_rpm, + model_tpm, + model_rpm, + active_projects, + ) = await self.check_available_usage( + model=data["model"], priority=key_priority ) ### CHECK TPM ### if available_tpm is not None and available_tpm == 0: @@ -267,21 +271,25 @@ class _PROXY_DynamicRateLimitHandler(CustomLogger): key_priority: Optional[str] = user_api_key_dict.metadata.get( "priority", None ) - available_tpm, available_rpm, model_tpm, model_rpm, active_projects = ( - await self.check_available_usage( - model=model_info["model_name"], priority=key_priority - ) - ) - response._hidden_params["additional_headers"] = ( - { # Add additional response headers - easier debugging - "x-litellm-model_group": model_info["model_name"], - "x-ratelimit-remaining-litellm-project-tokens": available_tpm, - "x-ratelimit-remaining-litellm-project-requests": available_rpm, - "x-ratelimit-remaining-model-tokens": model_tpm, - "x-ratelimit-remaining-model-requests": model_rpm, - "x-ratelimit-current-active-projects": active_projects, - } + ( + available_tpm, + available_rpm, + model_tpm, + model_rpm, + active_projects, + ) = await self.check_available_usage( + model=model_info["model_name"], priority=key_priority ) + response._hidden_params[ + "additional_headers" + ] = { # Add additional response headers - easier debugging + "x-litellm-model_group": model_info["model_name"], + "x-ratelimit-remaining-litellm-project-tokens": available_tpm, + "x-ratelimit-remaining-litellm-project-requests": available_rpm, + "x-ratelimit-remaining-model-tokens": model_tpm, + "x-ratelimit-remaining-model-requests": model_rpm, + "x-ratelimit-current-active-projects": active_projects, + } return response return await super().async_post_call_success_hook( diff --git a/litellm/proxy/hooks/key_management_event_hooks.py b/litellm/proxy/hooks/key_management_event_hooks.py index 2030cb2a45..c2c4f0669f 100644 --- a/litellm/proxy/hooks/key_management_event_hooks.py +++ b/litellm/proxy/hooks/key_management_event_hooks.py @@ -28,7 +28,6 @@ LITELLM_PREFIX_STORED_VIRTUAL_KEYS = "litellm/" class KeyManagementEventHooks: - @staticmethod async def async_key_generated_hook( data: GenerateKeyRequest, diff --git a/litellm/proxy/hooks/parallel_request_limiter.py b/litellm/proxy/hooks/parallel_request_limiter.py index 06f3b6afe5..242c013d67 100644 --- a/litellm/proxy/hooks/parallel_request_limiter.py +++ b/litellm/proxy/hooks/parallel_request_limiter.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: from litellm.proxy.utils import InternalUsageCache as _InternalUsageCache - Span = _Span + Span = Union[_Span, Any] InternalUsageCache = _InternalUsageCache else: Span = Any @@ -201,7 +201,9 @@ class _PROXY_MaxParallelRequestsHandler(CustomLogger): if rpm_limit is None: rpm_limit = sys.maxsize - values_to_update_in_cache: List[Tuple[Any, Any]] = ( + values_to_update_in_cache: List[ + Tuple[Any, Any] + ] = ( [] ) # values that need to get updated in cache, will run a batch_set_cache after this function @@ -678,9 +680,9 @@ class _PROXY_MaxParallelRequestsHandler(CustomLogger): async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time): try: self.print_verbose("Inside Max Parallel Request Failure Hook") - litellm_parent_otel_span: Union[Span, None] = ( - _get_parent_otel_span_from_kwargs(kwargs=kwargs) - ) + litellm_parent_otel_span: Union[ + Span, None + ] = _get_parent_otel_span_from_kwargs(kwargs=kwargs) _metadata = kwargs["litellm_params"].get("metadata", {}) or {} global_max_parallel_requests = _metadata.get( "global_max_parallel_requests", None @@ -807,11 +809,11 @@ class _PROXY_MaxParallelRequestsHandler(CustomLogger): current_minute = datetime.now().strftime("%M") precise_minute = f"{current_date}-{current_hour}-{current_minute}" request_count_api_key = f"{api_key}::{precise_minute}::request_count" - current: Optional[CurrentItemRateLimit] = ( - await self.internal_usage_cache.async_get_cache( - key=request_count_api_key, - litellm_parent_otel_span=user_api_key_dict.parent_otel_span, - ) + current: Optional[ + CurrentItemRateLimit + ] = await self.internal_usage_cache.async_get_cache( + key=request_count_api_key, + litellm_parent_otel_span=user_api_key_dict.parent_otel_span, ) key_remaining_rpm_limit: Optional[int] = None @@ -843,15 +845,15 @@ class _PROXY_MaxParallelRequestsHandler(CustomLogger): _additional_headers = _hidden_params.get("additional_headers", {}) or {} if key_remaining_rpm_limit is not None: - _additional_headers["x-ratelimit-remaining-requests"] = ( - key_remaining_rpm_limit - ) + _additional_headers[ + "x-ratelimit-remaining-requests" + ] = key_remaining_rpm_limit if key_rpm_limit is not None: _additional_headers["x-ratelimit-limit-requests"] = key_rpm_limit if key_remaining_tpm_limit is not None: - _additional_headers["x-ratelimit-remaining-tokens"] = ( - key_remaining_tpm_limit - ) + _additional_headers[ + "x-ratelimit-remaining-tokens" + ] = key_remaining_tpm_limit if key_tpm_limit is not None: _additional_headers["x-ratelimit-limit-tokens"] = key_tpm_limit diff --git a/litellm/proxy/hooks/prompt_injection_detection.py b/litellm/proxy/hooks/prompt_injection_detection.py index b1b2bbee5c..b8fa8466a3 100644 --- a/litellm/proxy/hooks/prompt_injection_detection.py +++ b/litellm/proxy/hooks/prompt_injection_detection.py @@ -196,7 +196,6 @@ class _OPTIONAL_PromptInjectionDetection(CustomLogger): return data except HTTPException as e: - if ( e.status_code == 400 and isinstance(e.detail, dict) diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index f205b0146f..39c1eeace9 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -51,10 +51,10 @@ class _ProxyDBLogger(CustomLogger): ) _metadata["user_api_key"] = user_api_key_dict.api_key _metadata["status"] = "failure" - _metadata["error_information"] = ( - StandardLoggingPayloadSetup.get_error_information( - original_exception=original_exception, - ) + _metadata[ + "error_information" + ] = StandardLoggingPayloadSetup.get_error_information( + original_exception=original_exception, ) existing_metadata: dict = request_data.get("metadata", None) or {} diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index ece5ecf4b7..6427be5a6e 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -346,11 +346,11 @@ class LiteLLMProxyRequestSetup: ## KEY-LEVEL SPEND LOGS / TAGS if "tags" in key_metadata and key_metadata["tags"] is not None: - data[_metadata_variable_name]["tags"] = ( - LiteLLMProxyRequestSetup._merge_tags( - request_tags=data[_metadata_variable_name].get("tags"), - tags_to_add=key_metadata["tags"], - ) + data[_metadata_variable_name][ + "tags" + ] = LiteLLMProxyRequestSetup._merge_tags( + request_tags=data[_metadata_variable_name].get("tags"), + tags_to_add=key_metadata["tags"], ) if "spend_logs_metadata" in key_metadata and isinstance( key_metadata["spend_logs_metadata"], dict @@ -556,9 +556,9 @@ async def add_litellm_data_to_request( # noqa: PLR0915 data[_metadata_variable_name]["litellm_api_version"] = version if general_settings is not None: - data[_metadata_variable_name]["global_max_parallel_requests"] = ( - general_settings.get("global_max_parallel_requests", None) - ) + data[_metadata_variable_name][ + "global_max_parallel_requests" + ] = general_settings.get("global_max_parallel_requests", None) ### KEY-LEVEL Controls key_metadata = user_api_key_dict.metadata diff --git a/litellm/proxy/management_endpoints/budget_management_endpoints.py b/litellm/proxy/management_endpoints/budget_management_endpoints.py index 20aa1c6bbf..65b0156afe 100644 --- a/litellm/proxy/management_endpoints/budget_management_endpoints.py +++ b/litellm/proxy/management_endpoints/budget_management_endpoints.py @@ -197,7 +197,6 @@ async def budget_settings( for field_name, field_info in BudgetNewRequest.model_fields.items(): if field_name in allowed_args: - _stored_in_db = True _response_obj = ConfigList( diff --git a/litellm/proxy/management_endpoints/common_utils.py b/litellm/proxy/management_endpoints/common_utils.py index d80a06c597..550ff44616 100644 --- a/litellm/proxy/management_endpoints/common_utils.py +++ b/litellm/proxy/management_endpoints/common_utils.py @@ -16,7 +16,6 @@ def _is_user_team_admin( if ( member.user_id is not None and member.user_id == user_api_key_dict.user_id ) and member.role == "admin": - return True return False diff --git a/litellm/proxy/management_endpoints/customer_endpoints.py b/litellm/proxy/management_endpoints/customer_endpoints.py index 976ff8581f..1f6f846bc7 100644 --- a/litellm/proxy/management_endpoints/customer_endpoints.py +++ b/litellm/proxy/management_endpoints/customer_endpoints.py @@ -230,7 +230,6 @@ async def new_end_user( detail={"error": CommonProxyErrors.db_not_connected_error.value}, ) try: - ## VALIDATION ## if data.default_model is not None: if llm_router is None: diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 79de6da1fd..90444013a8 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -82,9 +82,9 @@ def _update_internal_new_user_params(data_json: dict, data: NewUserRequest) -> d data_json["user_id"] = str(uuid.uuid4()) auto_create_key = data_json.pop("auto_create_key", True) if auto_create_key is False: - data_json["table_name"] = ( - "user" # only create a user, don't create key if 'auto_create_key' set to False - ) + data_json[ + "table_name" + ] = "user" # only create a user, don't create key if 'auto_create_key' set to False is_internal_user = False if data.user_role and data.user_role.is_internal_user_role: @@ -370,7 +370,6 @@ async def ui_get_available_role( _data_to_return = {} for role in LitellmUserRoles: - # We only show a subset of roles on UI if role in [ LitellmUserRoles.PROXY_ADMIN, @@ -652,9 +651,9 @@ def _update_internal_user_params(data_json: dict, data: UpdateUserRequest) -> di "budget_duration" not in non_default_values ): # applies internal user limits, if user role updated if is_internal_user and litellm.internal_user_budget_duration is not None: - non_default_values["budget_duration"] = ( - litellm.internal_user_budget_duration - ) + non_default_values[ + "budget_duration" + ] = litellm.internal_user_budget_duration duration_s = duration_in_seconds( duration=non_default_values["budget_duration"] ) @@ -965,13 +964,13 @@ async def get_users( "in": user_id_list, # Now passing a list of strings as required by Prisma } - users: Optional[List[LiteLLM_UserTable]] = ( - await prisma_client.db.litellm_usertable.find_many( - where=where_conditions, - skip=skip, - take=page_size, - order={"created_at": "desc"}, - ) + users: Optional[ + List[LiteLLM_UserTable] + ] = await prisma_client.db.litellm_usertable.find_many( + where=where_conditions, + skip=skip, + take=page_size, + order={"created_at": "desc"}, ) # Get total count of user rows @@ -1226,13 +1225,13 @@ async def ui_view_users( } # Query users with pagination and filters - users: Optional[List[BaseModel]] = ( - await prisma_client.db.litellm_usertable.find_many( - where=where_conditions, - skip=skip, - take=page_size, - order={"created_at": "desc"}, - ) + users: Optional[ + List[BaseModel] + ] = await prisma_client.db.litellm_usertable.find_many( + where=where_conditions, + skip=skip, + take=page_size, + order={"created_at": "desc"}, ) if not users: diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 9141d9d14a..b0bf1fb619 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -227,7 +227,6 @@ def _personal_key_membership_check( def _personal_key_generation_check( user_api_key_dict: UserAPIKeyAuth, data: GenerateKeyRequest ): - if ( litellm.key_generation_settings is None or litellm.key_generation_settings.get("personal_key_generation") is None @@ -568,9 +567,9 @@ async def generate_key_fn( # noqa: PLR0915 request_type="key", **data_json, table_name="key" ) - response["soft_budget"] = ( - data.soft_budget - ) # include the user-input soft budget in the response + response[ + "soft_budget" + ] = data.soft_budget # include the user-input soft budget in the response response = GenerateKeyResponse(**response) @@ -1448,10 +1447,10 @@ async def delete_verification_tokens( try: if prisma_client: tokens = [_hash_token_if_needed(token=key) for key in tokens] - _keys_being_deleted: List[LiteLLM_VerificationToken] = ( - await prisma_client.db.litellm_verificationtoken.find_many( - where={"token": {"in": tokens}} - ) + _keys_being_deleted: List[ + LiteLLM_VerificationToken + ] = await prisma_client.db.litellm_verificationtoken.find_many( + where={"token": {"in": tokens}} ) # Assuming 'db' is your Prisma Client instance @@ -1553,9 +1552,9 @@ async def _rotate_master_key( from litellm.proxy.proxy_server import proxy_config try: - models: Optional[List] = ( - await prisma_client.db.litellm_proxymodeltable.find_many() - ) + models: Optional[ + List + ] = await prisma_client.db.litellm_proxymodeltable.find_many() except Exception: models = None # 2. process model table @@ -1677,7 +1676,6 @@ async def regenerate_key_fn( Note: This is an Enterprise feature. It requires a premium license to use. """ try: - from litellm.proxy.proxy_server import ( hash_token, master_key, @@ -1824,7 +1822,6 @@ async def validate_key_list_check( key_alias: Optional[str], prisma_client: PrismaClient, ) -> Optional[LiteLLM_UserTable]: - if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: return None @@ -1835,11 +1832,11 @@ async def validate_key_list_check( param="user_id", code=status.HTTP_403_FORBIDDEN, ) - complete_user_info_db_obj: Optional[BaseModel] = ( - await prisma_client.db.litellm_usertable.find_unique( - where={"user_id": user_api_key_dict.user_id}, - include={"organization_memberships": True}, - ) + complete_user_info_db_obj: Optional[ + BaseModel + ] = await prisma_client.db.litellm_usertable.find_unique( + where={"user_id": user_api_key_dict.user_id}, + include={"organization_memberships": True}, ) if complete_user_info_db_obj is None: @@ -1900,10 +1897,10 @@ async def get_admin_team_ids( if complete_user_info is None: return [] # Get all teams that user is an admin of - teams: Optional[List[BaseModel]] = ( - await prisma_client.db.litellm_teamtable.find_many( - where={"team_id": {"in": complete_user_info.teams}} - ) + teams: Optional[ + List[BaseModel] + ] = await prisma_client.db.litellm_teamtable.find_many( + where={"team_id": {"in": complete_user_info.teams}} ) if teams is None: return [] diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 88245e36d1..0e8a9e4cc8 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -399,7 +399,6 @@ class ModelManagementAuthChecks: prisma_client: PrismaClient, premium_user: bool, ) -> Literal[True]: - ## Check team model auth if ( model_params.model_info is not None @@ -579,7 +578,6 @@ async def add_new_model( ) try: - if prisma_client is None: raise HTTPException( status_code=500, @@ -717,7 +715,6 @@ async def update_model( ) try: - if prisma_client is None: raise HTTPException( status_code=500, diff --git a/litellm/proxy/management_endpoints/organization_endpoints.py b/litellm/proxy/management_endpoints/organization_endpoints.py index c202043fbe..37de12a9d2 100644 --- a/litellm/proxy/management_endpoints/organization_endpoints.py +++ b/litellm/proxy/management_endpoints/organization_endpoints.py @@ -358,11 +358,11 @@ async def info_organization(organization_id: str): if prisma_client is None: raise HTTPException(status_code=500, detail={"error": "No db connected"}) - response: Optional[LiteLLM_OrganizationTableWithMembers] = ( - await prisma_client.db.litellm_organizationtable.find_unique( - where={"organization_id": organization_id}, - include={"litellm_budget_table": True, "members": True, "teams": True}, - ) + response: Optional[ + LiteLLM_OrganizationTableWithMembers + ] = await prisma_client.db.litellm_organizationtable.find_unique( + where={"organization_id": organization_id}, + include={"litellm_budget_table": True, "members": True, "teams": True}, ) if response is None: @@ -486,12 +486,13 @@ async def organization_member_add( updated_organization_memberships: List[LiteLLM_OrganizationMembershipTable] = [] for member in members: - updated_user, updated_organization_membership = ( - await add_member_to_organization( - member=member, - organization_id=data.organization_id, - prisma_client=prisma_client, - ) + ( + updated_user, + updated_organization_membership, + ) = await add_member_to_organization( + member=member, + organization_id=data.organization_id, + prisma_client=prisma_client, ) updated_users.append(updated_user) @@ -657,16 +658,16 @@ async def organization_member_update( }, data={"budget_id": budget_id}, ) - final_organization_membership: Optional[BaseModel] = ( - await prisma_client.db.litellm_organizationmembership.find_unique( - where={ - "user_id_organization_id": { - "user_id": data.user_id, - "organization_id": data.organization_id, - } - }, - include={"litellm_budget_table": True}, - ) + final_organization_membership: Optional[ + BaseModel + ] = await prisma_client.db.litellm_organizationmembership.find_unique( + where={ + "user_id_organization_id": { + "user_id": data.user_id, + "organization_id": data.organization_id, + } + }, + include={"litellm_budget_table": True}, ) if final_organization_membership is None: diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index f5bcc6ba11..842b5c8e75 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -506,12 +506,12 @@ async def update_team( updated_kv["model_id"] = _model_id updated_kv = prisma_client.jsonify_team_object(db_data=updated_kv) - team_row: Optional[LiteLLM_TeamTable] = ( - await prisma_client.db.litellm_teamtable.update( - where={"team_id": data.team_id}, - data=updated_kv, - include={"litellm_model_table": True}, # type: ignore - ) + team_row: Optional[ + LiteLLM_TeamTable + ] = await prisma_client.db.litellm_teamtable.update( + where={"team_id": data.team_id}, + data=updated_kv, + include={"litellm_model_table": True}, # type: ignore ) if team_row is None or team_row.team_id is None: @@ -1137,10 +1137,10 @@ async def delete_team( team_rows: List[LiteLLM_TeamTable] = [] for team_id in data.team_ids: try: - team_row_base: Optional[BaseModel] = ( - await prisma_client.db.litellm_teamtable.find_unique( - where={"team_id": team_id} - ) + team_row_base: Optional[ + BaseModel + ] = await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": team_id} ) if team_row_base is None: raise Exception @@ -1298,10 +1298,10 @@ async def team_info( ) try: - team_info: Optional[BaseModel] = ( - await prisma_client.db.litellm_teamtable.find_unique( - where={"team_id": team_id} - ) + team_info: Optional[ + BaseModel + ] = await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": team_id} ) if team_info is None: raise Exception diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index 86dec9fcaf..d38ff6b536 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -213,9 +213,9 @@ async def google_login(request: Request): # noqa: PLR0915 if state: redirect_params["state"] = state elif "okta" in generic_authorization_endpoint: - redirect_params["state"] = ( - uuid.uuid4().hex - ) # set state param for okta - required + redirect_params[ + "state" + ] = uuid.uuid4().hex # set state param for okta - required return await generic_sso.get_login_redirect(**redirect_params) # type: ignore elif ui_username is not None: # No Google, Microsoft SSO @@ -725,9 +725,9 @@ async def insert_sso_user( if user_defined_values.get("max_budget") is None: user_defined_values["max_budget"] = litellm.max_internal_user_budget if user_defined_values.get("budget_duration") is None: - user_defined_values["budget_duration"] = ( - litellm.internal_user_budget_duration - ) + user_defined_values[ + "budget_duration" + ] = litellm.internal_user_budget_duration if user_defined_values["user_role"] is None: user_defined_values["user_role"] = LitellmUserRoles.INTERNAL_USER_VIEW_ONLY diff --git a/litellm/proxy/management_helpers/utils.py b/litellm/proxy/management_helpers/utils.py index 69a5cf9141..cb8e079b76 100644 --- a/litellm/proxy/management_helpers/utils.py +++ b/litellm/proxy/management_helpers/utils.py @@ -179,7 +179,7 @@ def _delete_api_key_from_cache(kwargs): user_api_key_cache.delete_cache(key=update_request.key) # delete key request - if isinstance(update_request, KeyRequest): + if isinstance(update_request, KeyRequest) and update_request.keys: for key in update_request.keys: user_api_key_cache.delete_cache(key=key) pass @@ -251,7 +251,6 @@ async def send_management_endpoint_alert( proxy_logging_obj is not None and proxy_logging_obj.slack_alerting_instance is not None ): - # Virtual Key Events if function_name in management_function_to_event_name: _event_name: AlertType = management_function_to_event_name[function_name] diff --git a/litellm/proxy/openai_files_endpoints/files_endpoints.py b/litellm/proxy/openai_files_endpoints/files_endpoints.py index ffbca91c69..e810ba026e 100644 --- a/litellm/proxy/openai_files_endpoints/files_endpoints.py +++ b/litellm/proxy/openai_files_endpoints/files_endpoints.py @@ -316,7 +316,6 @@ async def get_file_content( data: Dict = {} try: - # Include original request and headers in the data data = await add_litellm_data_to_request( data=data, diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py index 51845956fc..d6f2a01712 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py @@ -25,7 +25,6 @@ else: class AnthropicPassthroughLoggingHandler: - @staticmethod def anthropic_passthrough_handler( httpx_response: httpx.Response, @@ -123,9 +122,9 @@ class AnthropicPassthroughLoggingHandler: litellm_model_response.id = logging_obj.litellm_call_id litellm_model_response.model = model logging_obj.model_call_details["model"] = model - logging_obj.model_call_details["custom_llm_provider"] = ( - litellm.LlmProviders.ANTHROPIC.value - ) + logging_obj.model_call_details[ + "custom_llm_provider" + ] = litellm.LlmProviders.ANTHROPIC.value return kwargs except Exception as e: verbose_proxy_logger.exception( diff --git a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py index 9443563738..a20f39e65c 100644 --- a/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py +++ b/litellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.py @@ -3,6 +3,7 @@ import re from datetime import datetime from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from urllib.parse import urlparse + import httpx import litellm @@ -222,7 +223,9 @@ class VertexPassthroughLoggingHandler: @staticmethod def _get_custom_llm_provider_from_url(url: str) -> str: parsed_url = urlparse(url) - if parsed_url.hostname and parsed_url.hostname.endswith("generativelanguage.googleapis.com"): + if parsed_url.hostname and parsed_url.hostname.endswith( + "generativelanguage.googleapis.com" + ): return litellm.LlmProviders.GEMINI.value return litellm.LlmProviders.VERTEX_AI.value diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index a13b0dc216..a6b1b3e614 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -373,7 +373,6 @@ async def pass_through_request( # noqa: PLR0915 litellm_call_id = str(uuid.uuid4()) url: Optional[httpx.URL] = None try: - from litellm.litellm_core_utils.litellm_logging import Logging from litellm.proxy.proxy_server import proxy_logging_obj @@ -384,7 +383,6 @@ async def pass_through_request( # noqa: PLR0915 ) if merge_query_params: - # Create a new URL with the merged query params url = url.copy_with( query=urlencode( @@ -771,7 +769,6 @@ def _is_streaming_response(response: httpx.Response) -> bool: async def initialize_pass_through_endpoints(pass_through_endpoints: list): - verbose_proxy_logger.debug("initializing pass through endpoints") from litellm.proxy._types import CommonProxyErrors, LiteLLMRoutes from litellm.proxy.proxy_server import app, premium_user diff --git a/litellm/proxy/pass_through_endpoints/passthrough_endpoint_router.py b/litellm/proxy/pass_through_endpoints/passthrough_endpoint_router.py index 89cccfc071..a02cacc3cc 100644 --- a/litellm/proxy/pass_through_endpoints/passthrough_endpoint_router.py +++ b/litellm/proxy/pass_through_endpoints/passthrough_endpoint_router.py @@ -130,9 +130,9 @@ class PassthroughEndpointRouter: vertex_location=location, vertex_credentials=vertex_credentials, ) - self.deployment_key_to_vertex_credentials[deployment_key] = ( - vertex_pass_through_credentials - ) + self.deployment_key_to_vertex_credentials[ + deployment_key + ] = vertex_pass_through_credentials def _get_deployment_key( self, project_id: Optional[str], location: Optional[str] diff --git a/litellm/proxy/pass_through_endpoints/streaming_handler.py b/litellm/proxy/pass_through_endpoints/streaming_handler.py index b022bf1d25..2c11e4a2dd 100644 --- a/litellm/proxy/pass_through_endpoints/streaming_handler.py +++ b/litellm/proxy/pass_through_endpoints/streaming_handler.py @@ -21,7 +21,6 @@ from .types import EndpointType class PassThroughStreamingHandler: - @staticmethod async def chunk_processor( response: httpx.Response, diff --git a/litellm/proxy/prisma_migration.py b/litellm/proxy/prisma_migration.py index 22fa4da9de..c1e2220c15 100644 --- a/litellm/proxy/prisma_migration.py +++ b/litellm/proxy/prisma_migration.py @@ -9,8 +9,8 @@ import time sys.path.insert( 0, os.path.abspath("./") ) # Adds the parent directory to the system path -from litellm.secret_managers.aws_secret_manager import decrypt_env_var from litellm._logging import verbose_proxy_logger +from litellm.secret_managers.aws_secret_manager import decrypt_env_var if os.getenv("USE_AWS_KMS", None) is not None and os.getenv("USE_AWS_KMS") == "True": ## V2 IMPLEMENTATION OF AWS KMS - USER WANTS TO DECRYPT MULTIPLE KEYS IN THEIR ENV @@ -39,7 +39,9 @@ if not database_url: ) exit(1) else: - verbose_proxy_logger.info("Using existing DATABASE_URL environment variable") # Log existing DATABASE_URL + verbose_proxy_logger.info( + "Using existing DATABASE_URL environment variable" + ) # Log existing DATABASE_URL # Set DIRECT_URL to the value of DATABASE_URL if it is not set, required for migrations direct_url = os.getenv("DIRECT_URL") @@ -63,12 +65,18 @@ while retry_count < max_retries and exit_code != 0: # run prisma generate verbose_proxy_logger.info("Running 'prisma generate'...") result = subprocess.run(["prisma", "generate"], capture_output=True, text=True) - verbose_proxy_logger.info(f"'prisma generate' stdout: {result.stdout}") # Log stdout + verbose_proxy_logger.info( + f"'prisma generate' stdout: {result.stdout}" + ) # Log stdout exit_code = result.returncode if exit_code != 0: - verbose_proxy_logger.info(f"'prisma generate' failed with exit code {exit_code}.") - verbose_proxy_logger.error(f"'prisma generate' stderr: {result.stderr}") # Log stderr + verbose_proxy_logger.info( + f"'prisma generate' failed with exit code {exit_code}." + ) + verbose_proxy_logger.error( + f"'prisma generate' stderr: {result.stderr}" + ) # Log stderr # Run the Prisma db push command verbose_proxy_logger.info("Running 'prisma db push --accept-data-loss'...") @@ -79,14 +87,20 @@ while retry_count < max_retries and exit_code != 0: exit_code = result.returncode if exit_code != 0: - verbose_proxy_logger.info(f"'prisma db push' stderr: {result.stderr}") # Log stderr - verbose_proxy_logger.error(f"'prisma db push' failed with exit code {exit_code}.") + verbose_proxy_logger.info( + f"'prisma db push' stderr: {result.stderr}" + ) # Log stderr + verbose_proxy_logger.error( + f"'prisma db push' failed with exit code {exit_code}." + ) if retry_count < max_retries: verbose_proxy_logger.info("Retrying in 10 seconds...") time.sleep(10) if retry_count == max_retries and exit_code != 0: - verbose_proxy_logger.error(f"Unable to push database changes after {max_retries} retries.") + verbose_proxy_logger.error( + f"Unable to push database changes after {max_retries} retries." + ) exit(1) verbose_proxy_logger.info("Database push successful!") diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 89c15f413d..f59d117181 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -18,6 +18,7 @@ from typing import ( List, Optional, Tuple, + Union, cast, get_args, get_origin, @@ -36,7 +37,7 @@ if TYPE_CHECKING: from litellm.integrations.opentelemetry import OpenTelemetry - Span = _Span + Span = Union[_Span, Any] else: Span = Any OpenTelemetry = Any @@ -763,9 +764,9 @@ model_max_budget_limiter = _PROXY_VirtualKeyModelMaxBudgetLimiter( dual_cache=user_api_key_cache ) litellm.logging_callback_manager.add_litellm_callback(model_max_budget_limiter) -redis_usage_cache: Optional[RedisCache] = ( - None # redis cache used for tracking spend, tpm/rpm limits -) +redis_usage_cache: Optional[ + RedisCache +] = None # redis cache used for tracking spend, tpm/rpm limits user_custom_auth = None user_custom_key_generate = None user_custom_sso = None @@ -818,7 +819,6 @@ async def check_request_disconnection(request: Request, llm_api_call_task): while time.time() - start_time < 600: await asyncio.sleep(1) if await request.is_disconnected(): - # cancel the LLM API Call task if any passed - this is passed from individual providers # Example OpenAI, Azure, VertexAI etc llm_api_call_task.cancel() @@ -1092,9 +1092,9 @@ async def update_cache( # noqa: PLR0915 _id = "team_id:{}".format(team_id) try: # Fetch the existing cost for the given user - existing_spend_obj: Optional[LiteLLM_TeamTable] = ( - await user_api_key_cache.async_get_cache(key=_id) - ) + existing_spend_obj: Optional[ + LiteLLM_TeamTable + ] = await user_api_key_cache.async_get_cache(key=_id) if existing_spend_obj is None: # do nothing if team not in api key cache return @@ -1589,7 +1589,7 @@ class ProxyConfig: # users can pass os.environ/ variables on the proxy - we should read them from the env for key, value in cache_params.items(): - if type(value) is str and value.startswith("os.environ/"): + if isinstance(value, str) and value.startswith("os.environ/"): cache_params[key] = get_secret(value) ## to pass a complete url, or set ssl=True, etc. just set it as `os.environ[REDIS_URL] = `, _redis.py checks for REDIS specific environment variables @@ -1610,7 +1610,6 @@ class ProxyConfig: litellm.guardrail_name_config_map = guardrail_name_config_map elif key == "callbacks": - initialize_callbacks_on_proxy( value=value, premium_user=premium_user, @@ -2765,9 +2764,9 @@ async def initialize( # noqa: PLR0915 user_api_base = api_base dynamic_config[user_model]["api_base"] = api_base if api_version: - os.environ["AZURE_API_VERSION"] = ( - api_version # set this for azure - litellm can read this from the env - ) + os.environ[ + "AZURE_API_VERSION" + ] = api_version # set this for azure - litellm can read this from the env if max_tokens: # model-specific param dynamic_config[user_model]["max_tokens"] = max_tokens if temperature: # model-specific param @@ -2810,7 +2809,6 @@ async def async_assistants_data_generator( try: time.time() async with response as chunk: - ### CALL HOOKS ### - modify outgoing data chunk = await proxy_logging_obj.async_post_call_streaming_hook( user_api_key_dict=user_api_key_dict, response=chunk @@ -4675,7 +4673,6 @@ async def get_thread( global proxy_logging_obj data: Dict = {} try: - # Include original request and headers in the data data = await add_litellm_data_to_request( data=data, @@ -6385,7 +6382,6 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: - _stored_in_db: Optional[bool] = None if field_name in alerting_args_dict: _stored_in_db = True @@ -7333,7 +7329,6 @@ async def update_config(config_info: ConfigYAML): # noqa: PLR0915 "success_callback" in updated_litellm_settings and "success_callback" in config["litellm_settings"] ): - # check both success callback are lists if isinstance( config["litellm_settings"]["success_callback"], list @@ -7588,7 +7583,6 @@ async def get_config_list( for field_name, field_info in ConfigGeneralSettings.model_fields.items(): if field_name in allowed_args: - ## HANDLE TYPED DICT typed_dict_type = allowed_args[field_name]["type"] @@ -7621,9 +7615,9 @@ async def get_config_list( hasattr(sub_field_info, "description") and sub_field_info.description is not None ): - nested_fields[idx].field_description = ( - sub_field_info.description - ) + nested_fields[ + idx + ].field_description = sub_field_info.description idx += 1 _stored_in_db = None diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 4c0e22aef7..4690b6cbd8 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -286,7 +286,6 @@ async def get_global_activity( user_api_key_dict, start_date_obj, end_date_obj ) else: - sql_query = """ SELECT date_trunc('day', "startTime") AS date, @@ -453,7 +452,6 @@ async def get_global_activity_model( user_api_key_dict, start_date_obj, end_date_obj ) else: - sql_query = """ SELECT model_group, @@ -1096,7 +1094,6 @@ async def get_global_spend_report( start_date_obj, end_date_obj, team_id, customer_id, prisma_client ) if group_by == "team": - # first get data from spend logs -> SpendByModelApiKey # then read data from "SpendByModelApiKey" to format the response obj sql_query = """ @@ -1689,7 +1686,6 @@ async def ui_view_spend_logs( # noqa: PLR0915 ) try: - # Convert the date strings to datetime objects start_date_obj = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace( tzinfo=timezone.utc @@ -2160,7 +2156,6 @@ async def global_spend_for_internal_user( code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) try: - user_id = user_api_key_dict.user_id if user_id is None: raise ValueError("/global/spend/logs Error: User ID is None") @@ -2293,7 +2288,6 @@ async def global_spend(): from litellm.proxy.proxy_server import prisma_client try: - total_spend = 0.0 if prisma_client is None: diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 4c82f586fb..900b26f3f1 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -78,7 +78,7 @@ from litellm.types.utils import CallTypes, LoggedLiteLLMParams if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any @@ -491,7 +491,6 @@ class ProxyLogging: try: for callback in litellm.callbacks: - _callback = None if isinstance(callback, str): _callback = litellm.litellm_core_utils.litellm_logging.get_custom_logger_compatible_class( @@ -1197,9 +1196,9 @@ class PrismaClient: api_requests=1, ) - self.daily_user_spend_transactions[daily_transaction_key] = ( - daily_transaction - ) + self.daily_user_spend_transactions[ + daily_transaction_key + ] = daily_transaction except Exception as e: raise e diff --git a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py index 0c91c326f5..684e2ad061 100644 --- a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py @@ -69,10 +69,10 @@ async def langfuse_proxy_route( request=request, api_key="Bearer {}".format(api_key) ) - callback_settings_obj: Optional[TeamCallbackMetadata] = ( - _get_dynamic_logging_metadata( - user_api_key_dict=user_api_key_dict, proxy_config=proxy_config - ) + callback_settings_obj: Optional[ + TeamCallbackMetadata + ] = _get_dynamic_logging_metadata( + user_api_key_dict=user_api_key_dict, proxy_config=proxy_config ) dynamic_langfuse_public_key: Optional[str] = None diff --git a/litellm/rerank_api/main.py b/litellm/rerank_api/main.py index ce8ae21c82..9307ce5a55 100644 --- a/litellm/rerank_api/main.py +++ b/litellm/rerank_api/main.py @@ -107,13 +107,16 @@ def rerank( # noqa: PLR0915 k for k, v in unique_version_params.items() if v is not None ] - model, _custom_llm_provider, dynamic_api_key, dynamic_api_base = ( - litellm.get_llm_provider( - model=model, - custom_llm_provider=custom_llm_provider, - api_base=optional_params.api_base, - api_key=optional_params.api_key, - ) + ( + model, + _custom_llm_provider, + dynamic_api_key, + dynamic_api_base, + ) = litellm.get_llm_provider( + model=model, + custom_llm_provider=custom_llm_provider, + api_base=optional_params.api_base, + api_key=optional_params.api_key, ) rerank_provider_config: BaseRerankConfig = ( @@ -272,7 +275,6 @@ def rerank( # noqa: PLR0915 _is_async=_is_async, ) elif _custom_llm_provider == "jina_ai": - if dynamic_api_key is None: raise ValueError( "Jina AI API key is required, please set 'JINA_AI_API_KEY' in your environment" diff --git a/litellm/responses/main.py b/litellm/responses/main.py index aec2f8fe4a..70b651f376 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -165,21 +165,24 @@ def responses( # get llm provider logic litellm_params = GenericLiteLLMParams(**kwargs) - model, custom_llm_provider, dynamic_api_key, dynamic_api_base = ( - litellm.get_llm_provider( - model=model, - custom_llm_provider=custom_llm_provider, - api_base=litellm_params.api_base, - api_key=litellm_params.api_key, - ) + ( + model, + custom_llm_provider, + dynamic_api_key, + dynamic_api_base, + ) = litellm.get_llm_provider( + model=model, + custom_llm_provider=custom_llm_provider, + api_base=litellm_params.api_base, + api_key=litellm_params.api_key, ) # get provider config - responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( - ProviderConfigManager.get_provider_responses_api_config( - model=model, - provider=litellm.LlmProviders(custom_llm_provider), - ) + responses_api_provider_config: Optional[ + BaseResponsesAPIConfig + ] = ProviderConfigManager.get_provider_responses_api_config( + model=model, + provider=litellm.LlmProviders(custom_llm_provider), ) if responses_api_provider_config is None: diff --git a/litellm/router.py b/litellm/router.py index f739bc381d..78ad2afe1a 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -148,7 +148,7 @@ from .router_utils.pattern_match_deployments import PatternMatchRouter if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any @@ -333,9 +333,9 @@ class Router: ) # names of models under litellm_params. ex. azure/chatgpt-v-2 self.deployment_latency_map = {} ### CACHING ### - cache_type: Literal["local", "redis", "redis-semantic", "s3", "disk"] = ( - "local" # default to an in-memory cache - ) + cache_type: Literal[ + "local", "redis", "redis-semantic", "s3", "disk" + ] = "local" # default to an in-memory cache redis_cache = None cache_config: Dict[str, Any] = {} @@ -556,9 +556,9 @@ class Router: ) ) - self.model_group_retry_policy: Optional[Dict[str, RetryPolicy]] = ( - model_group_retry_policy - ) + self.model_group_retry_policy: Optional[ + Dict[str, RetryPolicy] + ] = model_group_retry_policy self.allowed_fails_policy: Optional[AllowedFailsPolicy] = None if allowed_fails_policy is not None: @@ -1093,9 +1093,9 @@ class Router: """ Adds default litellm params to kwargs, if set. """ - self.default_litellm_params[metadata_variable_name] = ( - self.default_litellm_params.pop("metadata", {}) - ) + self.default_litellm_params[ + metadata_variable_name + ] = self.default_litellm_params.pop("metadata", {}) for k, v in self.default_litellm_params.items(): if ( k not in kwargs and v is not None @@ -1678,14 +1678,16 @@ class Router: f"Prompt variables is set but not a dictionary. Got={prompt_variables}, type={type(prompt_variables)}" ) - model, messages, optional_params = ( - litellm_logging_object.get_chat_completion_prompt( - model=litellm_model, - messages=messages, - non_default_params=get_non_default_completion_params(kwargs=kwargs), - prompt_id=prompt_id, - prompt_variables=prompt_variables, - ) + ( + model, + messages, + optional_params, + ) = litellm_logging_object.get_chat_completion_prompt( + model=litellm_model, + messages=messages, + non_default_params=get_non_default_completion_params(kwargs=kwargs), + prompt_id=prompt_id, + prompt_variables=prompt_variables, ) kwargs = {**kwargs, **optional_params} @@ -2924,7 +2926,6 @@ class Router: Future Improvement - cache the result. """ try: - filtered_model_list = self.get_model_list() if filtered_model_list is None: raise Exception("Router not yet initialized.") @@ -3211,11 +3212,11 @@ class Router: if isinstance(e, litellm.ContextWindowExceededError): if context_window_fallbacks is not None: - fallback_model_group: Optional[List[str]] = ( - self._get_fallback_model_group_from_fallbacks( - fallbacks=context_window_fallbacks, - model_group=model_group, - ) + fallback_model_group: Optional[ + List[str] + ] = self._get_fallback_model_group_from_fallbacks( + fallbacks=context_window_fallbacks, + model_group=model_group, ) if fallback_model_group is None: raise original_exception @@ -3247,11 +3248,11 @@ class Router: e.message += "\n{}".format(error_message) elif isinstance(e, litellm.ContentPolicyViolationError): if content_policy_fallbacks is not None: - fallback_model_group: Optional[List[str]] = ( - self._get_fallback_model_group_from_fallbacks( - fallbacks=content_policy_fallbacks, - model_group=model_group, - ) + fallback_model_group: Optional[ + List[str] + ] = self._get_fallback_model_group_from_fallbacks( + fallbacks=content_policy_fallbacks, + model_group=model_group, ) if fallback_model_group is None: raise original_exception @@ -3282,11 +3283,12 @@ class Router: e.message += "\n{}".format(error_message) if fallbacks is not None and model_group is not None: verbose_router_logger.debug(f"inside model fallbacks: {fallbacks}") - fallback_model_group, generic_fallback_idx = ( - get_fallback_model_group( - fallbacks=fallbacks, # if fallbacks = [{"gpt-3.5-turbo": ["claude-3-haiku"]}] - model_group=cast(str, model_group), - ) + ( + fallback_model_group, + generic_fallback_idx, + ) = get_fallback_model_group( + fallbacks=fallbacks, # if fallbacks = [{"gpt-3.5-turbo": ["claude-3-haiku"]}] + model_group=cast(str, model_group), ) ## if none, check for generic fallback if ( @@ -3444,11 +3446,12 @@ class Router: """ Retry Logic """ - _healthy_deployments, _all_deployments = ( - await self._async_get_healthy_deployments( - model=kwargs.get("model") or "", - parent_otel_span=parent_otel_span, - ) + ( + _healthy_deployments, + _all_deployments, + ) = await self._async_get_healthy_deployments( + model=kwargs.get("model") or "", + parent_otel_span=parent_otel_span, ) # raises an exception if this error should not be retries @@ -3513,11 +3516,12 @@ class Router: remaining_retries = num_retries - current_attempt _model: Optional[str] = kwargs.get("model") # type: ignore if _model is not None: - _healthy_deployments, _ = ( - await self._async_get_healthy_deployments( - model=_model, - parent_otel_span=parent_otel_span, - ) + ( + _healthy_deployments, + _, + ) = await self._async_get_healthy_deployments( + model=_model, + parent_otel_span=parent_otel_span, ) else: _healthy_deployments = [] @@ -3884,7 +3888,6 @@ class Router: ) if exception_headers is not None: - _time_to_cooldown = ( litellm.utils._get_retry_after_from_exception_header( response_headers=exception_headers @@ -6131,7 +6134,6 @@ class Router: try: model_id = deployment.get("model_info", {}).get("id", None) if response is None: - # update self.deployment_stats if model_id is not None: self._update_usage( diff --git a/litellm/router_strategy/base_routing_strategy.py b/litellm/router_strategy/base_routing_strategy.py index a39d17e386..ea87e25eba 100644 --- a/litellm/router_strategy/base_routing_strategy.py +++ b/litellm/router_strategy/base_routing_strategy.py @@ -38,9 +38,9 @@ class BaseRoutingStrategy(ABC): except RuntimeError: # No event loop in current thread self._create_sync_thread(default_sync_interval) - self.in_memory_keys_to_update: set[str] = ( - set() - ) # Set with max size of 1000 keys + self.in_memory_keys_to_update: set[ + str + ] = set() # Set with max size of 1000 keys async def _increment_value_in_current_window( self, key: str, value: Union[int, float], ttl: int diff --git a/litellm/router_strategy/budget_limiter.py b/litellm/router_strategy/budget_limiter.py index 4f123df282..9e4001b67b 100644 --- a/litellm/router_strategy/budget_limiter.py +++ b/litellm/router_strategy/budget_limiter.py @@ -53,9 +53,9 @@ class RouterBudgetLimiting(CustomLogger): self.dual_cache = dual_cache self.redis_increment_operation_queue: List[RedisPipelineIncrementOperation] = [] asyncio.create_task(self.periodic_sync_in_memory_spend_with_redis()) - self.provider_budget_config: Optional[GenericBudgetConfigType] = ( - provider_budget_config - ) + self.provider_budget_config: Optional[ + GenericBudgetConfigType + ] = provider_budget_config self.deployment_budget_config: Optional[GenericBudgetConfigType] = None self.tag_budget_config: Optional[GenericBudgetConfigType] = None self._init_provider_budgets() @@ -94,11 +94,13 @@ class RouterBudgetLimiting(CustomLogger): potential_deployments: List[Dict] = [] - cache_keys, provider_configs, deployment_configs = ( - await self._async_get_cache_keys_for_router_budget_limiting( - healthy_deployments=healthy_deployments, - request_kwargs=request_kwargs, - ) + ( + cache_keys, + provider_configs, + deployment_configs, + ) = await self._async_get_cache_keys_for_router_budget_limiting( + healthy_deployments=healthy_deployments, + request_kwargs=request_kwargs, ) # Single cache read for all spend values @@ -114,17 +116,18 @@ class RouterBudgetLimiting(CustomLogger): for idx, key in enumerate(cache_keys): spend_map[key] = float(current_spends[idx] or 0.0) - potential_deployments, deployment_above_budget_info = ( - self._filter_out_deployments_above_budget( - healthy_deployments=healthy_deployments, - provider_configs=provider_configs, - deployment_configs=deployment_configs, - spend_map=spend_map, - potential_deployments=potential_deployments, - request_tags=_get_tags_from_request_kwargs( - request_kwargs=request_kwargs - ), - ) + ( + potential_deployments, + deployment_above_budget_info, + ) = self._filter_out_deployments_above_budget( + healthy_deployments=healthy_deployments, + provider_configs=provider_configs, + deployment_configs=deployment_configs, + spend_map=spend_map, + potential_deployments=potential_deployments, + request_tags=_get_tags_from_request_kwargs( + request_kwargs=request_kwargs + ), ) if len(potential_deployments) == 0: diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index b049c94264..55ca98843d 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -14,7 +14,7 @@ from litellm.types.utils import LiteLLMPydanticObjectBase if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/router_strategy/lowest_tpm_rpm_v2.py b/litellm/router_strategy/lowest_tpm_rpm_v2.py index d1a46b7ea8..9e6c139314 100644 --- a/litellm/router_strategy/lowest_tpm_rpm_v2.py +++ b/litellm/router_strategy/lowest_tpm_rpm_v2.py @@ -20,7 +20,7 @@ from .base_routing_strategy import BaseRoutingStrategy if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any @@ -69,7 +69,6 @@ class LowestTPMLoggingHandler_v2(BaseRoutingStrategy, CustomLogger): Raises - RateLimitError if deployment over defined RPM limit """ try: - # ------------ # Setup values # ------------ diff --git a/litellm/router_utils/cooldown_cache.py b/litellm/router_utils/cooldown_cache.py index f096b026c0..13d6318fc4 100644 --- a/litellm/router_utils/cooldown_cache.py +++ b/litellm/router_utils/cooldown_cache.py @@ -3,7 +3,7 @@ Wrapper around router cache. Meant to handle model cooldown logic """ import time -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypedDict +from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypedDict, Union from litellm import verbose_logger from litellm.caching.caching import DualCache @@ -12,7 +12,7 @@ from litellm.caching.in_memory_cache import InMemoryCache if TYPE_CHECKING: from opentelemetry.trace import Span as _Span - Span = _Span + Span = Union[_Span, Any] else: Span = Any diff --git a/litellm/router_utils/cooldown_callbacks.py b/litellm/router_utils/cooldown_callbacks.py index 54a016d3ec..5961a04feb 100644 --- a/litellm/router_utils/cooldown_callbacks.py +++ b/litellm/router_utils/cooldown_callbacks.py @@ -59,9 +59,9 @@ async def router_cooldown_event_callback( pass # get the prometheus logger from in memory loggers - prometheusLogger: Optional[PrometheusLogger] = ( - _get_prometheus_logger_from_callbacks() - ) + prometheusLogger: Optional[ + PrometheusLogger + ] = _get_prometheus_logger_from_callbacks() if prometheusLogger is not None: prometheusLogger.set_deployment_complete_outage( diff --git a/litellm/router_utils/cooldown_handlers.py b/litellm/router_utils/cooldown_handlers.py index 52babc27f2..ed9c2dd229 100644 --- a/litellm/router_utils/cooldown_handlers.py +++ b/litellm/router_utils/cooldown_handlers.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: from litellm.router import Router as _Router LitellmRouter = _Router - Span = _Span + Span = Union[_Span, Any] else: LitellmRouter = Any Span = Any diff --git a/litellm/router_utils/handle_error.py b/litellm/router_utils/handle_error.py index 132440cbc3..c331da70ac 100644 --- a/litellm/router_utils/handle_error.py +++ b/litellm/router_utils/handle_error.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union from litellm._logging import verbose_router_logger from litellm.router_utils.cooldown_handlers import ( @@ -13,7 +13,7 @@ if TYPE_CHECKING: from litellm.router import Router as _Router LitellmRouter = _Router - Span = _Span + Span = Union[_Span, Any] else: LitellmRouter = Any Span = Any diff --git a/litellm/router_utils/pattern_match_deployments.py b/litellm/router_utils/pattern_match_deployments.py index 729510574a..c6804b1ad4 100644 --- a/litellm/router_utils/pattern_match_deployments.py +++ b/litellm/router_utils/pattern_match_deployments.py @@ -105,13 +105,11 @@ class PatternMatchRouter: new_deployments = [] for deployment in deployments: new_deployment = copy.deepcopy(deployment) - new_deployment["litellm_params"]["model"] = ( - PatternMatchRouter.set_deployment_model_name( - matched_pattern=matched_pattern, - litellm_deployment_litellm_model=deployment["litellm_params"][ - "model" - ], - ) + new_deployment["litellm_params"][ + "model" + ] = PatternMatchRouter.set_deployment_model_name( + matched_pattern=matched_pattern, + litellm_deployment_litellm_model=deployment["litellm_params"]["model"], ) new_deployments.append(new_deployment) diff --git a/litellm/router_utils/prompt_caching_cache.py b/litellm/router_utils/prompt_caching_cache.py index 1bf686d694..6a96b85e8a 100644 --- a/litellm/router_utils/prompt_caching_cache.py +++ b/litellm/router_utils/prompt_caching_cache.py @@ -4,7 +4,7 @@ Wrapper around router cache. Meant to store model id when prompt caching support import hashlib import json -from typing import TYPE_CHECKING, Any, List, Optional, TypedDict +from typing import TYPE_CHECKING, Any, List, Optional, TypedDict, Union from litellm.caching.caching import DualCache from litellm.caching.in_memory_cache import InMemoryCache @@ -16,7 +16,7 @@ if TYPE_CHECKING: from litellm.router import Router litellm_router = Router - Span = _Span + Span = Union[_Span, Any] else: Span = Any litellm_router = Any diff --git a/litellm/secret_managers/aws_secret_manager_v2.py b/litellm/secret_managers/aws_secret_manager_v2.py index fd89d6c558..327dbf3d19 100644 --- a/litellm/secret_managers/aws_secret_manager_v2.py +++ b/litellm/secret_managers/aws_secret_manager_v2.py @@ -50,7 +50,6 @@ class AWSSecretsManagerV2(BaseAWSLLM, BaseSecretManager): if use_aws_secret_manager is None or use_aws_secret_manager is False: return try: - cls.validate_environment() litellm.secret_manager_client = cls() litellm._key_management_system = KeyManagementSystem.AWS_SECRET_MANAGER diff --git a/litellm/secret_managers/hashicorp_secret_manager.py b/litellm/secret_managers/hashicorp_secret_manager.py index a3d129f89c..e0b4a08ce8 100644 --- a/litellm/secret_managers/hashicorp_secret_manager.py +++ b/litellm/secret_managers/hashicorp_secret_manager.py @@ -34,7 +34,7 @@ class HashicorpSecretManager(BaseSecretManager): # Validate environment if not self.vault_token: raise ValueError( - "Missing Vault token. Please set VAULT_TOKEN in your environment." + "Missing Vault token. Please set HCP_VAULT_TOKEN in your environment." ) litellm.secret_manager_client = self @@ -90,9 +90,9 @@ class HashicorpSecretManager(BaseSecretManager): headers["X-Vault-Namespace"] = self.vault_namespace try: # We use the client cert and key for mutual TLS - resp = httpx.post( + client = httpx.Client(cert=(self.tls_cert_path, self.tls_key_path)) + resp = client.post( login_url, - cert=(self.tls_cert_path, self.tls_key_path), headers=headers, json=self._get_tls_cert_auth_body(), ) diff --git a/litellm/types/integrations/arize_phoenix.py b/litellm/types/integrations/arize_phoenix.py index 4566022d17..a8a1fed5a6 100644 --- a/litellm/types/integrations/arize_phoenix.py +++ b/litellm/types/integrations/arize_phoenix.py @@ -1,9 +1,11 @@ from typing import TYPE_CHECKING, Literal, Optional from pydantic import BaseModel + from .arize import Protocol + class ArizePhoenixConfig(BaseModel): otlp_auth_headers: Optional[str] = None protocol: Protocol - endpoint: str + endpoint: str diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 1c5552637c..3ba5a3a4e0 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -722,12 +722,12 @@ class OpenAIChatCompletionChunk(ChatCompletionChunk): class Hyperparameters(BaseModel): batch_size: Optional[Union[str, int]] = None # "Number of examples in each batch." - learning_rate_multiplier: Optional[Union[str, float]] = ( - None # Scaling factor for the learning rate - ) - n_epochs: Optional[Union[str, int]] = ( - None # "The number of epochs to train the model for" - ) + learning_rate_multiplier: Optional[ + Union[str, float] + ] = None # Scaling factor for the learning rate + n_epochs: Optional[ + Union[str, int] + ] = None # "The number of epochs to train the model for" class FineTuningJobCreate(BaseModel): @@ -754,18 +754,18 @@ class FineTuningJobCreate(BaseModel): model: str # "The name of the model to fine-tune." training_file: str # "The ID of an uploaded file that contains training data." - hyperparameters: Optional[Hyperparameters] = ( - None # "The hyperparameters used for the fine-tuning job." - ) - suffix: Optional[str] = ( - None # "A string of up to 18 characters that will be added to your fine-tuned model name." - ) - validation_file: Optional[str] = ( - None # "The ID of an uploaded file that contains validation data." - ) - integrations: Optional[List[str]] = ( - None # "A list of integrations to enable for your fine-tuning job." - ) + hyperparameters: Optional[ + Hyperparameters + ] = None # "The hyperparameters used for the fine-tuning job." + suffix: Optional[ + str + ] = None # "A string of up to 18 characters that will be added to your fine-tuned model name." + validation_file: Optional[ + str + ] = None # "The ID of an uploaded file that contains validation data." + integrations: Optional[ + List[str] + ] = None # "A list of integrations to enable for your fine-tuning job." seed: Optional[int] = None # "The seed controls the reproducibility of the job." diff --git a/litellm/types/rerank.py b/litellm/types/rerank.py index 8e2a8cc334..fb6dae0d1d 100644 --- a/litellm/types/rerank.py +++ b/litellm/types/rerank.py @@ -21,7 +21,6 @@ class RerankRequest(BaseModel): max_tokens_per_doc: Optional[int] = None - class OptionalRerankParams(TypedDict, total=False): query: str top_n: Optional[int] @@ -60,9 +59,9 @@ class RerankResponseResult(TypedDict, total=False): class RerankResponse(BaseModel): id: Optional[str] = None - results: Optional[List[RerankResponseResult]] = ( - None # Contains index and relevance_score - ) + results: Optional[ + List[RerankResponseResult] + ] = None # Contains index and relevance_score meta: Optional[RerankResponseMeta] = None # Contains api_version and billed_units # Define private attributes using PrivateAttr diff --git a/litellm/types/router.py b/litellm/types/router.py index dcd547def2..45a8a3fcf6 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -95,18 +95,16 @@ class ModelInfo(BaseModel): id: Optional[ str ] # Allow id to be optional on input, but it will always be present as a str in the model instance - db_model: bool = ( - False # used for proxy - to separate models which are stored in the db vs. config. - ) + db_model: bool = False # used for proxy - to separate models which are stored in the db vs. config. updated_at: Optional[datetime.datetime] = None updated_by: Optional[str] = None created_at: Optional[datetime.datetime] = None created_by: Optional[str] = None - base_model: Optional[str] = ( - None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking - ) + base_model: Optional[ + str + ] = None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking tier: Optional[Literal["free", "paid"]] = None """ @@ -171,12 +169,12 @@ class GenericLiteLLMParams(CredentialLiteLLMParams): custom_llm_provider: Optional[str] = None tpm: Optional[int] = None rpm: Optional[int] = None - timeout: Optional[Union[float, str, httpx.Timeout]] = ( - None # if str, pass in as os.environ/ - ) - stream_timeout: Optional[Union[float, str]] = ( - None # timeout when making stream=True calls, if str, pass in as os.environ/ - ) + timeout: Optional[ + Union[float, str, httpx.Timeout] + ] = None # if str, pass in as os.environ/ + stream_timeout: Optional[ + Union[float, str] + ] = None # timeout when making stream=True calls, if str, pass in as os.environ/ max_retries: Optional[int] = None organization: Optional[str] = None # for openai orgs configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None @@ -251,9 +249,9 @@ class GenericLiteLLMParams(CredentialLiteLLMParams): if max_retries is not None and isinstance(max_retries, str): max_retries = int(max_retries) # cast to int # We need to keep max_retries in args since it's a parameter of GenericLiteLLMParams - args["max_retries"] = ( - max_retries # Put max_retries back in args after popping it - ) + args[ + "max_retries" + ] = max_retries # Put max_retries back in args after popping it super().__init__(**args, **params) def __contains__(self, key): @@ -577,7 +575,6 @@ class AssistantsTypedDict(TypedDict): class FineTuningConfig(BaseModel): - custom_llm_provider: Literal["azure", "openai"] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index fe6330f8bd..7f84a41cd5 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -482,7 +482,6 @@ from openai.types.chat.chat_completion_audio import ChatCompletionAudio class ChatCompletionAudioResponse(ChatCompletionAudio): - def __init__( self, data: str, @@ -927,7 +926,6 @@ class StreamingChoices(OpenAIObject): self.finish_reason = None self.index = index if delta is not None: - if isinstance(delta, Delta): self.delta = delta elif isinstance(delta, dict): @@ -961,7 +959,6 @@ class StreamingChoices(OpenAIObject): class StreamingChatCompletionChunk(OpenAIChatCompletionChunk): def __init__(self, **kwargs): - new_choices = [] for choice in kwargs["choices"]: new_choice = StreamingChoices(**choice).model_dump() diff --git a/litellm/utils.py b/litellm/utils.py index 3c8b6667f9..777352ed34 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -482,7 +482,6 @@ def get_dynamic_callbacks( def function_setup( # noqa: PLR0915 original_function: str, rules_obj, start_time, *args, **kwargs ): # just run once to check if user wants to send their data anywhere - PostHog/Sentry/Slack/etc. - ### NOTICES ### from litellm import Logging as LiteLLMLogging from litellm.litellm_core_utils.litellm_logging import set_callbacks @@ -504,9 +503,9 @@ def function_setup( # noqa: PLR0915 function_id: Optional[str] = kwargs["id"] if "id" in kwargs else None ## DYNAMIC CALLBACKS ## - dynamic_callbacks: Optional[List[Union[str, Callable, CustomLogger]]] = ( - kwargs.pop("callbacks", None) - ) + dynamic_callbacks: Optional[ + List[Union[str, Callable, CustomLogger]] + ] = kwargs.pop("callbacks", None) all_callbacks = get_dynamic_callbacks(dynamic_callbacks=dynamic_callbacks) if len(all_callbacks) > 0: @@ -1190,9 +1189,9 @@ def client(original_function): # noqa: PLR0915 exception=e, retry_policy=kwargs.get("retry_policy"), ) - kwargs["retry_policy"] = ( - reset_retry_policy() - ) # prevent infinite loops + kwargs[ + "retry_policy" + ] = reset_retry_policy() # prevent infinite loops litellm.num_retries = ( None # set retries to None to prevent infinite loops ) @@ -1404,7 +1403,6 @@ def client(original_function): # noqa: PLR0915 if ( num_retries and not _is_litellm_router_call ): # only enter this if call is not from litellm router/proxy. router has it's own logic for retrying - try: litellm.num_retries = ( None # set retries to None to prevent infinite loops @@ -1425,7 +1423,6 @@ def client(original_function): # noqa: PLR0915 and context_window_fallback_dict and model in context_window_fallback_dict ): - if len(args) > 0: args[0] = context_window_fallback_dict[model] # type: ignore else: @@ -1521,7 +1518,6 @@ def _select_tokenizer( @lru_cache(maxsize=128) def _select_tokenizer_helper(model: str) -> SelectTokenizerResponse: - if litellm.disable_hf_tokenizer_download is True: return _return_openai_tokenizer(model) @@ -2990,16 +2986,16 @@ def get_optional_params( # noqa: PLR0915 True # so that main.py adds the function call to the prompt ) if "tools" in non_default_params: - optional_params["functions_unsupported_model"] = ( - non_default_params.pop("tools") - ) + optional_params[ + "functions_unsupported_model" + ] = non_default_params.pop("tools") non_default_params.pop( "tool_choice", None ) # causes ollama requests to hang elif "functions" in non_default_params: - optional_params["functions_unsupported_model"] = ( - non_default_params.pop("functions") - ) + optional_params[ + "functions_unsupported_model" + ] = non_default_params.pop("functions") elif ( litellm.add_function_to_prompt ): # if user opts to add it to prompt instead @@ -3022,10 +3018,10 @@ def get_optional_params( # noqa: PLR0915 if "response_format" in non_default_params: if provider_config is not None: - non_default_params["response_format"] = ( - provider_config.get_json_schema_from_pydantic_object( - response_format=non_default_params["response_format"] - ) + non_default_params[ + "response_format" + ] = provider_config.get_json_schema_from_pydantic_object( + response_format=non_default_params["response_format"] ) else: non_default_params["response_format"] = type_to_response_format_param( @@ -3177,7 +3173,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "replicate": - optional_params = litellm.ReplicateConfig().map_openai_params( non_default_params=non_default_params, optional_params=optional_params, @@ -3211,7 +3206,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "together_ai": - optional_params = litellm.TogetherAIConfig().map_openai_params( non_default_params=non_default_params, optional_params=optional_params, @@ -3279,7 +3273,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "vertex_ai": - if model in litellm.vertex_mistral_models: if "codestral" in model: optional_params = ( @@ -3358,7 +3351,6 @@ def get_optional_params( # noqa: PLR0915 elif "anthropic" in bedrock_base_model and bedrock_route == "invoke": if bedrock_base_model.startswith("anthropic.claude-3"): - optional_params = ( litellm.AmazonAnthropicClaude3Config().map_openai_params( non_default_params=non_default_params, @@ -3395,7 +3387,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "cloudflare": - optional_params = litellm.CloudflareChatConfig().map_openai_params( model=model, non_default_params=non_default_params, @@ -3407,7 +3398,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "ollama": - optional_params = litellm.OllamaConfig().map_openai_params( non_default_params=non_default_params, optional_params=optional_params, @@ -3419,7 +3409,6 @@ def get_optional_params( # noqa: PLR0915 ), ) elif custom_llm_provider == "ollama_chat": - optional_params = litellm.OllamaChatConfig().map_openai_params( model=model, non_default_params=non_default_params, @@ -4005,9 +3994,9 @@ def _count_characters(text: str) -> int: def get_response_string(response_obj: Union[ModelResponse, ModelResponseStream]) -> str: - _choices: Union[List[Union[Choices, StreamingChoices]], List[StreamingChoices]] = ( - response_obj.choices - ) + _choices: Union[ + List[Union[Choices, StreamingChoices]], List[StreamingChoices] + ] = response_obj.choices response_str = "" for choice in _choices: @@ -4405,7 +4394,6 @@ def _get_model_info_helper( # noqa: PLR0915 ): _model_info = None if _model_info is None and model in litellm.model_cost: - key = model _model_info = _get_model_info_from_model_cost(key=key) if not _check_provider_match( @@ -4416,7 +4404,6 @@ def _get_model_info_helper( # noqa: PLR0915 _model_info is None and combined_stripped_model_name in litellm.model_cost ): - key = combined_stripped_model_name _model_info = _get_model_info_from_model_cost(key=key) if not _check_provider_match( @@ -4424,7 +4411,6 @@ def _get_model_info_helper( # noqa: PLR0915 ): _model_info = None if _model_info is None and stripped_model_name in litellm.model_cost: - key = stripped_model_name _model_info = _get_model_info_from_model_cost(key=key) if not _check_provider_match( @@ -4432,7 +4418,6 @@ def _get_model_info_helper( # noqa: PLR0915 ): _model_info = None if _model_info is None and split_model in litellm.model_cost: - key = split_model _model_info = _get_model_info_from_model_cost(key=key) if not _check_provider_match( diff --git a/poetry.lock b/poetry.lock index 64e8eaa8ff..d659d66952 100644 --- a/poetry.lock +++ b/poetry.lock @@ -436,7 +436,7 @@ files = [ name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, @@ -658,7 +658,7 @@ cron = ["capturer (>=2.4)"] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, @@ -2563,7 +2563,7 @@ files = [ name = "pycparser" version = "2.22" description = "C parser in Python" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, @@ -3326,6 +3326,32 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, +] + [[package]] name = "s3transfer" version = "0.10.4" @@ -3603,6 +3629,111 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "types-cffi" +version = "1.16.0.20241221" +description = "Typing stubs for cffi" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_cffi-1.16.0.20241221-py3-none-any.whl", hash = "sha256:e5b76b4211d7a9185f6ab8d06a106d56c7eb80af7cdb8bfcb4186ade10fb112f"}, + {file = "types_cffi-1.16.0.20241221.tar.gz", hash = "sha256:1c96649618f4b6145f58231acb976e0b448be6b847f7ab733dabe62dfbff6591"}, +] + +[package.dependencies] +types-setuptools = "*" + +[[package]] +name = "types-pyopenssl" +version = "24.1.0.20240722" +description = "Typing stubs for pyOpenSSL" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"}, + {file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-cffi = "*" + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20241230" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"}, + {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"}, +] + +[[package]] +name = "types-redis" +version = "4.6.0.20241004" +description = "Typing stubs for redis" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"}, + {file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"}, +] + +[package.dependencies] +cryptography = ">=35.0.0" +types-pyOpenSSL = "*" + +[[package]] +name = "types-requests" +version = "2.31.0.6" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, + {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-requests" +version = "2.32.0.20241016" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-setuptools" +version = "75.8.0.20250110" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"}, + {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"}, +] + +[[package]] +name = "types-urllib3" +version = "1.26.25.14" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, + {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, +] + [[package]] name = "typing-extensions" version = "4.13.0" @@ -3993,4 +4124,4 @@ proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "0f195796116a7c7a4a04d9958a7662d74baccb5266f531bb58a4403fd4db4e0e" +content-hash = "36792478ff4afec5c8e748caf9b2ae6bebf3dd223e78bea2626b6589ef3277e4" diff --git a/pyproject.toml b/pyproject.toml index 2dbfcc39de..5eb4a71160 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ Documentation = "https://docs.litellm.ai" [tool.poetry.dependencies] python = ">=3.8.1,<4.0, !=3.9.7" httpx = ">=0.23.0" -openai = ">=1.66.1" +openai = ">=1.68.2" python-dotenv = ">=0.2.0" tiktoken = ">=0.7.0" importlib-metadata = ">=6.8.0" @@ -100,6 +100,11 @@ pytest = "^7.4.3" pytest-mock = "^3.12.0" pytest-asyncio = "^0.21.1" respx = "^0.20.2" +ruff = "^0.1.0" +types-requests = "*" +types-setuptools = "*" +types-redis = "*" +types-PyYAML = "*" [tool.poetry.group.proxy-dev.dependencies] prisma = "0.11.0" diff --git a/tests/litellm_utils_tests/test_hashicorp.py b/tests/litellm_utils_tests/test_hashicorp.py index 612af5a79c..c61168c99d 100644 --- a/tests/litellm_utils_tests/test_hashicorp.py +++ b/tests/litellm_utils_tests/test_hashicorp.py @@ -165,19 +165,26 @@ async def test_hashicorp_secret_manager_delete_secret(): ) -def test_hashicorp_secret_manager_tls_cert_auth(): - with patch("httpx.post") as mock_post: - # Configure the mock response for TLS auth - mock_auth_response = MagicMock() - mock_auth_response.json.return_value = { +def test_hashicorp_secret_manager_tls_cert_auth(monkeypatch): + monkeypatch.setenv("HCP_VAULT_TOKEN", "test-client-token-12345") + print("HCP_VAULT_TOKEN=", os.getenv("HCP_VAULT_TOKEN")) + # Mock both httpx.post and httpx.Client + with patch("httpx.Client") as mock_client: + # Configure the mock client and response + mock_response = MagicMock() + mock_response.json.return_value = { "auth": { "client_token": "test-client-token-12345", "lease_duration": 3600, "renewable": True, } } - mock_auth_response.raise_for_status.return_value = None - mock_post.return_value = mock_auth_response + mock_response.raise_for_status.return_value = None + + # Configure the mock client's post method + mock_client_instance = MagicMock() + mock_client_instance.post.return_value = mock_response + mock_client.return_value = mock_client_instance # Create a new instance with TLS cert config test_manager = HashicorpSecretManager() @@ -185,19 +192,22 @@ def test_hashicorp_secret_manager_tls_cert_auth(): test_manager.tls_key_path = "key.pem" test_manager.vault_cert_role = "test-role" test_manager.vault_namespace = "test-namespace" + # Test the TLS auth method token = test_manager._auth_via_tls_cert() - # Verify the token and request parameters + # Verify the token assert token == "test-client-token-12345" - mock_post.assert_called_once_with( + + # Verify Client was created with correct cert tuple + mock_client.assert_called_once_with(cert=("cert.pem", "key.pem")) + + # Verify post was called with correct parameters + mock_client_instance.post.assert_called_once_with( f"{test_manager.vault_addr}/v1/auth/cert/login", - cert=("cert.pem", "key.pem"), headers={"X-Vault-Namespace": "test-namespace"}, json={"name": "test-role"}, ) # Verify the token was cached - assert ( - test_manager.cache.get_cache("hcp_vault_token") == "test-client-token-12345" - ) + assert test_manager.cache.get_cache("hcp_vault_token") == "test-client-token-12345" From fe3623d7756d0648b45c0d825fc8413a08bd5bcb Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 11:26:18 -0700 Subject: [PATCH 192/244] init mcp client manager --- .../mcp_server/mcp_client_manager.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 litellm/proxy/_experimental/mcp_server/mcp_client_manager.py diff --git a/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py new file mode 100644 index 0000000000..c1a2310775 --- /dev/null +++ b/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py @@ -0,0 +1,89 @@ +""" +MCP Client Manager + +This class is responsible for managing MCP SSE clients. + +This is a Proxy +""" + +from typing import Any, Dict, List, Optional + +from mcp import ClientSession +from mcp.client.sse import sse_client +from pydantic import BaseModel, ConfigDict + +from litellm._logging import verbose_logger + + +class MCPSSEServer(BaseModel): + name: str + url: str + client_session: Optional[ClientSession] = None + + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class MCPServerManager: + def __init__(self, mcp_servers: List[MCPSSEServer]): + self.mcp_servers: List[MCPSSEServer] = mcp_servers + """ + eg. + [ + { + "name": "zapier_mcp_server", + "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" + }, + { + "name": "google_drive_mcp_server", + "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" + } + ] + """ + + self.tool_name_to_mcp_server_name_mapping: Dict[str, str] = {} + """ + { + "gmail_send_email": "zapier_mcp_server", + } + """ + + async def list_tools(self): + """ + List all tools available in all the MCP Servers + """ + for server in self.mcp_servers: + async with sse_client(url=server.url) as (read, write): + async with ClientSession(read, write) as session: + server.client_session = session + await server.client_session.initialize() + list_tools_result = await server.client_session.list_tools() + verbose_logger.debug( + f"Tools from {server.name}: {list_tools_result}" + ) + for tool in list_tools_result.tools: + self.tool_name_to_mcp_server_name_mapping[tool.name] = ( + server.name + ) + + async def call_tool(self, name: str, arguments: Dict[str, Any]): + """ + Call a tool with the given name and arguments + """ + mcp_server = self._get_mcp_server_from_tool_name(name) + if mcp_server is None: + raise ValueError(f"Tool {name} not found") + async with sse_client(url=mcp_server.url) as (read, write): + async with ClientSession(read, write) as session: + mcp_server.client_session = session + await mcp_server.client_session.initialize() + return await mcp_server.client_session.call_tool(name, arguments) + + def _get_mcp_server_from_tool_name(self, tool_name: str) -> Optional[MCPSSEServer]: + """ + Get the MCP Server from the tool name + """ + if tool_name in self.tool_name_to_mcp_server_name_mapping: + for server in self.mcp_servers: + if server.name == self.tool_name_to_mcp_server_name_mapping[tool_name]: + return server + return None From aa2489d74fc5968c7be9add11d9e064170a8edde Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Mar 2025 11:36:51 -0700 Subject: [PATCH 193/244] build(.gitignore): update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index aac1a61ddc..e599411328 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ litellm/proxy/migrations/0_init/migration.sql litellm/proxy/db/migrations/0_init/migration.sql litellm/proxy/db/migrations/* litellm/proxy/migrations/*config.yaml +litellm/proxy/migrations/* From ec283f7289683017e037c6c19b21d3f3b02aaaca Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 12:46:13 -0700 Subject: [PATCH 194/244] use global_mcp_server_manager --- .../mcp_server/mcp_client_manager.py | 89 ------------------- litellm/proxy/proxy_server.py | 7 ++ 2 files changed, 7 insertions(+), 89 deletions(-) delete mode 100644 litellm/proxy/_experimental/mcp_server/mcp_client_manager.py diff --git a/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py deleted file mode 100644 index c1a2310775..0000000000 --- a/litellm/proxy/_experimental/mcp_server/mcp_client_manager.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -MCP Client Manager - -This class is responsible for managing MCP SSE clients. - -This is a Proxy -""" - -from typing import Any, Dict, List, Optional - -from mcp import ClientSession -from mcp.client.sse import sse_client -from pydantic import BaseModel, ConfigDict - -from litellm._logging import verbose_logger - - -class MCPSSEServer(BaseModel): - name: str - url: str - client_session: Optional[ClientSession] = None - - model_config = ConfigDict(arbitrary_types_allowed=True) - - -class MCPServerManager: - def __init__(self, mcp_servers: List[MCPSSEServer]): - self.mcp_servers: List[MCPSSEServer] = mcp_servers - """ - eg. - [ - { - "name": "zapier_mcp_server", - "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" - }, - { - "name": "google_drive_mcp_server", - "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" - } - ] - """ - - self.tool_name_to_mcp_server_name_mapping: Dict[str, str] = {} - """ - { - "gmail_send_email": "zapier_mcp_server", - } - """ - - async def list_tools(self): - """ - List all tools available in all the MCP Servers - """ - for server in self.mcp_servers: - async with sse_client(url=server.url) as (read, write): - async with ClientSession(read, write) as session: - server.client_session = session - await server.client_session.initialize() - list_tools_result = await server.client_session.list_tools() - verbose_logger.debug( - f"Tools from {server.name}: {list_tools_result}" - ) - for tool in list_tools_result.tools: - self.tool_name_to_mcp_server_name_mapping[tool.name] = ( - server.name - ) - - async def call_tool(self, name: str, arguments: Dict[str, Any]): - """ - Call a tool with the given name and arguments - """ - mcp_server = self._get_mcp_server_from_tool_name(name) - if mcp_server is None: - raise ValueError(f"Tool {name} not found") - async with sse_client(url=mcp_server.url) as (read, write): - async with ClientSession(read, write) as session: - mcp_server.client_session = session - await mcp_server.client_session.initialize() - return await mcp_server.client_session.call_tool(name, arguments) - - def _get_mcp_server_from_tool_name(self, tool_name: str) -> Optional[MCPSSEServer]: - """ - Get the MCP Server from the tool name - """ - if tool_name in self.tool_name_to_mcp_server_name_mapping: - for server in self.mcp_servers: - if server.name == self.tool_name_to_mcp_server_name_mapping[tool_name]: - return server - return None diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 89c15f413d..1a469d2c27 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -127,6 +127,9 @@ from litellm.litellm_core_utils.core_helpers import ( from litellm.litellm_core_utils.credential_accessor import CredentialAccessor from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler +from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, +) from litellm.proxy._experimental.mcp_server.server import router as mcp_router from litellm.proxy._experimental.mcp_server.tool_registry import ( global_mcp_tool_registry, @@ -1961,6 +1964,10 @@ class ProxyConfig: if mcp_tools_config: global_mcp_tool_registry.load_tools_from_config(mcp_tools_config) + mcp_servers_config = config.get("mcp_servers", None) + if mcp_servers_config: + global_mcp_server_manager.load_servers_from_config(mcp_servers_config) + ## CREDENTIALS credential_list_dict = self.load_credential_list(config=config) litellm.credential_list = credential_list_dict From 6aa660b3f5ffef27faad0488dddb289a53080e23 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 12:51:16 -0700 Subject: [PATCH 195/244] mcp server manager --- .../mcp_server/mcp_server_manager.py | 125 ++++++++++++++++++ .../proxy/_experimental/mcp_server/server.py | 51 ++++++- .../types/mcp_server/mcp_server_manager.py | 11 ++ 3 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 litellm/proxy/_experimental/mcp_server/mcp_server_manager.py create mode 100644 litellm/types/mcp_server/mcp_server_manager.py diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py new file mode 100644 index 0000000000..344fb39f79 --- /dev/null +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -0,0 +1,125 @@ +""" +MCP Client Manager + +This class is responsible for managing MCP SSE clients. + +This is a Proxy +""" + +import json +from typing import Any, Dict, List, Optional + +from mcp import ClientSession +from mcp.client.sse import sse_client +from mcp.types import Tool as MCPTool + +from litellm._logging import verbose_logger +from litellm.types.mcp_server.mcp_server_manager import MCPSSEServer + + +class MCPServerManager: + def __init__(self): + self.mcp_servers: List[MCPSSEServer] = [] + """ + eg. + [ + { + "name": "zapier_mcp_server", + "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" + }, + { + "name": "google_drive_mcp_server", + "url": "https://actions.zapier.com/mcp/sk-ak-2ew3bofIeQIkNoeKIdXrF1Hhhp/sse" + } + ] + """ + + self.tool_name_to_mcp_server_name_mapping: Dict[str, str] = {} + """ + { + "gmail_send_email": "zapier_mcp_server", + } + """ + + def load_servers_from_config(self, mcp_servers_config: Dict[str, Any]): + """ + Load the MCP Servers from the config + """ + for server_name, server_config in mcp_servers_config.items(): + self.mcp_servers.append( + MCPSSEServer( + name=server_name, + url=server_config["url"], + ) + ) + verbose_logger.debug( + f"Loaded MCP Servers: {json.dumps(self.mcp_servers, indent=4, default=str)}" + ) + + async def list_tools(self) -> List[MCPTool]: + """ + List all tools available across all MCP Servers. + + Returns: + List[MCPTool]: Combined list of tools from all servers + """ + list_tools_result: List[MCPTool] = [] + verbose_logger.debug("SSE SERVER MANAGER LISTING TOOLS") + + for server in self.mcp_servers: + tools = await self._get_tools_from_server(server) + list_tools_result.extend(tools) + + return list_tools_result + + async def _get_tools_from_server(self, server: MCPSSEServer) -> List[MCPTool]: + """ + Helper method to get tools from a single MCP server. + + Args: + server (MCPSSEServer): The server to query tools from + + Returns: + List[MCPTool]: List of tools available on the server + """ + verbose_logger.debug(f"Connecting to url: {server.url}") + + async with sse_client(url=server.url) as (read, write): + async with ClientSession(read, write) as session: + server.client_session = session + await server.client_session.initialize() + + tools_result = await server.client_session.list_tools() + verbose_logger.debug(f"Tools from {server.name}: {tools_result}") + + # Update tool to server mapping + for tool in tools_result.tools: + self.tool_name_to_mcp_server_name_mapping[tool.name] = server.name + + return tools_result.tools + + async def call_tool(self, name: str, arguments: Dict[str, Any]): + """ + Call a tool with the given name and arguments + """ + mcp_server = self._get_mcp_server_from_tool_name(name) + if mcp_server is None: + raise ValueError(f"Tool {name} not found") + async with sse_client(url=mcp_server.url) as (read, write): + async with ClientSession(read, write) as session: + mcp_server.client_session = session + await mcp_server.client_session.initialize() + return await mcp_server.client_session.call_tool(name, arguments) + + def _get_mcp_server_from_tool_name(self, tool_name: str) -> Optional[MCPSSEServer]: + """ + Get the MCP Server from the tool name + """ + if tool_name in self.tool_name_to_mcp_server_name_mapping: + for server in self.mcp_servers: + if server.name == self.tool_name_to_mcp_server_name_mapping[tool_name]: + return server + return None + + +global_mcp_server_manager: MCPServerManager = MCPServerManager() diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index f617312d5a..a1de700d50 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -36,6 +36,7 @@ if MCP_AVAILABLE: from mcp.types import TextContent as MCPTextContent from mcp.types import Tool as MCPTool + from .mcp_server_manager import global_mcp_server_manager from .sse_transport import SseServerTransport from .tool_registry import global_mcp_tool_registry @@ -67,24 +68,64 @@ if MCP_AVAILABLE: inputSchema=tool.input_schema, ) ) - + verbose_logger.debug( + "GLOBAL MCP TOOLS: %s", global_mcp_tool_registry.list_tools() + ) + sse_tools: List[MCPTool] = await global_mcp_server_manager.list_tools() + verbose_logger.debug("SSE TOOLS: %s", sse_tools) + if sse_tools is not None: + tools.extend(sse_tools) return tools @server.call_tool() - async def handle_call_tool( + async def mcp_server_tool_call( name: str, arguments: Dict[str, Any] | None ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: """ Call a specific tool with the provided arguments + + Args: + name (str): Name of the tool to call + arguments (Dict[str, Any] | None): Arguments to pass to the tool + + Returns: + List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: Tool execution results + + Raises: + HTTPException: If tool not found or arguments missing """ - tool = global_mcp_tool_registry.get_tool(name) - if not tool: - raise HTTPException(status_code=404, detail=f"Tool '{name}' not found") + # Validate arguments if arguments is None: raise HTTPException( status_code=400, detail="Request arguments are required" ) + # Try managed server tool first + if name in global_mcp_server_manager.tool_name_to_mcp_server_name_mapping: + return await _handle_managed_mcp_tool(name, arguments) + + # Fall back to local tool registry + return await _handle_local_mcp_tool(name, arguments) + + async def _handle_managed_mcp_tool( + name: str, arguments: Dict[str, Any] + ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: + """Handle tool execution for managed server tools""" + call_tool_result = await global_mcp_server_manager.call_tool( + name=name, + arguments=arguments, + ) + verbose_logger.debug("CALL TOOL RESULT: %s", call_tool_result) + return call_tool_result.content + + async def _handle_local_mcp_tool( + name: str, arguments: Dict[str, Any] + ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: + """Handle tool execution for local registry tools""" + tool = global_mcp_tool_registry.get_tool(name) + if not tool: + raise HTTPException(status_code=404, detail=f"Tool '{name}' not found") + try: result = tool.handler(**arguments) return [MCPTextContent(text=str(result), type="text")] diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py new file mode 100644 index 0000000000..a106557fb6 --- /dev/null +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -0,0 +1,11 @@ +from typing import Optional + +from mcp import ClientSession +from pydantic import BaseModel, ConfigDict + + +class MCPSSEServer(BaseModel): + name: str + url: str + client_session: Optional[ClientSession] = None + model_config = ConfigDict(arbitrary_types_allowed=True) From a1ec0dd0e2963bec022a2e0784013eea6b5abd34 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 12:52:46 -0700 Subject: [PATCH 196/244] add testing mcp server --- litellm/proxy/proxy_config.yaml | 14 +++-- tests/mcp_tests/test_mcp_server.py | 37 ++++++++++++ tests/pass_through_tests/test_mcp_routes.py | 66 ++++++++++++++++++--- 3 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 tests/mcp_tests/test_mcp_server.py diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 106003f996..9da28ff51f 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,7 +1,13 @@ model_list: - - model_name: fake-openai-endpoint + - model_name: gpt-4o litellm_params: - model: openai/fake - api_key: fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ + model: openai/gpt-4o +mcp_servers: + { + "Zapier MCP": { + "url": "os.environ/ZAPIER_MCP_SERVER_URL", + }, + } + +} \ No newline at end of file diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py new file mode 100644 index 0000000000..17abd1d6a5 --- /dev/null +++ b/tests/mcp_tests/test_mcp_server.py @@ -0,0 +1,37 @@ +# Create server parameters for stdio connection +import os +import sys +import pytest + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm.proxy._experimental.mcp_server.mcp_client_manager import ( + MCPServerManager, + MCPSSEServer, +) + + +MCP_SERVERS = [ + MCPSSEServer(name="zapier_mcp_server", url=os.environ.get("ZAPIER_MCP_SERVER_URL")), +] + +mcp_server_manager = MCPServerManager(mcp_servers=MCP_SERVERS) + + +@pytest.mark.asyncio +async def test_mcp_server_manager(): + tools = await mcp_server_manager.list_tools() + print("TOOLS FROM MCP SERVER MANAGER== ", tools) + + result = await mcp_server_manager.call_tool( + name="gmail_send_email", arguments={"body": "Test"} + ) + print("RESULT FROM CALLING TOOL FROM MCP SERVER MANAGER== ", result) + + +""" +TODO test with multiple MCP servers and calling a specific + +""" diff --git a/tests/pass_through_tests/test_mcp_routes.py b/tests/pass_through_tests/test_mcp_routes.py index 687efe6195..d496a430af 100644 --- a/tests/pass_through_tests/test_mcp_routes.py +++ b/tests/pass_through_tests/test_mcp_routes.py @@ -2,15 +2,24 @@ import asyncio import os +import pytest from langchain_mcp_adapters.tools import load_mcp_tools from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from mcp import ClientSession from mcp.client.sse import sse_client +from litellm.experimental_mcp_client.tools import ( + transform_mcp_tool_to_openai_tool, + _transform_openai_tool_call_to_mcp_tool_call_request, +) +import json -async def main(): - model = ChatOpenAI(model="gpt-4o", api_key="sk-12") +@pytest.mark.asyncio +async def test_mcp_routes(): + model = ChatOpenAI( + model="gpt-4o", api_key="sk-1234", base_url="http://localhost:4000" + ) async with sse_client(url="http://localhost:4000/mcp/") as (read, write): async with ClientSession(read, write) as session: @@ -25,11 +34,52 @@ async def main(): print("Tools loaded") print(tools) - # # Create and run the agent - # agent = create_react_agent(model, tools) - # agent_response = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"}) + # Create and run the agent + agent = create_react_agent(model, tools) + agent_response = await agent.ainvoke({"messages": "Send an "}) + print(agent_response) -# Run the async function -if __name__ == "__main__": - asyncio.run(main()) +@pytest.mark.asyncio +async def test_mcp_routes_with_vertex_ai(): + # Create and run the agent + from openai import AsyncOpenAI + + openai_client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") + async with sse_client(url="http://localhost:4000/mcp/") as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + MCP_TOOLS = await session.list_tools() + + print("MCP TOOLS from litellm proxy: ", MCP_TOOLS) + messages = [ + { + "role": "user", + "content": "send an email about litellm supporting MCP and send it to krrish@berri.ai", + } + ] + llm_response = await openai_client.chat.completions.create( + model="gpt-4o", + messages=messages, + tools=[ + transform_mcp_tool_to_openai_tool(tool) for tool in MCP_TOOLS.tools + ], + tool_choice="required", + ) + print("LLM RESPONSE: ", json.dumps(llm_response, indent=4, default=str)) + + # Add assertions to verify the response + openai_tool = llm_response.choices[0].message.tool_calls[0] + + # Call the tool using MCP client + mcp_tool_call_request = ( + _transform_openai_tool_call_to_mcp_tool_call_request( + openai_tool.model_dump() + ) + ) + call_result = await session.call_tool( + name=mcp_tool_call_request.name, + arguments=mcp_tool_call_request.arguments, + ) + print("CALL RESULT: ", call_result) + pass From 1cf5cbab5099522f092d54da4d119f05638e5830 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 13:35:46 -0700 Subject: [PATCH 197/244] REST API endpoint for MCP --- .../mcp_server/mcp_server_manager.py | 1 + .../proxy/_experimental/mcp_server/server.py | 82 ++++++++++++++++++- litellm/proxy/proxy_config.yaml | 11 +-- .../types/mcp_server/mcp_server_manager.py | 14 +++- 4 files changed, 100 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 344fb39f79..aa99a11318 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -50,6 +50,7 @@ class MCPServerManager: MCPSSEServer( name=server_name, url=server_config["url"], + mcp_info=server_config.get("mcp_info", None), ) ) verbose_logger.debug( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index a1de700d50..4fc91342c2 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -6,11 +6,15 @@ import asyncio from typing import Any, Dict, List, Union from anyio import BrokenResourceError -from fastapi import APIRouter, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import StreamingResponse from pydantic import ValidationError from litellm._logging import verbose_logger +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.types.mcp_server.mcp_server_manager import ( + ListMCPToolsRestAPIResponseObject, +) # Check if MCP is available # "mcp" requires python 3.10 or higher, but several litellm users use python 3.8 @@ -53,9 +57,14 @@ if MCP_AVAILABLE: ######################################################## ############### MCP Server Routes ####################### ######################################################## - @server.list_tools() async def list_tools() -> list[MCPTool]: + """ + List all available tools + """ + return await _list_mcp_tools() + + async def _list_mcp_tools() -> List[MCPTool]: """ List all available tools """ @@ -95,6 +104,18 @@ if MCP_AVAILABLE: HTTPException: If tool not found or arguments missing """ # Validate arguments + response = await call_mcp_tool( + name=name, + arguments=arguments, + ) + return response + + async def call_mcp_tool( + name: str, arguments: Dict[str, Any] | None + ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: + """ + Call a specific tool with the provided arguments + """ if arguments is None: raise HTTPException( status_code=400, detail="Request arguments are required" @@ -154,6 +175,63 @@ if MCP_AVAILABLE: await sse.handle_post_message(request.scope, request.receive, request._send) await request.close() + ######################################################## + ############ MCP Server REST API Routes ################# + ######################################################## + @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) + async def list_tool_rest_api() -> ( + List[Dict[str, ListMCPToolsRestAPIResponseObject]] + ): + """ + List all available tools with information about the server they belong to. + + Example response: + Tools: + [ + "zapier": { + "tools": [ + { + "name": "create_zap", + "description": "Create a new zap", + "inputSchema": "tool_input_schema", + } + ], + "mcp_info": { + "logo_url": "https://www.zapier.com/logo.png", + } + }, + "fetch": { + "tools": [ + { + "name": "fetch_data", + "description": "Fetch data from a URL", + } + ], + "mcp_info": { + "logo_url": "https://www.fetch.com/logo.png", + } + } + """ + list_tools_result: List[Dict[str, ListMCPToolsRestAPIResponseObject]] = [] + for server in global_mcp_server_manager.mcp_servers: + tools = await global_mcp_server_manager._get_tools_from_server(server) + list_tools_result.append( + { + server.name: ListMCPToolsRestAPIResponseObject( + tools=tools, + mcp_info=server.mcp_info, + ) + } + ) + return list_tools_result + + @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) + async def call_tool_rest_api(name: str, arguments: Dict[str, Any]): + return await call_mcp_tool( + name=name, + arguments=arguments, + ) + options = InitializationOptions( server_name="litellm-mcp-server", server_version="0.1.0", diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 9da28ff51f..bffb39a1b8 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -5,9 +5,10 @@ model_list: mcp_servers: { - "Zapier MCP": { - "url": "os.environ/ZAPIER_MCP_SERVER_URL", - }, + "Zapier MCP": { + "url": "os.environ/ZAPIER_MCP_SERVER_URL", + "mcp_info": { + "logo_url": "https://www.zapier.com/logo.png", + } + } } - -} \ No newline at end of file diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index a106557fb6..0996e62b3a 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -1,6 +1,7 @@ -from typing import Optional +from typing import Any, Dict, List, Optional from mcp import ClientSession +from mcp.types import Tool as MCPTool from pydantic import BaseModel, ConfigDict @@ -8,4 +9,15 @@ class MCPSSEServer(BaseModel): name: str url: str client_session: Optional[ClientSession] = None + mcp_info: Optional[Dict[str, Any]] = None + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class ListMCPToolsRestAPIResponseObject(BaseModel): + """ + Object returned by the /tools/list REST API route. + """ + + tools: List[MCPTool] + mcp_info: Optional[Dict[str, Any]] = None model_config = ConfigDict(arbitrary_types_allowed=True) From b381dde9ac52d1010e7c09b8f7cad4662a52d9b6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 14:30:34 -0700 Subject: [PATCH 198/244] basic UI rendering of MCP tools --- ui/litellm-dashboard/src/app/page.tsx | 8 + .../src/components/leftnav.tsx | 4 +- .../src/components/mcp_tools/columns.tsx | 306 ++++++++++++++++++ .../src/components/mcp_tools/types.tsx | 71 ++++ .../src/components/networking.tsx | 70 ++++ 5 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/columns.tsx create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/types.tsx diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index f480501b58..a4256b3f4b 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -32,6 +32,8 @@ import GuardrailsPanel from "@/components/guardrails"; import TransformRequestPanel from "@/components/transform_request"; import { fetchUserModels } from "@/components/create_key_button"; import { fetchTeams } from "@/components/common_components/fetch_teams"; +import MCPToolsViewer from "@/components/mcp_tools"; + function getCookie(name: string) { const cookieValue = document.cookie .split("; ") @@ -347,6 +349,12 @@ export default function CreateKeyPage() { accessToken={accessToken} allTeams={teams as Team[] ?? []} /> + ) : page == "mcp-tools" ? ( + ) : page == "new_usage" ? ( , roles: all_admin_roles }, { key: "11", page: "guardrails", label: "Guardrails", icon: , roles: all_admin_roles }, { key: "12", page: "new_usage", label: "New Usage", icon: , roles: all_admin_roles }, + { key: "18", page: "mcp-tools", label: "MCP Tools", icon: , roles: all_admin_roles }, ] }, { diff --git a/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx b/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx new file mode 100644 index 0000000000..5e347a7e1e --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx @@ -0,0 +1,306 @@ +import React from "react"; +import { ColumnDef } from "@tanstack/react-table"; +import { MCPTool, InputSchema } from "./types"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "mcp_info.server_name", + header: "Provider", + cell: ({ row }) => { + const serverName = row.original.mcp_info.server_name; + const logoUrl = row.original.mcp_info.logo_url; + + return ( +
+ {logoUrl && ( + {`${serverName} + )} + {serverName} +
+ ); + }, + }, + { + accessorKey: "name", + header: "Tool Name", + cell: ({ row }) => { + const name = row.getValue("name") as string; + return ( +
+ {name} +
+ ); + }, + }, + { + accessorKey: "description", + header: "Description", + cell: ({ row }) => { + const description = row.getValue("description") as string; + return ( +
+ {description} +
+ ); + }, + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + const tool = row.original; + + return ( +
+ +
+ ); + }, + }, +]; + +// Tool Panel component to display when a tool is selected +export function ToolTestPanel({ + tool, + onSubmit, + isLoading, + result, + error, + onClose +}: { + tool: MCPTool; + onSubmit: (args: Record) => void; + isLoading: boolean; + result: any | null; + error: Error | null; + onClose: () => void; +}) { + const [formState, setFormState] = React.useState>({}); + + // Create a placeholder schema if we only have the "tool_input_schema" string + const schema: InputSchema = React.useMemo(() => { + if (typeof tool.inputSchema === 'string') { + // Default schema with a single text field + return { + type: "object", + properties: { + input: { + type: "string", + description: "Input for this tool" + } + }, + required: ["input"] + }; + } + return tool.inputSchema as InputSchema; + }, [tool.inputSchema]); + + const handleInputChange = (key: string, value: any) => { + setFormState(prev => ({ + ...prev, + [key]: value + })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit(formState); + }; + + return ( +
+
+
+

Test Tool: {tool.name}

+

{tool.description}

+

Provider: {tool.mcp_info.server_name}

+
+ +
+ +
+ {/* Form Section */} +
+

Input Parameters

+
+ {typeof tool.inputSchema === 'string' ? ( +
+

This tool uses a dynamic input schema.

+
+ + handleInputChange("input", e.target.value)} + required + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> +
+
+ ) : ( + Object.entries(schema.properties).map(([key, prop]) => ( +
+ + {prop.description && ( +

{prop.description}

+ )} + + {/* Render appropriate input based on type */} + {prop.type === "string" && ( + handleInputChange(key, e.target.value)} + required={schema.required?.includes(key)} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> + )} + + {prop.type === "number" && ( + handleInputChange(key, parseFloat(e.target.value))} + required={schema.required?.includes(key)} + className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + /> + )} + + {prop.type === "boolean" && ( +
+ handleInputChange(key, e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + Enable +
+ )} +
+ )) + )} + +
+ +
+
+
+ + {/* Result Section */} +
+

Result

+ + {isLoading && ( +
+
+
+ )} + + {error && ( +
+

Error

+
{error.message}
+
+ )} + + {result && !isLoading && !error && ( +
+ {result.map((content: any, idx: number) => ( +
+ {content.type === "text" && ( +
+

{content.text}

+
+ )} + + {content.type === "image" && content.url && ( +
+ Tool result +
+ )} + + {content.type === "embedded_resource" && ( +
+

Embedded Resource

+

Type: {content.resource_type}

+ {content.url && ( + + View Resource + + )} +
+ )} +
+ ))} + +
+
+ Raw JSON Response +
+                    {JSON.stringify(result, null, 2)}
+                  
+
+
+
+ )} + + {!result && !isLoading && !error && ( +
+

The result will appear here after you call the tool.

+
+ )} +
+
+
+ ); +} \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/mcp_tools/types.tsx b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx new file mode 100644 index 0000000000..7bbb76fa23 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/types.tsx @@ -0,0 +1,71 @@ +// Define the structure for tool input schema properties +export interface InputSchemaProperty { + type: string; + description?: string; + } + + // Define the structure for the input schema of a tool + export interface InputSchema { + type: "object"; + properties: Record; + required?: string[]; + } + + // Define MCP provider info + export interface MCPInfo { + server_name: string; + logo_url?: string; + } + + // Define the structure for a single MCP tool + export interface MCPTool { + name: string; + description: string; + inputSchema: InputSchema | string; // API returns string "tool_input_schema" or the actual schema + mcp_info: MCPInfo; + // Function to select a tool (added in the component) + onToolSelect?: (tool: MCPTool) => void; + } + + // Define the response structure for the listMCPTools endpoint - now a flat array + export type ListMCPToolsResponse = MCPTool[]; + + // Define the argument structure for calling an MCP tool + export interface CallMCPToolArgs { + name: string; + arguments: Record | null; + server_name?: string; // Now using server_name from mcp_info + } + + // Define the possible content types in the response + export interface MCPTextContent { + type: "text"; + text: string; + annotations?: any; + } + + export interface MCPImageContent { + type: "image"; + url?: string; + data?: string; + } + + export interface MCPEmbeddedResource { + type: "embedded_resource"; + resource_type?: string; + url?: string; + data?: any; + } + + // Define the union type for the content array in the response + export type MCPContent = MCPTextContent | MCPImageContent | MCPEmbeddedResource; + + // Define the response structure for the callMCPTool endpoint + export type CallMCPToolResponse = MCPContent[]; + + // Props for the main component + export interface MCPToolsViewerProps { + accessToken: string | null; + userRole: string | null; + userID: string | null; + } \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 83d258e532..57a2dd3331 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4084,3 +4084,73 @@ export const updateInternalUserSettings = async (accessToken: string, settings: throw error; } }; + + +export const listMCPTools = async (accessToken: string) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/mcp/tools/list` + : `/mcp/tools/list`; + + console.log("Fetching MCP tools from:", url); + + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Fetched MCP tools:", data); + return data; + } catch (error) { + console.error("Failed to fetch MCP tools:", error); + throw error; + } +}; + + +export const callMCPTool = async (accessToken: string, toolName: string, toolArguments: Record) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/mcp/tools/call` + : `/mcp/tools/call`; + + console.log("Calling MCP tool:", toolName, "with arguments:", toolArguments); + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + tool_name: toolName, + tool_arguments: toolArguments, + }), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("MCP tool call response:", data); + return data; + } catch (error) { + console.error("Failed to call MCP tool:", error); + throw error; + } +}; \ No newline at end of file From e0cff7595cf7d82c3ac195a760cd92697ceb6999 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 14:31:35 -0700 Subject: [PATCH 199/244] endpoints to list and call tools --- .../mcp_server/mcp_server_manager.py | 7 +- .../proxy/_experimental/mcp_server/server.py | 45 +++--- litellm/proxy/proxy_config.yaml | 2 +- .../types/mcp_server/mcp_server_manager.py | 13 +- .../src/components/mcp_tools/index.tsx | 147 ++++++++++++++++++ 5 files changed, 182 insertions(+), 32 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/index.tsx diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index aa99a11318..a2b378fe09 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -14,7 +14,7 @@ from mcp.client.sse import sse_client from mcp.types import Tool as MCPTool from litellm._logging import verbose_logger -from litellm.types.mcp_server.mcp_server_manager import MCPSSEServer +from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPSSEServer class MCPServerManager: @@ -46,11 +46,14 @@ class MCPServerManager: Load the MCP Servers from the config """ for server_name, server_config in mcp_servers_config.items(): + _mcp_info: dict = server_config.get("mcp_info", None) or {} + mcp_info = MCPInfo(**_mcp_info) + mcp_info["server_name"] = server_name self.mcp_servers.append( MCPSSEServer( name=server_name, url=server_config["url"], - mcp_info=server_config.get("mcp_info", None), + mcp_info=mcp_info, ) ) verbose_logger.debug( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 4fc91342c2..698a556a01 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -179,50 +179,45 @@ if MCP_AVAILABLE: ############ MCP Server REST API Routes ################# ######################################################## @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) - async def list_tool_rest_api() -> ( - List[Dict[str, ListMCPToolsRestAPIResponseObject]] - ): + async def list_tool_rest_api() -> List[ListMCPToolsRestAPIResponseObject]: """ List all available tools with information about the server they belong to. Example response: Tools: [ - "zapier": { - "tools": [ - { - "name": "create_zap", - "description": "Create a new zap", - "inputSchema": "tool_input_schema", - } - ], + { + "name": "create_zap", + "description": "Create a new zap", + "inputSchema": "tool_input_schema", "mcp_info": { + "server_name": "zapier", "logo_url": "https://www.zapier.com/logo.png", } }, - "fetch": { - "tools": [ - { - "name": "fetch_data", - "description": "Fetch data from a URL", - } - ], + { + "name": "fetch_data", + "description": "Fetch data from a URL", + "inputSchema": "tool_input_schema", "mcp_info": { + "server_name": "fetch", "logo_url": "https://www.fetch.com/logo.png", } } + ] """ - list_tools_result: List[Dict[str, ListMCPToolsRestAPIResponseObject]] = [] + list_tools_result: List[ListMCPToolsRestAPIResponseObject] = [] for server in global_mcp_server_manager.mcp_servers: tools = await global_mcp_server_manager._get_tools_from_server(server) - list_tools_result.append( - { - server.name: ListMCPToolsRestAPIResponseObject( - tools=tools, + for tool in tools: + list_tools_result.append( + ListMCPToolsRestAPIResponseObject( + name=tool.name, + description=tool.description, + inputSchema=tool.inputSchema, mcp_info=server.mcp_info, ) - } - ) + ) return list_tools_result @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index bffb39a1b8..3956eab23f 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -8,7 +8,7 @@ mcp_servers: "Zapier MCP": { "url": "os.environ/ZAPIER_MCP_SERVER_URL", "mcp_info": { - "logo_url": "https://www.zapier.com/logo.png", + "logo_url": "https://espysys.com/wp-content/uploads/2024/08/zapier-logo.webp", } } } diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index 0996e62b3a..981926df3c 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -3,21 +3,26 @@ from typing import Any, Dict, List, Optional from mcp import ClientSession from mcp.types import Tool as MCPTool from pydantic import BaseModel, ConfigDict +from typing_extensions import TypedDict + + +class MCPInfo(TypedDict, total=False): + server_name: str + logo_url: Optional[str] class MCPSSEServer(BaseModel): name: str url: str client_session: Optional[ClientSession] = None - mcp_info: Optional[Dict[str, Any]] = None + mcp_info: Optional[MCPInfo] = None model_config = ConfigDict(arbitrary_types_allowed=True) -class ListMCPToolsRestAPIResponseObject(BaseModel): +class ListMCPToolsRestAPIResponseObject(MCPTool): """ Object returned by the /tools/list REST API route. """ - tools: List[MCPTool] - mcp_info: Optional[Dict[str, Any]] = None + mcp_info: Optional[MCPInfo] = None model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/index.tsx b/ui/litellm-dashboard/src/components/mcp_tools/index.tsx new file mode 100644 index 0000000000..9018752705 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/index.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import { DataTable } from '../view_logs/table'; +import { columns, ToolTestPanel } from './columns'; +import { MCPTool, MCPToolsViewerProps, CallMCPToolResponse } from './types'; +import { listMCPTools, callMCPTool } from '../networking'; + +export default function MCPToolsViewer({ + accessToken, + userRole, + userID, +}: MCPToolsViewerProps) { + const [searchTerm, setSearchTerm] = useState(''); + const [selectedTool, setSelectedTool] = useState(null); + const [toolResult, setToolResult] = useState(null); + const [toolError, setToolError] = useState(null); + + // Query to fetch MCP tools + const { data: mcpTools, isLoading: isLoadingTools } = useQuery({ + queryKey: ['mcpTools'], + queryFn: () => { + if (!accessToken) throw new Error('Access Token required'); + return listMCPTools(accessToken); + }, + enabled: !!accessToken, + }); + + // Mutation for calling a tool + const { mutate: executeTool, isPending: isCallingTool } = useMutation({ + mutationFn: (args: { tool: MCPTool; arguments: Record }) => { + if (!accessToken) throw new Error('Access Token required'); + return callMCPTool( + accessToken, + args.tool.name, + args.arguments + ); + }, + onSuccess: (data) => { + setToolResult(data); + setToolError(null); + }, + onError: (error: Error) => { + setToolError(error); + setToolResult(null); + }, + }); + + // Add onToolSelect handler to each tool + const toolsData = React.useMemo(() => { + if (!mcpTools) return []; + + return mcpTools.map(tool => ({ + ...tool, + onToolSelect: (tool: MCPTool) => { + setSelectedTool(tool); + setToolResult(null); + setToolError(null); + } + })); + }, [mcpTools]); + + // Filter tools based on search term + const filteredTools = React.useMemo(() => { + return toolsData.filter(tool => { + const searchLower = searchTerm.toLowerCase(); + return ( + tool.name.toLowerCase().includes(searchLower) || + tool.description.toLowerCase().includes(searchLower) || + tool.mcp_info.server_name.toLowerCase().includes(searchLower) + ); + }); + }, [toolsData, searchTerm]); + + // Handle tool call submission + const handleToolSubmit = (args: Record) => { + if (!selectedTool) return; + + executeTool({ + tool: selectedTool, + arguments: args, + }); + }; + + if (!accessToken || !userRole || !userID) { + return
Missing required authentication parameters.
; + } + + return ( +
+
+

MCP Tools

+
+ +
+
+
+
+ setSearchTerm(e.target.value)} + /> + + + +
+
+ {filteredTools.length} tool{filteredTools.length !== 1 ? "s" : ""} available +
+
+
+ + +
+ + {/* Tool Test Panel - Show when a tool is selected */} + {selectedTool && ( +
+ setSelectedTool(null)} + /> +
+ )} +
+ ); +} \ No newline at end of file From a4a083041dad4ea0a7994ce8d9597dcf6b55479e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 14:42:19 -0700 Subject: [PATCH 200/244] working MCP call tool method --- litellm/proxy/_experimental/mcp_server/server.py | 5 ++++- ui/litellm-dashboard/src/components/networking.tsx | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 698a556a01..3e94576efb 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -221,7 +221,10 @@ if MCP_AVAILABLE: return list_tools_result @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) - async def call_tool_rest_api(name: str, arguments: Dict[str, Any]): + async def call_tool_rest_api(request: Request): + data = await request.json() + name = data.get("name") + arguments = data.get("arguments") return await call_mcp_tool( name=name, arguments=arguments, diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 57a2dd3331..9190319cc9 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4135,8 +4135,8 @@ export const callMCPTool = async (accessToken: string, toolName: string, toolArg "Content-Type": "application/json", }, body: JSON.stringify({ - tool_name: toolName, - tool_arguments: toolArguments, + name: toolName, + arguments: toolArguments, }), }); From b7b9f9d9dae099a0d1a55cb2947d30e15de53dca Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 15:20:52 -0700 Subject: [PATCH 201/244] working MCP tool call logging --- litellm/litellm_core_utils/litellm_logging.py | 2 +- .../mcp_server/mcp_server_manager.py | 26 +++++++++++++++++ .../proxy/_experimental/mcp_server/server.py | 28 +++++++++++++------ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 3565c4468c..8e83aea69a 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1099,7 +1099,7 @@ class Logging(LiteLLMLoggingBaseClass): standard_built_in_tools_params=self.standard_built_in_tools_params, ) ) - elif isinstance(result, dict): # pass-through endpoints + elif isinstance(result, dict) or isinstance(result, list): ## STANDARDIZED LOGGING PAYLOAD self.model_call_details["standard_logging_object"] = ( get_standard_logging_object_payload( diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index a2b378fe09..c73eabd629 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -6,6 +6,7 @@ This class is responsible for managing MCP SSE clients. This is a Proxy """ +import asyncio import json from typing import Any, Dict, List, Optional @@ -60,6 +61,8 @@ class MCPServerManager: f"Loaded MCP Servers: {json.dumps(self.mcp_servers, indent=4, default=str)}" ) + self.initialize_tool_name_to_mcp_server_name_mapping() + async def list_tools(self) -> List[MCPTool]: """ List all tools available across all MCP Servers. @@ -102,6 +105,29 @@ class MCPServerManager: return tools_result.tools + def initialize_tool_name_to_mcp_server_name_mapping(self): + """ + On startup, initialize the tool name to MCP server name mapping + """ + try: + if asyncio.get_running_loop(): + asyncio.create_task( + self._initialize_tool_name_to_mcp_server_name_mapping() + ) + except RuntimeError as e: # no running event loop + verbose_logger.exception( + f"No running event loop - skipping tool name to MCP server name mapping initialization: {str(e)}" + ) + + async def _initialize_tool_name_to_mcp_server_name_mapping(self): + """ + Call list_tools for each server and update the tool name to MCP server name mapping + """ + for server in self.mcp_servers: + tools = await self._get_tools_from_server(server) + for tool in tools: + self.tool_name_to_mcp_server_name_mapping[tool.name] = server.name + async def call_tool(self, name: str, arguments: Dict[str, Any]): """ Call a tool with the given name and arguments diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 3e94576efb..32c33904a3 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -3,7 +3,7 @@ LiteLLM MCP Server Routes """ import asyncio -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Optional, Union from anyio import BrokenResourceError from fastapi import APIRouter, Depends, HTTPException, Request @@ -11,10 +11,12 @@ from fastapi.responses import StreamingResponse from pydantic import ValidationError from litellm._logging import verbose_logger +from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.mcp_server.mcp_server_manager import ( ListMCPToolsRestAPIResponseObject, ) +from litellm.utils import client # Check if MCP is available # "mcp" requires python 3.10 or higher, but several litellm users use python 3.8 @@ -110,8 +112,9 @@ if MCP_AVAILABLE: ) return response + @client async def call_mcp_tool( - name: str, arguments: Dict[str, Any] | None + name: str, arguments: Optional[Dict[str, Any]] = None, **kwargs: Any ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: """ Call a specific tool with the provided arguments @@ -221,14 +224,23 @@ if MCP_AVAILABLE: return list_tools_result @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) - async def call_tool_rest_api(request: Request): + async def call_tool_rest_api( + request: Request, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + ): + """ + REST API to call a specific MCP tool with the provided arguments + """ + from litellm.proxy.proxy_server import add_litellm_data_to_request, proxy_config + data = await request.json() - name = data.get("name") - arguments = data.get("arguments") - return await call_mcp_tool( - name=name, - arguments=arguments, + data = await add_litellm_data_to_request( + data=data, + request=request, + user_api_key_dict=user_api_key_dict, + proxy_config=proxy_config, ) + return await call_mcp_tool(**data) options = InitializationOptions( server_name="litellm-mcp-server", From 1604f87663f7d6081e99a266b241538bf25ca29f Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Sat, 29 Mar 2025 15:27:09 -0700 Subject: [PATCH 202/244] install prisma migration files - connects litellm proxy to litellm's prisma migration files (#9637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(README.md): initial commit adding a separate folder for additional proxy files. Meant to reduce size of core package * build(litellm-proxy-extras/): new pip package for storing migration files allows litellm proxy to use migration files, without adding them to core repo * build(litellm-proxy-extras/): cleanup pyproject.toml * build: move prisma migration files inside new proxy extras package * build(run_migration.py): update script to write to correct folder * build(proxy_cli.py): load in migration files from litellm-proxy-extras Closes https://github.com/BerriAI/litellm/issues/9558 * build: add MIT license to litellm-proxy-extras * test: update test * fix: fix schema * bump: version 0.1.0 → 0.1.1 * build(publish-proxy-extras.sh): add script for publishing new proxy-extras version * build(liccheck.ini): add litellm-proxy-extras to authorized packages * fix(litellm-proxy-extras/utils.py): move prisma migrate logic inside extra proxy pkg easier since migrations folder already there * build(pre-commit-config.yaml): add litellm_proxy_extras to ci tests * docs(config_settings.md): document new env var * build(pyproject.toml): bump relevant files when litellm-proxy-extras version changed * build(pre-commit-config.yaml): run poetry check on litellm-proxy-extras as well --- .pre-commit-config.yaml | 23 ++--- ci_cd/publish-proxy-extras.sh | 19 ++++ ci_cd/run_migration.py | 5 +- docs/my-website/docs/proxy/config_settings.md | 1 + litellm-proxy-extras/LICENSE | 26 ++++++ litellm-proxy-extras/README.md | 21 +++++ ...itellm_proxy_extras-0.1.0-py3-none-any.whl | Bin 0 -> 5320 bytes .../dist/litellm_proxy_extras-0.1.0.tar.gz | Bin 0 -> 3390 bytes ...itellm_proxy_extras-0.1.1-py3-none-any.whl | Bin 0 -> 6307 bytes .../dist/litellm_proxy_extras-0.1.1.tar.gz | Bin 0 -> 4245 bytes .../litellm_proxy_extras/__init__.py | 0 .../litellm_proxy_extras/_logging.py | 12 +++ .../20250326162113_baseline/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migrations/migration_lock.toml | 0 .../litellm_proxy_extras/utils.py | 80 +++++++++++++++++ litellm-proxy-extras/pyproject.toml | 30 +++++++ litellm-proxy-extras/tests/__init__.py | 0 litellm/proxy/db/prisma_client.py | 81 +++--------------- litellm/proxy/proxy_cli.py | 2 +- poetry.lock | 15 +++- pyproject.toml | 4 +- requirements.txt | 1 + tests/code_coverage_tests/liccheck.ini | 3 +- .../test_db_schema_migration.py | 2 +- 27 files changed, 229 insertions(+), 96 deletions(-) create mode 100644 ci_cd/publish-proxy-extras.sh create mode 100644 litellm-proxy-extras/LICENSE create mode 100644 litellm-proxy-extras/README.md create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.1.0-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.1.0.tar.gz create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.1.1-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.1.1.tar.gz create mode 100644 litellm-proxy-extras/litellm_proxy_extras/__init__.py create mode 100644 litellm-proxy-extras/litellm_proxy_extras/_logging.py rename {deploy => litellm-proxy-extras/litellm_proxy_extras}/migrations/20250326162113_baseline/migration.sql (100%) rename {deploy => litellm-proxy-extras/litellm_proxy_extras}/migrations/20250326171002_add_daily_user_table/migration.sql (100%) rename {deploy => litellm-proxy-extras/litellm_proxy_extras}/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql (100%) rename {deploy => litellm-proxy-extras/litellm_proxy_extras}/migrations/20250329084805_new_cron_job_table/migration.sql (100%) rename {deploy => litellm-proxy-extras/litellm_proxy_extras}/migrations/migration_lock.toml (100%) create mode 100644 litellm-proxy-extras/litellm_proxy_extras/utils.py create mode 100644 litellm-proxy-extras/pyproject.toml create mode 100644 litellm-proxy-extras/tests/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bceedb41aa..dedb37d6dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,46 +6,35 @@ repos: entry: pyright language: system types: [python] - files: ^litellm/ + files: ^(litellm/|litellm_proxy_extras/) - id: isort name: isort entry: isort language: system types: [python] - files: litellm/.*\.py + files: (litellm/|litellm_proxy_extras/).*\.py exclude: ^litellm/__init__.py$ - id: black name: black entry: poetry run black language: system types: [python] - files: litellm/.*\.py + files: (litellm/|litellm_proxy_extras/).*\.py - repo: https://github.com/pycqa/flake8 rev: 7.0.0 # The version of flake8 to use hooks: - id: flake8 exclude: ^litellm/tests/|^litellm/proxy/tests/|^litellm/tests/litellm/|^tests/litellm/ additional_dependencies: [flake8-print] - files: litellm/.*\.py - # - id: flake8 - # name: flake8 (router.py function length) - # files: ^litellm/router\.py$ - # args: [--max-function-length=40] - # # additional_dependencies: [flake8-functions] + files: (litellm/|litellm_proxy_extras/).*\.py - repo: https://github.com/python-poetry/poetry rev: 1.8.0 hooks: - id: poetry-check + files: ^(pyproject.toml|litellm-proxy-extras/pyproject.toml)$ - repo: local hooks: - id: check-files-match name: Check if files match entry: python3 ci_cd/check_files_match.py - language: system - # - id: check-file-length - # name: Check file length - # entry: python check_file_length.py - # args: ["10000"] # set your desired maximum number of lines - # language: python - # files: litellm/.*\.py - # exclude: ^litellm/tests/ \ No newline at end of file + language: system \ No newline at end of file diff --git a/ci_cd/publish-proxy-extras.sh b/ci_cd/publish-proxy-extras.sh new file mode 100644 index 0000000000..6c83d1f921 --- /dev/null +++ b/ci_cd/publish-proxy-extras.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Exit on error +set -e + +echo "🚀 Building and publishing litellm-proxy-extras" + +# Navigate to litellm-proxy-extras directory +cd "$(dirname "$0")/../litellm-proxy-extras" + +# Build the package +echo "📦 Building package..." +poetry build + +# Publish to PyPI +echo "🌎 Publishing to PyPI..." +poetry publish + +echo "✅ Done! Package published successfully" \ No newline at end of file diff --git a/ci_cd/run_migration.py b/ci_cd/run_migration.py index 69b4308e57..b11a38395c 100644 --- a/ci_cd/run_migration.py +++ b/ci_cd/run_migration.py @@ -8,7 +8,7 @@ import shutil def create_migration(migration_name: str = None): """ - Create a new migration SQL file in deploy/migrations directory by comparing + Create a new migration SQL file in the migrations directory by comparing current database state with schema Args: @@ -17,8 +17,7 @@ def create_migration(migration_name: str = None): try: # Get paths root_dir = Path(__file__).parent.parent - deploy_dir = root_dir / "deploy" - migrations_dir = deploy_dir / "migrations" + migrations_dir = root_dir / "litellm-proxy-extras" / "litellm_proxy_extras" / "migrations" schema_path = root_dir / "schema.prisma" # Create temporary PostgreSQL database diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 4a62184df7..05b3e0be37 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -515,4 +515,5 @@ router_settings: | UPSTREAM_LANGFUSE_RELEASE | Release version identifier for upstream Langfuse | UPSTREAM_LANGFUSE_SECRET_KEY | Secret key for upstream Langfuse authentication | USE_AWS_KMS | Flag to enable AWS Key Management Service for encryption +| USE_PRISMA_MIGRATE | Flag to use prisma migrate instead of prisma db push. Recommended for production environments. | WEBHOOK_URL | URL for receiving webhooks from external services diff --git a/litellm-proxy-extras/LICENSE b/litellm-proxy-extras/LICENSE new file mode 100644 index 0000000000..3bfef5bae9 --- /dev/null +++ b/litellm-proxy-extras/LICENSE @@ -0,0 +1,26 @@ +Portions of this software are licensed as follows: + +* All content that resides under the "enterprise/" directory of this repository, if that directory exists, is licensed under the license defined in "enterprise/LICENSE". +* Content outside of the above mentioned directories or restrictions above is available under the MIT license as defined below. +--- +MIT License + +Copyright (c) 2023 Berri AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/litellm-proxy-extras/README.md b/litellm-proxy-extras/README.md new file mode 100644 index 0000000000..29453f65ba --- /dev/null +++ b/litellm-proxy-extras/README.md @@ -0,0 +1,21 @@ +Additional files for the proxy. Reduces the size of the main litellm package. + +Currently, only stores the migration.sql files for litellm-proxy. + +To install, run: + +```bash +pip install litellm-proxy-extras +``` +OR + +```bash +pip install litellm[proxy] # installs litellm-proxy-extras and other proxy dependencies. +``` + +To use the migrations, run: + +```bash +litellm --use_prisma_migrate +``` + diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.1.0-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.1.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..1aff64ef585290c8856d6876ba687c4caa5efbae GIT binary patch literal 5320 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 Upg@5Fg&zz456yD*`T(E+0CEJs7ytkO literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.1.1-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.1.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..e2583935a4cf41ec0154a387fd9083f51caac5c7 GIT binary patch literal 6307 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 literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/litellm_proxy_extras/__init__.py b/litellm-proxy-extras/litellm_proxy_extras/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/litellm-proxy-extras/litellm_proxy_extras/_logging.py b/litellm-proxy-extras/litellm_proxy_extras/_logging.py new file mode 100644 index 0000000000..118caecf48 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/_logging.py @@ -0,0 +1,12 @@ +import logging + +# Set up package logger +logger = logging.getLogger("litellm_proxy_extras") +if not logger.handlers: # Only add handler if none exists + handler = logging.StreamHandler() + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) diff --git a/deploy/migrations/20250326162113_baseline/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20250326162113_baseline/migration.sql similarity index 100% rename from deploy/migrations/20250326162113_baseline/migration.sql rename to litellm-proxy-extras/litellm_proxy_extras/migrations/20250326162113_baseline/migration.sql diff --git a/deploy/migrations/20250326171002_add_daily_user_table/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20250326171002_add_daily_user_table/migration.sql similarity index 100% rename from deploy/migrations/20250326171002_add_daily_user_table/migration.sql rename to litellm-proxy-extras/litellm_proxy_extras/migrations/20250326171002_add_daily_user_table/migration.sql diff --git a/deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql similarity index 100% rename from deploy/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql rename to litellm-proxy-extras/litellm_proxy_extras/migrations/20250327180120_add_api_requests_to_daily_user_table/migration.sql diff --git a/deploy/migrations/20250329084805_new_cron_job_table/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20250329084805_new_cron_job_table/migration.sql similarity index 100% rename from deploy/migrations/20250329084805_new_cron_job_table/migration.sql rename to litellm-proxy-extras/litellm_proxy_extras/migrations/20250329084805_new_cron_job_table/migration.sql diff --git a/deploy/migrations/migration_lock.toml b/litellm-proxy-extras/litellm_proxy_extras/migrations/migration_lock.toml similarity index 100% rename from deploy/migrations/migration_lock.toml rename to litellm-proxy-extras/litellm_proxy_extras/migrations/migration_lock.toml diff --git a/litellm-proxy-extras/litellm_proxy_extras/utils.py b/litellm-proxy-extras/litellm_proxy_extras/utils.py new file mode 100644 index 0000000000..894ae34122 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/utils.py @@ -0,0 +1,80 @@ +import os +import random +import subprocess +import time +from typing import Optional + +from litellm_proxy_extras._logging import logger + + +def str_to_bool(value: Optional[str]) -> bool: + if value is None: + return False + return value.lower() in ("true", "1", "t", "y", "yes") + + +class ProxyExtrasDBManager: + @staticmethod + def setup_database(schema_path: str, use_migrate: bool = False) -> bool: + """ + Set up the database using either prisma migrate or prisma db push + Uses migrations from litellm-proxy-extras package + + Args: + schema_path (str): Path to the Prisma schema file + use_migrate (bool): Whether to use prisma migrate instead of db push + + Returns: + bool: True if setup was successful, False otherwise + """ + use_migrate = str_to_bool(os.getenv("USE_PRISMA_MIGRATE")) or use_migrate + for attempt in range(4): + original_dir = os.getcwd() + schema_dir = os.path.dirname(schema_path) + os.chdir(schema_dir) + + try: + if use_migrate: + logger.info("Running prisma migrate deploy") + try: + # Set migrations directory for Prisma + subprocess.run( + ["prisma", "migrate", "deploy"], + timeout=60, + check=True, + capture_output=True, + text=True, + ) + logger.info("prisma migrate deploy completed") + return True + except subprocess.CalledProcessError as e: + logger.info(f"prisma db error: {e.stderr}, e: {e.stdout}") + if ( + "P3005" in e.stderr + and "database schema is not empty" in e.stderr + ): + logger.info("Error: Database schema is not empty") + return False + else: + # Use prisma db push with increased timeout + subprocess.run( + ["prisma", "db", "push", "--accept-data-loss"], + timeout=60, + check=True, + ) + return True + except subprocess.TimeoutExpired: + logger.info(f"Attempt {attempt + 1} timed out") + time.sleep(random.randrange(5, 15)) + except subprocess.CalledProcessError as e: + attempts_left = 3 - attempt + retry_msg = ( + f" Retrying... ({attempts_left} attempts left)" + if attempts_left > 0 + else "" + ) + logger.info(f"The process failed to execute. Details: {e}.{retry_msg}") + time.sleep(random.randrange(5, 15)) + finally: + os.chdir(original_dir) + return False diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml new file mode 100644 index 0000000000..c130a7fa9b --- /dev/null +++ b/litellm-proxy-extras/pyproject.toml @@ -0,0 +1,30 @@ +[tool.poetry] +name = "litellm-proxy-extras" +version = "0.1.1" +description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." +authors = ["BerriAI"] +readme = "README.md" + + +[tool.poetry.urls] +homepage = "https://litellm.ai" +Homepage = "https://litellm.ai" +repository = "https://github.com/BerriAI/litellm" +Repository = "https://github.com/BerriAI/litellm" +documentation = "https://docs.litellm.ai" +Documentation = "https://docs.litellm.ai" + +[tool.poetry.dependencies] +python = ">=3.8.1,<4.0, !=3.9.7" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.commitizen] +version = "0.1.1" +version_files = [ + "pyproject.toml:version", + "../requirements.txt:litellm-proxy-extras==", + "../pyproject.toml:litellm-proxy-extras = {version = \"" +] \ No newline at end of file diff --git a/litellm-proxy-extras/tests/__init__.py b/litellm-proxy-extras/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/litellm/proxy/db/prisma_client.py b/litellm/proxy/db/prisma_client.py index 4e38321e91..c81dc35e0c 100644 --- a/litellm/proxy/db/prisma_client.py +++ b/litellm/proxy/db/prisma_client.py @@ -179,42 +179,6 @@ class PrismaManager: verbose_proxy_logger.warning(f"Error creating baseline migration: {e}") return False - @staticmethod - def _copy_spend_tracking_migrations(prisma_dir: str) -> bool: - import shutil - from pathlib import Path - - """ - Check for and copy over spend tracking migrations if they exist in the deploy directory. - Returns True if migrations were found and copied, False otherwise. - """ - try: - # Get the current file's directory - current_dir = Path(__file__).parent - - # Check for migrations in the deploy directory (../../deploy/migrations) - deploy_migrations_dir = ( - current_dir.parent.parent.parent / "deploy" / "migrations" - ) - - # Local migrations directory - local_migrations_dir = Path(prisma_dir + "/migrations") - - if deploy_migrations_dir.exists(): - # Create local migrations directory if it doesn't exist - local_migrations_dir.mkdir(parents=True, exist_ok=True) - - # Copy all migration files - # Copy entire migrations folder recursively - shutil.copytree( - deploy_migrations_dir, local_migrations_dir, dirs_exist_ok=True - ) - - return True - return False - except Exception: - return False - @staticmethod def _get_migration_names(migrations_dir: str) -> list: """Get all migration directory names from the migrations folder""" @@ -251,6 +215,7 @@ class PrismaManager: bool: True if setup was successful, False otherwise """ + use_migrate = str_to_bool(os.getenv("USE_PRISMA_MIGRATE")) or use_migrate for attempt in range(4): original_dir = os.getcwd() prisma_dir = PrismaManager._get_prisma_dir() @@ -258,44 +223,20 @@ class PrismaManager: os.chdir(prisma_dir) try: if use_migrate: - PrismaManager._copy_spend_tracking_migrations( - prisma_dir - ) # place a migration in the migrations directory - verbose_proxy_logger.info("Running prisma migrate deploy") try: - subprocess.run( - ["prisma", "migrate", "deploy"], - timeout=60, - check=True, - capture_output=True, - text=True, + from litellm_proxy_extras.utils import ProxyExtrasDBManager + except ImportError as e: + verbose_proxy_logger.error( + f"\033[1;31mLiteLLM: Failed to import proxy extras. Got {e}\033[0m" ) - verbose_proxy_logger.info("prisma migrate deploy completed") + return False - # Resolve all migrations in the migrations directory - migrations_dir = os.path.join(prisma_dir, "migrations") - PrismaManager._resolve_all_migrations(migrations_dir) + prisma_dir = PrismaManager._get_prisma_dir() + schema_path = prisma_dir + "/schema.prisma" - return True - except subprocess.CalledProcessError as e: - verbose_proxy_logger.warning( - f"prisma db error: {e.stderr}, e: {e.stdout}" - ) - if ( - "P3005" in e.stderr - and "database schema is not empty" in e.stderr - ): - verbose_proxy_logger.info("Creating baseline migration") - if PrismaManager._create_baseline_migration(schema_path): - verbose_proxy_logger.info( - "Resolving all migrations after baseline" - ) - - # Resolve all migrations after baseline - migrations_dir = os.path.join(prisma_dir, "migrations") - PrismaManager._resolve_all_migrations(migrations_dir) - - return True + return ProxyExtrasDBManager.setup_database( + schema_path=schema_path, use_migrate=use_migrate + ) else: # Use prisma db push with increased timeout subprocess.run( diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 8196eb597e..de78247daf 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -672,7 +672,7 @@ def run_server( # noqa: PLR0915 LiteLLMDatabaseConnectionPool.database_connection_pool_limit.value, ) db_connection_timeout = general_settings.get( - "database_connection_timeout", + "database_connection_pool_timeout", LiteLLMDatabaseConnectionPool.database_connection_pool_timeout.value, ) if database_url and database_url.startswith("os.environ/"): diff --git a/poetry.lock b/poetry.lock index d659d66952..157291f5f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1676,6 +1676,17 @@ files = [ importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} referencing = ">=0.31.0" +[[package]] +name = "litellm-proxy-extras" +version = "0.1.1" +description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." +optional = true +python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" +files = [ + {file = "litellm_proxy_extras-0.1.1-py3-none-any.whl", hash = "sha256:2b3c4c5474bacbde2424c1cd13b21f85c65e9c4346f6159badd49a210eedef5c"}, + {file = "litellm_proxy_extras-0.1.1.tar.gz", hash = "sha256:a1eb911ad2e3742238863d314a8bd6d02dd0cc213ba040b2c0593f132fbf3117"}, +] + [[package]] name = "markupsafe" version = "2.1.5" @@ -4119,9 +4130,9 @@ type = ["pytest-mypy"] [extras] extra-proxy = ["azure-identity", "azure-keyvault-secrets", "google-cloud-kms", "prisma", "redisvl", "resend"] -proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", "fastapi-sso", "gunicorn", "mcp", "orjson", "pynacl", "python-multipart", "pyyaml", "rq", "uvicorn", "uvloop", "websockets"] +proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", "fastapi-sso", "gunicorn", "litellm-proxy-extras", "mcp", "orjson", "pynacl", "python-multipart", "pyyaml", "rq", "uvicorn", "uvloop", "websockets"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "36792478ff4afec5c8e748caf9b2ae6bebf3dd223e78bea2626b6589ef3277e4" +content-hash = "16cbf20784776377805f5e33c6bc97dce76303132aa3d81c7e6fe743f0ee3fc1" diff --git a/pyproject.toml b/pyproject.toml index 5eb4a71160..34564bd049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.34.34", optional = true} redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = "1.5.0", optional = true, python = ">=3.10"} +litellm-proxy-extras = {version = "0.1.1", optional = true} [tool.poetry.extras] proxy = [ @@ -74,7 +75,8 @@ proxy = [ "pynacl", "websockets", "boto3", - "mcp" + "mcp", + "litellm-proxy-extras" ] extra_proxy = [ diff --git a/requirements.txt b/requirements.txt index c28a2e5cf2..b3f0f1d073 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,6 +38,7 @@ sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests cryptography==43.0.1 tzdata==2025.1 # IANA time zone database +litellm-proxy-extras==0.1.1 # for proxy extras - e.g. prisma migrations ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.0 # for env diff --git a/tests/code_coverage_tests/liccheck.ini b/tests/code_coverage_tests/liccheck.ini index efd7dffe1c..c5ecc898f2 100644 --- a/tests/code_coverage_tests/liccheck.ini +++ b/tests/code_coverage_tests/liccheck.ini @@ -85,4 +85,5 @@ anthropic: >=0.21.3 # MIT License detect-secrets: >=1.5.0 # MIT License importlib-metadata: >=6.8.0 # Apache 2.0 License tokenizers: >=0.20.2 # Apache 2.0 License -jinja2: >=3.1.4 # BSD 3-Clause License \ No newline at end of file +jinja2: >=3.1.4 # BSD 3-Clause License +litellm-proxy-extras: >=0.1.1 # MIT License \ No newline at end of file diff --git a/tests/proxy_unit_tests/test_db_schema_migration.py b/tests/proxy_unit_tests/test_db_schema_migration.py index 089c50ebf8..b317818375 100644 --- a/tests/proxy_unit_tests/test_db_schema_migration.py +++ b/tests/proxy_unit_tests/test_db_schema_migration.py @@ -24,7 +24,7 @@ def test_aaaasschema_migration_check(schema_setup, monkeypatch): # test_db_url = "postgresql://neondb_owner:npg_JiZPS0DAhRn4@ep-delicate-wave-a55cvbuc.us-east-2.aws.neon.tech/neondb?sslmode=require" monkeypatch.setenv("DATABASE_URL", test_db_url) - deploy_dir = Path("./deploy") + deploy_dir = Path("./litellm-proxy-extras/litellm_proxy_extras") source_migrations_dir = deploy_dir / "migrations" schema_path = Path("./schema.prisma") From 08a52f4389e8ffc445764f0569ee4373fe1252e8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 15:50:13 -0700 Subject: [PATCH 203/244] log MCP tool call metadata in SLP --- litellm/litellm_core_utils/litellm_logging.py | 5 +++ .../proxy/_experimental/mcp_server/server.py | 35 +++++++++++++++++++ litellm/proxy/_types.py | 2 ++ .../spend_tracking/spend_tracking_utils.py | 10 +++++- litellm/types/utils.py | 28 +++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 8e83aea69a..8dcd732686 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -67,6 +67,7 @@ from litellm.types.utils import ( StandardCallbackDynamicParams, StandardLoggingAdditionalHeaders, StandardLoggingHiddenParams, + StandardLoggingMCPToolCall, StandardLoggingMetadata, StandardLoggingModelCostFailureDebugInformation, StandardLoggingModelInformation, @@ -3114,6 +3115,7 @@ class StandardLoggingPayloadSetup: litellm_params: Optional[dict] = None, prompt_integration: Optional[str] = None, applied_guardrails: Optional[List[str]] = None, + mcp_tool_call_metadata: Optional[StandardLoggingMCPToolCall] = None, ) -> StandardLoggingMetadata: """ Clean and filter the metadata dictionary to include only the specified keys in StandardLoggingMetadata. @@ -3160,6 +3162,7 @@ class StandardLoggingPayloadSetup: user_api_key_end_user_id=None, prompt_management_metadata=prompt_management_metadata, applied_guardrails=applied_guardrails, + mcp_tool_call_metadata=mcp_tool_call_metadata, ) if isinstance(metadata, dict): # Filter the metadata dictionary to include only the specified keys @@ -3486,6 +3489,7 @@ def get_standard_logging_object_payload( litellm_params=litellm_params, prompt_integration=kwargs.get("prompt_integration", None), applied_guardrails=kwargs.get("applied_guardrails", None), + mcp_tool_call_metadata=kwargs.get("mcp_tool_call_metadata", None), ) _request_body = proxy_server_request.get("body", {}) @@ -3626,6 +3630,7 @@ def get_standard_logging_metadata( user_api_key_end_user_id=None, prompt_management_metadata=None, applied_guardrails=None, + mcp_tool_call_metadata=None, ) if isinstance(metadata, dict): # Filter the metadata dictionary to include only the specified keys diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 32c33904a3..0a0ec13340 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -11,11 +11,13 @@ from fastapi.responses import StreamingResponse from pydantic import ValidationError from litellm._logging import verbose_logger +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.mcp_server.mcp_server_manager import ( ListMCPToolsRestAPIResponseObject, ) +from litellm.types.utils import StandardLoggingMCPToolCall from litellm.utils import client # Check if MCP is available @@ -124,6 +126,20 @@ if MCP_AVAILABLE: status_code=400, detail="Request arguments are required" ) + standard_logging_mcp_tool_call: StandardLoggingMCPToolCall = ( + _get_standard_logging_mcp_tool_call( + name=name, + arguments=arguments, + ) + ) + litellm_logging_obj: Optional[LiteLLMLoggingObj] = kwargs.get( + "litellm_logging_obj", None + ) + if litellm_logging_obj: + litellm_logging_obj.model_call_details["mcp_tool_call_metadata"] = ( + standard_logging_mcp_tool_call + ) + # Try managed server tool first if name in global_mcp_server_manager.tool_name_to_mcp_server_name_mapping: return await _handle_managed_mcp_tool(name, arguments) @@ -131,6 +147,25 @@ if MCP_AVAILABLE: # Fall back to local tool registry return await _handle_local_mcp_tool(name, arguments) + def _get_standard_logging_mcp_tool_call( + name: str, + arguments: Dict[str, Any], + ) -> StandardLoggingMCPToolCall: + mcp_server = global_mcp_server_manager._get_mcp_server_from_tool_name(name) + if mcp_server: + mcp_info = mcp_server.mcp_info or {} + return StandardLoggingMCPToolCall( + name=name, + arguments=arguments, + mcp_server_name=mcp_info.get("server_name"), + mcp_server_logo_url=mcp_info.get("logo_url"), + ) + else: + return StandardLoggingMCPToolCall( + name=name, + arguments=arguments, + ) + async def _handle_managed_mcp_tool( name: str, arguments: Dict[str, Any] ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e7d6bec004..963d6762bf 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -27,6 +27,7 @@ from litellm.types.utils import ( ModelResponse, ProviderField, StandardCallbackDynamicParams, + StandardLoggingMCPToolCall, StandardLoggingPayloadErrorInformation, StandardLoggingPayloadStatus, StandardPassThroughResponseObject, @@ -1928,6 +1929,7 @@ class SpendLogsMetadata(TypedDict): ] # special param to log k,v pairs to spendlogs for a call requester_ip_address: Optional[str] applied_guardrails: Optional[List[str]] + mcp_tool_call_metadata: Optional[StandardLoggingMCPToolCall] status: StandardLoggingPayloadStatus proxy_server_request: Optional[str] batch_models: Optional[List[str]] diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 6e9a088077..096c5191b1 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -13,7 +13,7 @@ from litellm._logging import verbose_proxy_logger from litellm.litellm_core_utils.core_helpers import get_litellm_metadata_from_kwargs from litellm.proxy._types import SpendLogsMetadata, SpendLogsPayload from litellm.proxy.utils import PrismaClient, hash_token -from litellm.types.utils import StandardLoggingPayload +from litellm.types.utils import StandardLoggingMCPToolCall, StandardLoggingPayload from litellm.utils import get_end_user_id_for_cost_tracking @@ -38,6 +38,7 @@ def _get_spend_logs_metadata( metadata: Optional[dict], applied_guardrails: Optional[List[str]] = None, batch_models: Optional[List[str]] = None, + mcp_tool_call_metadata: Optional[StandardLoggingMCPToolCall] = None, ) -> SpendLogsMetadata: if metadata is None: return SpendLogsMetadata( @@ -55,6 +56,7 @@ def _get_spend_logs_metadata( error_information=None, proxy_server_request=None, batch_models=None, + mcp_tool_call_metadata=None, ) verbose_proxy_logger.debug( "getting payload for SpendLogs, available keys in metadata: " @@ -71,6 +73,7 @@ def _get_spend_logs_metadata( ) clean_metadata["applied_guardrails"] = applied_guardrails clean_metadata["batch_models"] = batch_models + clean_metadata["mcp_tool_call_metadata"] = mcp_tool_call_metadata return clean_metadata @@ -200,6 +203,11 @@ def get_logging_payload( # noqa: PLR0915 if standard_logging_payload is not None else None ), + mcp_tool_call_metadata=( + standard_logging_payload["metadata"].get("mcp_tool_call_metadata", None) + if standard_logging_payload is not None + else None + ), ) special_usage_fields = ["completion_tokens", "prompt_tokens", "total_tokens"] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index fe6330f8bd..33522abe06 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -1647,6 +1647,33 @@ class StandardLoggingUserAPIKeyMetadata(TypedDict): user_api_key_end_user_id: Optional[str] +class StandardLoggingMCPToolCall(TypedDict, total=False): + name: str + """ + Name of the tool to call + """ + arguments: dict + """ + Arguments to pass to the tool + """ + result: dict + """ + Result of the tool call + """ + + mcp_server_name: Optional[str] + """ + Name of the MCP server that the tool call was made to + """ + + mcp_server_logo_url: Optional[str] + """ + Optional logo URL of the MCP server that the tool call was made to + + (this is to render the logo on the logs page on litellm ui) + """ + + class StandardBuiltInToolsParams(TypedDict, total=False): """ Standard built-in OpenAItools parameters @@ -1677,6 +1704,7 @@ class StandardLoggingMetadata(StandardLoggingUserAPIKeyMetadata): requester_ip_address: Optional[str] requester_metadata: Optional[dict] prompt_management_metadata: Optional[StandardLoggingPromptManagementMetadata] + mcp_tool_call_metadata: Optional[StandardLoggingMCPToolCall] applied_guardrails: Optional[List[str]] From fe6c033427402d0491760dfae633332e2b33b2a3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 16:01:34 -0700 Subject: [PATCH 204/244] render MCP tools on ui logs page --- litellm/constants.py | 1 + litellm/proxy/_experimental/mcp_server/server.py | 7 +++++++ .../src/components/view_logs/columns.tsx | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/litellm/constants.py b/litellm/constants.py index e224b3d33e..d5e0215ebf 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -418,6 +418,7 @@ RESPONSE_FORMAT_TOOL_NAME = "json_tool_call" # default tool name used when conv ########################### Logging Callback Constants ########################### AZURE_STORAGE_MSFT_VERSION = "2019-07-07" +MCP_TOOL_NAME_PREFIX = "mcp_tool" ########################### LiteLLM Proxy Specific Constants ########################### ######################################################################################## diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 0a0ec13340..1469f523b4 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -11,6 +11,7 @@ from fastapi.responses import StreamingResponse from pydantic import ValidationError from litellm._logging import verbose_logger +from litellm.constants import MCP_TOOL_NAME_PREFIX from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth @@ -139,6 +140,12 @@ if MCP_AVAILABLE: litellm_logging_obj.model_call_details["mcp_tool_call_metadata"] = ( standard_logging_mcp_tool_call ) + litellm_logging_obj.model_call_details["model"] = ( + f"{MCP_TOOL_NAME_PREFIX}: {standard_logging_mcp_tool_call.get('name') or ''}" + ) + litellm_logging_obj.model_call_details["custom_llm_provider"] = ( + standard_logging_mcp_tool_call.get("mcp_server_name") + ) # Try managed server tool first if name in global_mcp_server_manager.tool_name_to_mcp_server_name_mapping: diff --git a/ui/litellm-dashboard/src/components/view_logs/columns.tsx b/ui/litellm-dashboard/src/components/view_logs/columns.tsx index 98f5bfbc9d..2732fdaa77 100644 --- a/ui/litellm-dashboard/src/components/view_logs/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/columns.tsx @@ -8,6 +8,19 @@ import { Tooltip } from "antd"; import { TimeCell } from "./time_cell"; import { Button } from "@tremor/react"; +// Helper to get the appropriate logo URL +const getLogoUrl = ( + row: LogEntry, + provider: string +) => { + // Check if mcp_tool_call_metadata exists and contains mcp_server_logo_url + if (row.metadata?.mcp_tool_call_metadata?.mcp_server_logo_url) { + return row.metadata.mcp_tool_call_metadata.mcp_server_logo_url; + } + // Fall back to default provider logo + return provider ? getProviderLogoAndName(provider).logo : ''; +}; + export type LogEntry = { request_id: string; api_key: string; @@ -177,7 +190,7 @@ export const columns: ColumnDef[] = [
{provider && ( { From 7dd54112cb57c143ab38ac9c5fe283c0362300e6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 16:23:58 -0700 Subject: [PATCH 205/244] fix showing list of MCP tools --- .../proxy/_experimental/mcp_server/server.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 1469f523b4..4cfed9a603 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -253,16 +253,20 @@ if MCP_AVAILABLE: """ list_tools_result: List[ListMCPToolsRestAPIResponseObject] = [] for server in global_mcp_server_manager.mcp_servers: - tools = await global_mcp_server_manager._get_tools_from_server(server) - for tool in tools: - list_tools_result.append( - ListMCPToolsRestAPIResponseObject( - name=tool.name, - description=tool.description, - inputSchema=tool.inputSchema, - mcp_info=server.mcp_info, + try: + tools = await global_mcp_server_manager._get_tools_from_server(server) + for tool in tools: + list_tools_result.append( + ListMCPToolsRestAPIResponseObject( + name=tool.name, + description=tool.description, + inputSchema=tool.inputSchema, + mcp_info=server.mcp_info, + ) ) - ) + except Exception as e: + verbose_logger.error(f"Error getting tools from {server.name}: {e}") + continue return list_tools_result @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) From 815263f7bcab6fd8eb9bd36e6b4a5d02cfc7d5c3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 16:28:23 -0700 Subject: [PATCH 206/244] rename transform_openai_tool_call_request_to_mcp_tool_call_request --- litellm/experimental_mcp_client/tools.py | 8 +++++--- tests/litellm/experimental_mcp_client/test_tools.py | 6 +++--- tests/pass_through_tests/test_mcp_routes.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/litellm/experimental_mcp_client/tools.py b/litellm/experimental_mcp_client/tools.py index f4ebbf4af4..592b0767fd 100644 --- a/litellm/experimental_mcp_client/tools.py +++ b/litellm/experimental_mcp_client/tools.py @@ -76,7 +76,7 @@ def _get_function_arguments(function: FunctionDefinition) -> dict: return arguments if isinstance(arguments, dict) else {} -def _transform_openai_tool_call_to_mcp_tool_call_request( +def transform_openai_tool_call_request_to_mcp_tool_call_request( openai_tool: ChatCompletionMessageToolCall, ) -> MCPCallToolRequestParams: """Convert an OpenAI ChatCompletionMessageToolCall to an MCP CallToolRequestParams.""" @@ -100,8 +100,10 @@ async def call_openai_tool( Returns: The result of the MCP tool call. """ - mcp_tool_call_request_params = _transform_openai_tool_call_to_mcp_tool_call_request( - openai_tool=openai_tool, + mcp_tool_call_request_params = ( + transform_openai_tool_call_request_to_mcp_tool_call_request( + openai_tool=openai_tool, + ) ) return await call_mcp_tool( session=session, diff --git a/tests/litellm/experimental_mcp_client/test_tools.py b/tests/litellm/experimental_mcp_client/test_tools.py index 7089d83217..ec430ecc9b 100644 --- a/tests/litellm/experimental_mcp_client/test_tools.py +++ b/tests/litellm/experimental_mcp_client/test_tools.py @@ -19,11 +19,11 @@ from mcp.types import Tool as MCPTool from litellm.experimental_mcp_client.tools import ( _get_function_arguments, - _transform_openai_tool_call_to_mcp_tool_call_request, call_mcp_tool, call_openai_tool, load_mcp_tools, transform_mcp_tool_to_openai_tool, + transform_openai_tool_call_request_to_mcp_tool_call_request, ) @@ -76,11 +76,11 @@ def test_transform_mcp_tool_to_openai_tool(mock_mcp_tool): } -def test_transform_openai_tool_call_to_mcp_tool_call_request(mock_mcp_tool): +def testtransform_openai_tool_call_request_to_mcp_tool_call_request(mock_mcp_tool): openai_tool = { "function": {"name": "test_tool", "arguments": json.dumps({"test": "value"})} } - mcp_tool_call_request = _transform_openai_tool_call_to_mcp_tool_call_request( + mcp_tool_call_request = transform_openai_tool_call_request_to_mcp_tool_call_request( openai_tool ) assert mcp_tool_call_request.name == "test_tool" diff --git a/tests/pass_through_tests/test_mcp_routes.py b/tests/pass_through_tests/test_mcp_routes.py index d496a430af..3435d53585 100644 --- a/tests/pass_through_tests/test_mcp_routes.py +++ b/tests/pass_through_tests/test_mcp_routes.py @@ -10,7 +10,7 @@ from mcp import ClientSession from mcp.client.sse import sse_client from litellm.experimental_mcp_client.tools import ( transform_mcp_tool_to_openai_tool, - _transform_openai_tool_call_to_mcp_tool_call_request, + transform_openai_tool_call_request_to_mcp_tool_call_request, ) import json @@ -73,7 +73,7 @@ async def test_mcp_routes_with_vertex_ai(): # Call the tool using MCP client mcp_tool_call_request = ( - _transform_openai_tool_call_to_mcp_tool_call_request( + transform_openai_tool_call_request_to_mcp_tool_call_request( openai_tool.model_dump() ) ) From 79e8bbbfd435284860b284719ea53c1debdf369a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 16:48:15 -0700 Subject: [PATCH 207/244] fix types on tools.py --- litellm/experimental_mcp_client/tools.py | 4 +- tests/pass_through_tests/test_mcp_routes.py | 101 ++++++++------------ 2 files changed, 40 insertions(+), 65 deletions(-) diff --git a/litellm/experimental_mcp_client/tools.py b/litellm/experimental_mcp_client/tools.py index 592b0767fd..cdc26af4b7 100644 --- a/litellm/experimental_mcp_client/tools.py +++ b/litellm/experimental_mcp_client/tools.py @@ -1,5 +1,5 @@ import json -from typing import List, Literal, Union +from typing import Dict, List, Literal, Union from mcp import ClientSession from mcp.types import CallToolRequestParams as MCPCallToolRequestParams @@ -77,7 +77,7 @@ def _get_function_arguments(function: FunctionDefinition) -> dict: def transform_openai_tool_call_request_to_mcp_tool_call_request( - openai_tool: ChatCompletionMessageToolCall, + openai_tool: Union[ChatCompletionMessageToolCall, Dict], ) -> MCPCallToolRequestParams: """Convert an OpenAI ChatCompletionMessageToolCall to an MCP CallToolRequestParams.""" function = openai_tool["function"] diff --git a/tests/pass_through_tests/test_mcp_routes.py b/tests/pass_through_tests/test_mcp_routes.py index 3435d53585..f7f0a0d17f 100644 --- a/tests/pass_through_tests/test_mcp_routes.py +++ b/tests/pass_through_tests/test_mcp_routes.py @@ -1,85 +1,60 @@ -# Create server parameters for stdio connection import asyncio -import os - -import pytest -from langchain_mcp_adapters.tools import load_mcp_tools -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionUserMessageParam from mcp import ClientSession from mcp.client.sse import sse_client from litellm.experimental_mcp_client.tools import ( transform_mcp_tool_to_openai_tool, transform_openai_tool_call_request_to_mcp_tool_call_request, ) -import json -@pytest.mark.asyncio -async def test_mcp_routes(): - model = ChatOpenAI( - model="gpt-4o", api_key="sk-1234", base_url="http://localhost:4000" - ) +async def main(): + # Initialize clients + client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") - async with sse_client(url="http://localhost:4000/mcp/") as (read, write): - async with ClientSession(read, write) as session: - # Initialize the connection - print("Initializing session") - await session.initialize() - print("Session initialized") - - # Get tools - print("Loading tools") - tools = await load_mcp_tools(session) - print("Tools loaded") - print(tools) - - # Create and run the agent - agent = create_react_agent(model, tools) - agent_response = await agent.ainvoke({"messages": "Send an "}) - print(agent_response) - - -@pytest.mark.asyncio -async def test_mcp_routes_with_vertex_ai(): - # Create and run the agent - from openai import AsyncOpenAI - - openai_client = AsyncOpenAI(api_key="sk-1234", base_url="http://localhost:4000") - async with sse_client(url="http://localhost:4000/mcp/") as (read, write): + # Connect to MCP + async with sse_client("http://localhost:4000/mcp/") as (read, write): async with ClientSession(read, write) as session: await session.initialize() - MCP_TOOLS = await session.list_tools() + mcp_tools = await session.list_tools() + print("List of MCP tools for MCP server:", mcp_tools.tools) - print("MCP TOOLS from litellm proxy: ", MCP_TOOLS) + # Create message messages = [ - { - "role": "user", - "content": "send an email about litellm supporting MCP and send it to krrish@berri.ai", - } + ChatCompletionUserMessageParam( + content="Send an email about LiteLLM supporting MCP", role="user" + ) ] - llm_response = await openai_client.chat.completions.create( + + # Request with tools + response = await client.chat.completions.create( model="gpt-4o", messages=messages, tools=[ - transform_mcp_tool_to_openai_tool(tool) for tool in MCP_TOOLS.tools + transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools ], - tool_choice="required", + tool_choice="auto", ) - print("LLM RESPONSE: ", json.dumps(llm_response, indent=4, default=str)) - # Add assertions to verify the response - openai_tool = llm_response.choices[0].message.tool_calls[0] + # Handle tool call + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + if tool_call: + # Convert format + mcp_call = ( + transform_openai_tool_call_request_to_mcp_tool_call_request( + openai_tool=tool_call.model_dump() + ) + ) - # Call the tool using MCP client - mcp_tool_call_request = ( - transform_openai_tool_call_request_to_mcp_tool_call_request( - openai_tool.model_dump() - ) - ) - call_result = await session.call_tool( - name=mcp_tool_call_request.name, - arguments=mcp_tool_call_request.arguments, - ) - print("CALL RESULT: ", call_result) - pass + # Execute tool + result = await session.call_tool( + name=mcp_call.name, arguments=mcp_call.arguments + ) + + print("Result:", result) + + +# Run it +asyncio.run(main()) From f2885bfb65c5b8c86071579df32d94002a0bc2e9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 16:48:38 -0700 Subject: [PATCH 208/244] add code example --- .../src/components/mcp_tools/code-example.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/code-example.tsx diff --git a/ui/litellm-dashboard/src/components/mcp_tools/code-example.tsx b/ui/litellm-dashboard/src/components/mcp_tools/code-example.tsx new file mode 100644 index 0000000000..5e0b170530 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/code-example.tsx @@ -0,0 +1,92 @@ +import React from 'react'; + +const codeString = `import asyncio +from openai import AsyncOpenAI +from openai.types.chat import ChatCompletionUserMessageParam +from mcp import ClientSession +from mcp.client.sse import sse_client +from litellm.experimental_mcp_client.tools import ( + transform_mcp_tool_to_openai_tool, + transform_openai_tool_call_request_to_mcp_tool_call_request, +) + +async def main(): + # Initialize clients + client = AsyncOpenAI( + api_key="sk-1234", + base_url="http://localhost:4000" + ) + + # Connect to MCP + async with sse_client("http://localhost:4000/mcp/") as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + mcp_tools = await session.list_tools() + print("List of MCP tools for MCP server:", mcp_tools.tools) + + # Create message + messages = [ + ChatCompletionUserMessageParam( + content="Send an email about LiteLLM supporting MCP", + role="user" + ) + ] + + # Request with tools + response = await client.chat.completions.create( + model="gpt-4o", + messages=messages, + tools=[transform_mcp_tool_to_openai_tool(tool) for tool in mcp_tools.tools], + tool_choice="auto" + ) + + # Handle tool call + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + if tool_call: + # Convert format + mcp_call = transform_openai_tool_call_request_to_mcp_tool_call_request( + openai_tool=tool_call.model_dump() + ) + + # Execute tool + result = await session.call_tool( + name=mcp_call.name, + arguments=mcp_call.arguments + ) + + print("Result:", result) + +# Run it +asyncio.run(main())`; + +export const CodeExample: React.FC = () => { + return ( +
+
+

Using MCP Tools

+
+
+
+
+
Python integration
+
+ +
+ +
+
+            {codeString}
+          
+
+
+
+ ); +}; \ No newline at end of file From 09e073d57b0965cb01578afa0f54fe281b67a624 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:04:32 -0700 Subject: [PATCH 209/244] ui mcp tools --- .../src/components/mcp_tools/columns.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx b/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx index 5e347a7e1e..b99ecbd9f0 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/columns.tsx @@ -1,6 +1,7 @@ import React from "react"; import { ColumnDef } from "@tanstack/react-table"; import { MCPTool, InputSchema } from "./types"; +import { Button } from "@tremor/react" export const columns: ColumnDef[] = [ { @@ -56,16 +57,18 @@ export const columns: ColumnDef[] = [ return (
- +
); }, @@ -220,13 +223,13 @@ export function ToolTestPanel({ )}
- +
From 047d767947f8341349e40bafb836a5dd01d827cc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:06:36 -0700 Subject: [PATCH 210/244] fix tests for gcs pub sub --- .../gcs_pub_sub_body/spend_logs_payload.json | 2 +- .../gcs_pub_sub_body/standard_logging_payload.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json b/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json index a4c0f3f58b..656cb6d589 100644 --- a/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json +++ b/tests/logging_callback_tests/gcs_pub_sub_body/spend_logs_payload.json @@ -9,7 +9,7 @@ "model": "gpt-4o", "user": "", "team_id": "", - "metadata": "{\"applied_guardrails\": [], \"batch_models\": null, \"additional_usage_values\": {\"completion_tokens_details\": null, \"prompt_tokens_details\": null}}", + "metadata": "{\"applied_guardrails\": [], \"batch_models\": null, \"mcp_tool_call_metadata\": null, \"additional_usage_values\": {\"completion_tokens_details\": null, \"prompt_tokens_details\": null}}", "cache_key": "Cache OFF", "spend": 0.00022500000000000002, "total_tokens": 30, diff --git a/tests/logging_callback_tests/gcs_pub_sub_body/standard_logging_payload.json b/tests/logging_callback_tests/gcs_pub_sub_body/standard_logging_payload.json index eb57387120..1dc72b704f 100644 --- a/tests/logging_callback_tests/gcs_pub_sub_body/standard_logging_payload.json +++ b/tests/logging_callback_tests/gcs_pub_sub_body/standard_logging_payload.json @@ -25,7 +25,8 @@ "requester_metadata": null, "user_api_key_end_user_id": null, "prompt_management_metadata": null, - "applied_guardrails": [] + "applied_guardrails": [], + "mcp_tool_call_metadata": null }, "cache_key": null, "response_cost": 0.00022500000000000002, From 3e378f2bec91d19eff14218094725aa4ec26bda2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:07:36 -0700 Subject: [PATCH 211/244] async def test_spend_logs_payload_e2e(self): --- .../proxy/spend_tracking/test_spend_management_endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index c1e94138a3..57706c94f7 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -457,7 +457,7 @@ class TestSpendLogsPayload: "model": "gpt-4o", "user": "", "team_id": "", - "metadata": '{"applied_guardrails": [], "batch_models": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": null}}', + "metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": null}}', "cache_key": "Cache OFF", "spend": 0.00022500000000000002, "total_tokens": 30, From 4e106ce21717d90be23a0c0359edaa8a42475faa Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:11:46 -0700 Subject: [PATCH 212/244] fix test --- tests/mcp_tests/test_mcp_server.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index 17abd1d6a5..cf64d16029 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -7,21 +7,24 @@ sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system path -from litellm.proxy._experimental.mcp_server.mcp_client_manager import ( +from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( MCPServerManager, MCPSSEServer, ) -MCP_SERVERS = [ - MCPSSEServer(name="zapier_mcp_server", url=os.environ.get("ZAPIER_MCP_SERVER_URL")), -] - -mcp_server_manager = MCPServerManager(mcp_servers=MCP_SERVERS) +mcp_server_manager = MCPServerManager() @pytest.mark.asyncio async def test_mcp_server_manager(): + mcp_server_manager.load_servers_from_config( + { + "zapier_mcp_server": { + "url": os.environ.get("ZAPIER_MCP_SERVER_URL"), + } + } + ) tools = await mcp_server_manager.list_tools() print("TOOLS FROM MCP SERVER MANAGER== ", tools) @@ -29,9 +32,3 @@ async def test_mcp_server_manager(): name="gmail_send_email", arguments={"body": "Test"} ) print("RESULT FROM CALLING TOOL FROM MCP SERVER MANAGER== ", result) - - -""" -TODO test with multiple MCP servers and calling a specific - -""" From a3df0269bb03aa839c98afaee5878a69209f1dd5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:38:24 -0700 Subject: [PATCH 213/244] fix tests --- .../proxy/spend_tracking/test_spend_management_endpoints.py | 2 +- tests/logging_callback_tests/test_otel_logging.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 57706c94f7..9ced59e4dc 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -555,7 +555,7 @@ class TestSpendLogsPayload: "model": "claude-3-7-sonnet-20250219", "user": "", "team_id": "", - "metadata": '{"applied_guardrails": [], "batch_models": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}', + "metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}', "cache_key": "Cache OFF", "spend": 0.01383, "total_tokens": 2598, diff --git a/tests/logging_callback_tests/test_otel_logging.py b/tests/logging_callback_tests/test_otel_logging.py index aeec20be23..2e102ec46c 100644 --- a/tests/logging_callback_tests/test_otel_logging.py +++ b/tests/logging_callback_tests/test_otel_logging.py @@ -274,6 +274,7 @@ def validate_redacted_message_span_attributes(span): "metadata.user_api_key_end_user_id", "metadata.user_api_key_user_email", "metadata.applied_guardrails", + "metadata.mcp_tool_call_metadata", ] _all_attributes = set( From abf06013ec574008e640a1e33d18f1445f9d1b06 Mon Sep 17 00:00:00 2001 From: Tan Yong Sheng <64836390+tan-yong-sheng@users.noreply.github.com> Date: Sun, 30 Mar 2025 08:40:27 +0800 Subject: [PATCH 214/244] update docs for openwebui (#9636) --- docs/my-website/docs/tutorials/openweb_ui.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/my-website/docs/tutorials/openweb_ui.md b/docs/my-website/docs/tutorials/openweb_ui.md index ab1e2e121e..b2c1204069 100644 --- a/docs/my-website/docs/tutorials/openweb_ui.md +++ b/docs/my-website/docs/tutorials/openweb_ui.md @@ -98,6 +98,5 @@ On the models dropdown select `thinking-anthropic-claude-3-7-sonnet` - - - +## Additional Resources +- Running LiteLLM and OpenWebUI on Windows Localhost: A Comprehensive Guide [https://www.tanyongsheng.com/note/running-litellm-and-openwebui-on-windows-localhost-a-comprehensive-guide/](https://www.tanyongsheng.com/note/running-litellm-and-openwebui-on-windows-localhost-a-comprehensive-guide/) \ No newline at end of file From 0e321eed1a8ab697d7dc0363a32f8886b6a05c34 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:44:32 -0700 Subject: [PATCH 215/244] fix import errors without mcp --- .../mcp_server/mcp_server_manager.py | 24 +++++++++++-------- .../types/mcp_server/mcp_server_manager.py | 13 ++++++---- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index c73eabd629..9eb44f5597 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -8,11 +8,17 @@ This is a Proxy import asyncio import json -from typing import Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional -from mcp import ClientSession -from mcp.client.sse import sse_client -from mcp.types import Tool as MCPTool +if TYPE_CHECKING: + from mcp import ClientSession + from mcp.client.sse import sse_client + from mcp.types import Tool as MCPTool +else: + # Provide fallback types for runtime incase `mcp` is not installed + ClientSession = None + MCPTool = object + sse_client = None from litellm._logging import verbose_logger from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPSSEServer @@ -93,10 +99,9 @@ class MCPServerManager: async with sse_client(url=server.url) as (read, write): async with ClientSession(read, write) as session: - server.client_session = session - await server.client_session.initialize() + await session.initialize() - tools_result = await server.client_session.list_tools() + tools_result = await session.list_tools() verbose_logger.debug(f"Tools from {server.name}: {tools_result}") # Update tool to server mapping @@ -137,9 +142,8 @@ class MCPServerManager: raise ValueError(f"Tool {name} not found") async with sse_client(url=mcp_server.url) as (read, write): async with ClientSession(read, write) as session: - mcp_server.client_session = session - await mcp_server.client_session.initialize() - return await mcp_server.client_session.call_tool(name, arguments) + await session.initialize() + return await session.call_tool(name, arguments) def _get_mcp_server_from_tool_name(self, tool_name: str) -> Optional[MCPSSEServer]: """ diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index 981926df3c..9752423e7e 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -1,10 +1,16 @@ -from typing import Any, Dict, List, Optional +from typing import TYPE_CHECKING, Optional -from mcp import ClientSession -from mcp.types import Tool as MCPTool from pydantic import BaseModel, ConfigDict from typing_extensions import TypedDict +if TYPE_CHECKING: + from mcp import ClientSession + from mcp.types import Tool as MCPTool +else: + # Provide fallback types for runtime incase `mcp` is not installed + ClientSession = None + MCPTool = object + class MCPInfo(TypedDict, total=False): server_name: str @@ -14,7 +20,6 @@ class MCPInfo(TypedDict, total=False): class MCPSSEServer(BaseModel): name: str url: str - client_session: Optional[ClientSession] = None mcp_info: Optional[MCPInfo] = None model_config = ConfigDict(arbitrary_types_allowed=True) From eb4b8d9e7dc2db5509edcc18a49be345f29565e3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Mar 2025 17:50:19 -0700 Subject: [PATCH 216/244] fix linting on DataTableWrapper --- .../src/components/mcp_tools/index.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/index.tsx b/ui/litellm-dashboard/src/components/mcp_tools/index.tsx index 9018752705..ae3d4fac62 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/index.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/index.tsx @@ -5,6 +5,31 @@ import { columns, ToolTestPanel } from './columns'; import { MCPTool, MCPToolsViewerProps, CallMCPToolResponse } from './types'; import { listMCPTools, callMCPTool } from '../networking'; +// Wrapper to handle the type mismatch between MCPTool and DataTable's expected type +function DataTableWrapper({ + columns, + data, + isLoading, +}: { + columns: any; + data: MCPTool[]; + isLoading: boolean; +}) { + // Create a dummy renderSubComponent and getRowCanExpand function + const renderSubComponent = () =>
; + const getRowCanExpand = () => false; + + return ( + + ); +} + export default function MCPToolsViewer({ accessToken, userRole, @@ -49,7 +74,7 @@ export default function MCPToolsViewer({ const toolsData = React.useMemo(() => { if (!mcpTools) return []; - return mcpTools.map(tool => ({ + return mcpTools.map((tool: MCPTool) => ({ ...tool, onToolSelect: (tool: MCPTool) => { setSelectedTool(tool); @@ -61,7 +86,7 @@ export default function MCPToolsViewer({ // Filter tools based on search term const filteredTools = React.useMemo(() => { - return toolsData.filter(tool => { + return toolsData.filter((tool: MCPTool) => { const searchLower = searchTerm.toLowerCase(); return ( tool.name.toLowerCase().includes(searchLower) || @@ -122,7 +147,7 @@ export default function MCPToolsViewer({

cGAOEfWX;kG#h4YjO)ZT(;*$M?7$OzLlU@3+ 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%0D?qEl&qwUoJasQ6B7!~<*JhSy9xB7F_9mz;n1R> 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 X$Qb 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