|  | 
|  | 1 | +#!/usr/bin/env python3 | 
|  | 2 | +""" | 
|  | 3 | +Test script for LiteLLM tracing with local LiteLLM server. | 
|  | 4 | +
 | 
|  | 5 | +This script demonstrates how to test the LiteLLM integration with: | 
|  | 6 | +1. Local LiteLLM proxy server | 
|  | 7 | +2. Custom API base URLs | 
|  | 8 | +3. Various providers and models | 
|  | 9 | +
 | 
|  | 10 | +Prerequisites: | 
|  | 11 | +- LiteLLM server running locally (e.g., litellm --port 4000) | 
|  | 12 | +- API keys configured in environment or LiteLLM config | 
|  | 13 | +""" | 
|  | 14 | + | 
|  | 15 | +import os | 
|  | 16 | +import sys | 
|  | 17 | +import time | 
|  | 18 | +from typing import Dict, Any | 
|  | 19 | + | 
|  | 20 | +# Add the src directory to the path for local testing | 
|  | 21 | +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../../src')) | 
|  | 22 | + | 
|  | 23 | +try: | 
|  | 24 | + import litellm | 
|  | 25 | + from openlayer.lib import trace_litellm | 
|  | 26 | + from openlayer.lib.tracing import tracer | 
|  | 27 | + from openlayer.lib.tracing.tracer import configure | 
|  | 28 | +except ImportError as e: | 
|  | 29 | + print(f"Import error: {e}") | 
|  | 30 | + print("Make sure to install required dependencies:") | 
|  | 31 | + print("pip install litellm openlayer") | 
|  | 32 | + sys.exit(1) | 
|  | 33 | + | 
|  | 34 | + | 
|  | 35 | +class LiteLLMTester: | 
|  | 36 | + """Test LiteLLM tracing with various configurations.""" | 
|  | 37 | +  | 
|  | 38 | + def __init__(self, base_url: str = None, api_key: str = None, openlayer_base_url: str = None): | 
|  | 39 | + """Initialize the tester with optional custom base URL and API key.""" | 
|  | 40 | + self.base_url = base_url or "http://localhost:4000" | 
|  | 41 | + self.api_key = api_key or os.getenv("LITELLM_API_KEY", "sk-1234") | 
|  | 42 | + self.openlayer_base_url = openlayer_base_url or "http://localhost:8080/v1" | 
|  | 43 | +  | 
|  | 44 | + # Configure OpenLayer base URL programmatically | 
|  | 45 | + configure(base_url=self.openlayer_base_url) | 
|  | 46 | + print(f"🔧 OpenLayer configured for: {self.openlayer_base_url}") | 
|  | 47 | +  | 
|  | 48 | + # Configure LiteLLM for local testing | 
|  | 49 | + if base_url: | 
|  | 50 | + # Set custom API base for testing with local LiteLLM server | 
|  | 51 | + os.environ["LITELLM_BASE_URL"] = self.base_url | 
|  | 52 | +  | 
|  | 53 | + # Enable tracing | 
|  | 54 | + trace_litellm() | 
|  | 55 | + print(f"✅ LiteLLM tracing enabled") | 
|  | 56 | + print(f"🔗 LiteLLM Base URL: {self.base_url}") | 
|  | 57 | + print(f"🏠 OpenLayer Base URL: {self.openlayer_base_url}") | 
|  | 58 | +  | 
|  | 59 | + def test_basic_completion(self, model: str = "gpt-3.5-turbo") -> Dict[str, Any]: | 
|  | 60 | + """Test basic completion with tracing.""" | 
|  | 61 | + print(f"\n📝 Testing basic completion with {model}") | 
|  | 62 | +  | 
|  | 63 | + try: | 
|  | 64 | + response = litellm.completion( | 
|  | 65 | + model=model, | 
|  | 66 | + messages=[ | 
|  | 67 | + {"role": "system", "content": "You are a helpful assistant."}, | 
|  | 68 | + {"role": "user", "content": "What is 2 + 2?"} | 
|  | 69 | + ], | 
|  | 70 | + temperature=0.5, | 
|  | 71 | + max_tokens=50, | 
|  | 72 | + api_base=self.base_url, | 
|  | 73 | + api_key=self.api_key, | 
|  | 74 | + inference_id=f"test-basic-{int(time.time())}" | 
|  | 75 | + ) | 
|  | 76 | +  | 
|  | 77 | + result = { | 
|  | 78 | + "status": "success", | 
|  | 79 | + "model": response.model, | 
|  | 80 | + "content": response.choices[0].message.content, | 
|  | 81 | + "usage": response.usage.model_dump() if response.usage else None, | 
|  | 82 | + "provider": getattr(response, '_hidden_params', {}).get('custom_llm_provider', 'unknown') | 
|  | 83 | + } | 
|  | 84 | +  | 
|  | 85 | + print(f"✅ Success: {result['content'][:100]}...") | 
|  | 86 | + print(f"📊 Usage: {result['usage']}") | 
|  | 87 | + print(f"🏢 Provider: {result['provider']}") | 
|  | 88 | +  | 
|  | 89 | + return result | 
|  | 90 | +  | 
|  | 91 | + except Exception as e: | 
|  | 92 | + print(f"❌ Error: {e}") | 
|  | 93 | + return {"status": "error", "error": str(e)} | 
|  | 94 | +  | 
|  | 95 | + def test_streaming_completion(self, model: str = "gpt-3.5-turbo") -> Dict[str, Any]: | 
|  | 96 | + """Test streaming completion with tracing.""" | 
|  | 97 | + print(f"\n🌊 Testing streaming completion with {model}") | 
|  | 98 | +  | 
|  | 99 | + try: | 
|  | 100 | + stream = litellm.completion( | 
|  | 101 | + model=model, | 
|  | 102 | + messages=[ | 
|  | 103 | + {"role": "user", "content": "Count from 1 to 5, one number per line."} | 
|  | 104 | + ], | 
|  | 105 | + stream=True, | 
|  | 106 | + temperature=0.3, | 
|  | 107 | + max_tokens=50, | 
|  | 108 | + api_base=self.base_url, | 
|  | 109 | + api_key=self.api_key, | 
|  | 110 | + inference_id=f"test-stream-{int(time.time())}" | 
|  | 111 | + ) | 
|  | 112 | +  | 
|  | 113 | + collected_content = [] | 
|  | 114 | + chunk_count = 0 | 
|  | 115 | +  | 
|  | 116 | + for chunk in stream: | 
|  | 117 | + chunk_count += 1 | 
|  | 118 | + if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: | 
|  | 119 | + content = chunk.choices[0].delta.content | 
|  | 120 | + collected_content.append(content) | 
|  | 121 | + print(content, end="", flush=True) | 
|  | 122 | +  | 
|  | 123 | + full_content = "".join(collected_content) | 
|  | 124 | +  | 
|  | 125 | + result = { | 
|  | 126 | + "status": "success", | 
|  | 127 | + "model": model, | 
|  | 128 | + "content": full_content, | 
|  | 129 | + "chunks": chunk_count, | 
|  | 130 | + "provider": "streamed" # Provider detection in streaming is complex | 
|  | 131 | + } | 
|  | 132 | +  | 
|  | 133 | + print(f"\n✅ Streaming complete: {chunk_count} chunks") | 
|  | 134 | + print(f"📝 Content: {full_content}") | 
|  | 135 | +  | 
|  | 136 | + return result | 
|  | 137 | +  | 
|  | 138 | + except Exception as e: | 
|  | 139 | + print(f"❌ Streaming error: {e}") | 
|  | 140 | + return {"status": "error", "error": str(e)} | 
|  | 141 | +  | 
|  | 142 | + def test_multiple_providers(self, models: list = None) -> Dict[str, Any]: | 
|  | 143 | + """Test multiple providers/models with tracing.""" | 
|  | 144 | + if models is None: | 
|  | 145 | + models = [ | 
|  | 146 | + "gpt-3.5-turbo", | 
|  | 147 | + "claude-3-haiku-20240307",  | 
|  | 148 | + "gemini-pro", | 
|  | 149 | + "llama-2-7b-chat" | 
|  | 150 | + ] | 
|  | 151 | +  | 
|  | 152 | + print(f"\n🔄 Testing multiple providers: {models}") | 
|  | 153 | +  | 
|  | 154 | + results = {} | 
|  | 155 | + prompt = "What is the capital of Japan?" | 
|  | 156 | +  | 
|  | 157 | + with tracer.create_step( | 
|  | 158 | + name="Multi-Provider Test", | 
|  | 159 | + metadata={"test_type": "provider_comparison", "models": models} | 
|  | 160 | + ) as step: | 
|  | 161 | +  | 
|  | 162 | + for model in models: | 
|  | 163 | + try: | 
|  | 164 | + print(f"\n🧪 Testing {model}...") | 
|  | 165 | +  | 
|  | 166 | + response = litellm.completion( | 
|  | 167 | + model=model, | 
|  | 168 | + messages=[{"role": "user", "content": prompt}], | 
|  | 169 | + temperature=0.5, | 
|  | 170 | + max_tokens=30, | 
|  | 171 | + api_base=self.base_url, | 
|  | 172 | + api_key=self.api_key, | 
|  | 173 | + inference_id=f"multi-test-{model.replace('/', '-')}-{int(time.time())}" | 
|  | 174 | + ) | 
|  | 175 | +  | 
|  | 176 | + results[model] = { | 
|  | 177 | + "status": "success", | 
|  | 178 | + "content": response.choices[0].message.content, | 
|  | 179 | + "usage": response.usage.model_dump() if response.usage else None, | 
|  | 180 | + "provider": getattr(response, '_hidden_params', {}).get('custom_llm_provider', 'unknown') | 
|  | 181 | + } | 
|  | 182 | +  | 
|  | 183 | + print(f"✅ {model}: {results[model]['content'][:50]}...") | 
|  | 184 | +  | 
|  | 185 | + except Exception as e: | 
|  | 186 | + results[model] = {"status": "error", "error": str(e)} | 
|  | 187 | + print(f"❌ {model}: {e}") | 
|  | 188 | +  | 
|  | 189 | + step.log(results=results) | 
|  | 190 | +  | 
|  | 191 | + return results | 
|  | 192 | +  | 
|  | 193 | + def test_function_calling(self, model: str = "gpt-3.5-turbo") -> Dict[str, Any]: | 
|  | 194 | + """Test function calling with tracing.""" | 
|  | 195 | + print(f"\n🔧 Testing function calling with {model}") | 
|  | 196 | +  | 
|  | 197 | + functions = [ | 
|  | 198 | + { | 
|  | 199 | + "name": "get_current_weather", | 
|  | 200 | + "description": "Get the current weather in a given location", | 
|  | 201 | + "parameters": { | 
|  | 202 | + "type": "object", | 
|  | 203 | + "properties": { | 
|  | 204 | + "location": { | 
|  | 205 | + "type": "string", | 
|  | 206 | + "description": "The city and state, e.g. San Francisco, CA" | 
|  | 207 | + }, | 
|  | 208 | + "unit": { | 
|  | 209 | + "type": "string", | 
|  | 210 | + "enum": ["celsius", "fahrenheit"] | 
|  | 211 | + } | 
|  | 212 | + }, | 
|  | 213 | + "required": ["location"] | 
|  | 214 | + } | 
|  | 215 | + } | 
|  | 216 | + ] | 
|  | 217 | +  | 
|  | 218 | + try: | 
|  | 219 | + response = litellm.completion( | 
|  | 220 | + model=model, | 
|  | 221 | + messages=[ | 
|  | 222 | + {"role": "user", "content": "What's the weather like in Tokyo?"} | 
|  | 223 | + ], | 
|  | 224 | + functions=functions, | 
|  | 225 | + function_call="auto", | 
|  | 226 | + api_base=self.base_url, | 
|  | 227 | + api_key=self.api_key, | 
|  | 228 | + inference_id=f"test-func-{int(time.time())}" | 
|  | 229 | + ) | 
|  | 230 | +  | 
|  | 231 | + message = response.choices[0].message | 
|  | 232 | +  | 
|  | 233 | + if message.function_call: | 
|  | 234 | + result = { | 
|  | 235 | + "status": "success", | 
|  | 236 | + "function_name": message.function_call.name, | 
|  | 237 | + "arguments": message.function_call.arguments, | 
|  | 238 | + "usage": response.usage.model_dump() if response.usage else None | 
|  | 239 | + } | 
|  | 240 | + print(f"✅ Function called: {result['function_name']}") | 
|  | 241 | + print(f"📋 Arguments: {result['arguments']}") | 
|  | 242 | + else: | 
|  | 243 | + result = { | 
|  | 244 | + "status": "success", | 
|  | 245 | + "content": message.content, | 
|  | 246 | + "note": "No function call triggered", | 
|  | 247 | + "usage": response.usage.model_dump() if response.usage else None | 
|  | 248 | + } | 
|  | 249 | + print(f"✅ Regular response: {result['content']}") | 
|  | 250 | +  | 
|  | 251 | + return result | 
|  | 252 | +  | 
|  | 253 | + except Exception as e: | 
|  | 254 | + print(f"❌ Function calling error: {e}") | 
|  | 255 | + return {"status": "error", "error": str(e)} | 
|  | 256 | +  | 
|  | 257 | + def run_all_tests(self): | 
|  | 258 | + """Run all test scenarios.""" | 
|  | 259 | + print("🚀 Starting comprehensive LiteLLM tracing tests") | 
|  | 260 | + print("=" * 60) | 
|  | 261 | +  | 
|  | 262 | + results = { | 
|  | 263 | + "basic": self.test_basic_completion(), | 
|  | 264 | + "streaming": self.test_streaming_completion(), | 
|  | 265 | + "multi_provider": self.test_multiple_providers(), | 
|  | 266 | + "function_calling": self.test_function_calling(), | 
|  | 267 | + } | 
|  | 268 | +  | 
|  | 269 | + print("\n" + "=" * 60) | 
|  | 270 | + print("📊 Test Summary:") | 
|  | 271 | +  | 
|  | 272 | + for test_name, result in results.items(): | 
|  | 273 | + status = result.get("status", "unknown") | 
|  | 274 | + emoji = "✅" if status == "success" else "❌" | 
|  | 275 | + print(f"{emoji} {test_name}: {status}") | 
|  | 276 | +  | 
|  | 277 | + return results | 
|  | 278 | + | 
|  | 279 | + | 
|  | 280 | +def main(): | 
|  | 281 | + """Main test function.""" | 
|  | 282 | + print("🧪 LiteLLM Tracing Test Suite") | 
|  | 283 | + print("=" * 40) | 
|  | 284 | +  | 
|  | 285 | + # Configuration | 
|  | 286 | + base_url = os.getenv("LITELLM_BASE_URL", "http://localhost:4000") | 
|  | 287 | + api_key = os.getenv("LITELLM_API_KEY", "sk-1234") | 
|  | 288 | + openlayer_base_url = os.getenv("OPENLAYER_BASE_URL", "http://localhost:8080/v1") | 
|  | 289 | +  | 
|  | 290 | + # You can also set OpenLayer configuration | 
|  | 291 | + os.environ.setdefault("OPENLAYER_API_KEY", "sk-ol-vMcEc8O_Tw52HDIF8ihNsiIlzmHLnXxC") | 
|  | 292 | + os.environ.setdefault("OPENLAYER_INFERENCE_PIPELINE_ID", "efefdd4f-12ab-4343-a164-7c10d2d48d61") | 
|  | 293 | +  | 
|  | 294 | + print(f"🔗 LiteLLM Base URL: {base_url}") | 
|  | 295 | + print(f"🏠 OpenLayer Base URL: {openlayer_base_url}") | 
|  | 296 | + print(f"🔑 API Key: {api_key[:8]}...") | 
|  | 297 | +  | 
|  | 298 | + # Initialize tester | 
|  | 299 | + tester = LiteLLMTester(base_url=base_url, api_key=api_key, openlayer_base_url=openlayer_base_url) | 
|  | 300 | +  | 
|  | 301 | + # Run tests | 
|  | 302 | + try: | 
|  | 303 | + results = tester.run_all_tests() | 
|  | 304 | +  | 
|  | 305 | + print("\n🎯 All tests completed!") | 
|  | 306 | + print("Check your OpenLayer dashboard for detailed traces.") | 
|  | 307 | +  | 
|  | 308 | + except KeyboardInterrupt: | 
|  | 309 | + print("\n⏹️ Tests interrupted by user") | 
|  | 310 | + except Exception as e: | 
|  | 311 | + print(f"\n💥 Unexpected error: {e}") | 
|  | 312 | + import traceback | 
|  | 313 | + traceback.print_exc() | 
|  | 314 | + | 
|  | 315 | + | 
|  | 316 | +if __name__ == "__main__": | 
|  | 317 | + main() | 
0 commit comments