diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 659dc350fe47..3820f0ea33ac 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -233,6 +233,9 @@ def generate_feedback_box(): from litellm.proxy.vertex_ai_endpoints.google_ai_studio_endpoints import ( router as gemini_router, ) +from litellm.proxy.vertex_ai_endpoints.langfuse_endpoints import ( + router as langfuse_router, +) from litellm.proxy.vertex_ai_endpoints.vertex_endpoints import router as vertex_router from litellm.proxy.vertex_ai_endpoints.vertex_endpoints import set_default_vertex_config from litellm.router import ( @@ -9738,6 +9741,7 @@ def cleanup_router_config_variables(): app.include_router(fine_tuning_router) app.include_router(vertex_router) app.include_router(gemini_router) +app.include_router(langfuse_router) app.include_router(pass_through_router) app.include_router(health_router) app.include_router(key_management_router) diff --git a/litellm/proxy/vertex_ai_endpoints/google_ai_studio_endpoints.py b/litellm/proxy/vertex_ai_endpoints/google_ai_studio_endpoints.py index cfb5231f95f7..4efca5efdc89 100644 --- a/litellm/proxy/vertex_ai_endpoints/google_ai_studio_endpoints.py +++ b/litellm/proxy/vertex_ai_endpoints/google_ai_studio_endpoints.py @@ -1,7 +1,7 @@ """ What is this? -Google AI Studio Pass-Through Endpoints +Provider-specific Pass-Through Endpoints """ """ diff --git a/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py new file mode 100644 index 000000000000..a19a8b127b4a --- /dev/null +++ b/litellm/proxy/vertex_ai_endpoints/langfuse_endpoints.py @@ -0,0 +1,117 @@ +""" +What is this? + +Logging Pass-Through Endpoints +""" + +""" +1. Create pass-through endpoints for any LITELLM_BASE_URL/langfuse/ map to LANGFUSE_BASE_URL/ +""" + +import ast +import asyncio +import base64 +import traceback +from base64 import b64encode +from datetime import datetime, timedelta, timezone +from typing import List, Optional +from urllib.parse import urlencode + +import fastapi +import httpx +from fastapi import ( + APIRouter, + Depends, + File, + Form, + Header, + HTTPException, + Request, + Response, + UploadFile, + status, +) +from starlette.datastructures import QueryParams + +import litellm +from litellm._logging import verbose_proxy_logger +from litellm.batches.main import FileObject +from litellm.fine_tuning.main import vertex_fine_tuning_apis_instance +from litellm.proxy._types import * +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( + create_pass_through_route, +) + +router = APIRouter() +default_vertex_config = None + + +def create_request_copy(request: Request): + return { + "method": request.method, + "url": str(request.url), + "headers": dict(request.headers), + "cookies": request.cookies, + "query_params": dict(request.query_params), + } + + +@router.api_route("/langfuse/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE"]) +async def langfuse_proxy_route( + endpoint: str, + request: Request, + fastapi_response: Response, +): + ## CHECK FOR LITELLM API KEY IN THE QUERY PARAMS - ?..key=LITELLM_API_KEY + api_key = request.headers.get("Authorization") or "" + + ## decrypt base64 hash + api_key = api_key.replace("Basic ", "") + + decoded_bytes = base64.b64decode(api_key) + decoded_str = decoded_bytes.decode("utf-8") + api_key = decoded_str.split(":")[1] + + user_api_key_dict = await user_api_key_auth( + request=request, api_key="Bearer {}".format(api_key) + ) + + base_target_url = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com") + if not ( + base_target_url.startswith("http://") or base_target_url.startswith("https://") + ): + # add http:// if unset, assume communicating over private network - e.g. render + base_target_url = "http://" + base_target_url + + encoded_endpoint = httpx.URL(endpoint).path + + # Ensure endpoint starts with '/' for proper URL construction + if not encoded_endpoint.startswith("/"): + encoded_endpoint = "/" + encoded_endpoint + + # Construct the full target URL using httpx + base_url = httpx.URL(base_target_url) + updated_url = base_url.copy_with(path=encoded_endpoint) + + # Add or update query parameters + langfuse_public_key = litellm.utils.get_secret(secret_name="LANGFUSE_PUBLIC_KEY") + langfuse_secret_key = litellm.utils.get_secret(secret_name="LANGFUSE_SECRET_KEY") + + langfuse_combined_key = "Basic " + b64encode( + f"{langfuse_public_key}:{langfuse_secret_key}".encode("utf-8") + ).decode("ascii") + + ## CREATE PASS-THROUGH + endpoint_func = create_pass_through_route( + endpoint=endpoint, + target=str(updated_url), + custom_headers={"Authorization": langfuse_combined_key}, + ) # dynamically construct pass-through endpoint based on incoming path + received_value = await endpoint_func( + request, + fastapi_response, + user_api_key_dict, + ) + + return received_value