what else am I missing?
๐[private user] def trace_model_call(func): """ Decorator to trace model calls with OpenTelemetry. Args: func: The function to trace Returns: Wrapped function with tracing capabilities """ @wraps(func) async def wrapper(*args, **kwargs): start_time = time.time() if tracer: with tracer.start_as_current_span("llm.chat") as span: try: # Extract common parameters messages = kwargs.get("messages", []) prompt = messages[-1]["content"] if messages else kwargs.get("prompt", "") model_name = kwargs.get("model", "unknown") # Import OpenInference semantic conventions from openinference.semconv.trace import SpanAttributes, OpenInferenceSpanKindValues # Set required OpenInference attributes span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, OpenInferenceSpanKindValues.LLM.value) span.set_attribute("llm.model_name", model_name) # Set input and output values (required for useful spans) span.set_attribute(SpanAttributes.INPUT_VALUE, prompt) # Execute model call result = await func(*args, **kwargs) # Extract and set response attributes if hasattr(result, "choices") and result.choices: choice = result.choices[0] response = getattr(choice.message, "content", None) if hasattr(choice, "message") else None finish_reason = getattr(choice, "finish_reason", None) logger.info(f"Tracing: response={response}, finish_reason={finish_reason}") if response is not None: # Set output value using OpenInference attributes span.set_attribute(SpanAttributes.OUTPUT_VALUE, response) else: # Set empty output span.set_attribute(SpanAttributes.OUTPUT_VALUE, "None") else: logger.warning("No choices found in model result.") # Set error output using OpenInference attributes span.set_attribute(SpanAttributes.OUTPUT_VALUE, "No choices") # Set performance metrics duration = time.time() - start_time span.set_attribute("duration_seconds", duration) logger.info(f"Model call traced: {model_name} - Duration: {duration:.2f}s") return result except Exception as e: span.record_exception(e) span.set_status(trace.Status(trace.StatusCode.ERROR)) span.set_attribute("error", str(e)) logger.error(f"Error in model call: {str(e)}") raise else: # Fallback without tracing return await func(*args, **kwargs) return wrapper
๐[private user] whats the min shape of the fields to store something?
does it make sense?
def trace_model_call(self, model_name: str, model_type: str = "llm"): """Decorator to trace AI model calls""" def decorator(func): async def wrapper(*args, **kwargs): with self.tracer.start_as_current_span(f"{model_name}_call") as span: # Add model attributes span.set_attribute("model.name", model_name) span.set_attribute("model.type", model_type) span.set_attribute("model.provider", "openai") # Add input attributes if args and len(args) > 0: prompt = args[0] if isinstance(args[0], str) else str(args[0]) span.set_attribute("model.input_length", len(prompt)) span.set_attribute("model.input_tokens", len(prompt.split())) start_time = trace.get_current_span().get_span_context().trace_id try: result = await func(*args, **kwargs) # Add output attributes if isinstance(result, str): span.set_attribute("model.output_length", len(result)) span.set_attribute("model.output_tokens", len(result.split())) span.set_attribute("model.success", True) return result except Exception as e: span.set_attribute("model.success", False) span.set_attribute("model.error", str(e)) span.record_exception(e) raise return wrapper return decorator def add_span_attribute(self, key: str, value: Any): """Add attribute to current span""" try: current_span = trace.get_current_span() if current_span: current_span.set_attribute(key, str(value)) except Exception as e: logger.warning(f"Failed to add span attribute: {str(e)}") def record_event(self, name: str, attributes: Dict[str, Any] = None): """Record an event in the current span""" try: current_span = trace.get_current_span() if current_span: current_span.add_event(name, attributes or {}) except Exception as e: logger.warning(f"Failed to record event: {str(e)}") def record_exception(self, exception: Exception, attributes: Dict[str, Any] = None): """Record an exception in the current span""" try: current_span = trace.get_current_span() if current_span: current_span.record_exception(exception, attributes or {}) except Exception as e: logger.warning(f"Failed to record exception: {str(e)}") def get_tracer(self): """Get the tracer instance""" return self.tracer def test_connection(self): """Test Arize connection by sending a test span""" try: with self.tracer.start_as_current_span("test_arize_connection") as span: span.set_attribute("test.attribute", "Hello Arize!") span.set_attribute("test.timestamp", str(os.getenv("TIMESTAMP", "now"))) logger.info("Test span sent to Arize. Check your dashboard at app.arize.com") return True except Exception as e: logger.error(f"Failed to test Arize connection: {str(e)}") return False def shutdown(self): """Shutdown Arize telemetry""" try: self.tracer_provider.shutdown() logger.info("Arize telemetry shutdown completed") except Exception as e: logger.error(f"Error shutting down Arize telemetry: {str(e)}")
""" Arize Provider Handles all Arize telemetry and monitoring operations """ import os import logging from typing import Dict, Any, Optional from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.resources import Resource from ..config.settings import ARIZE_API_KEY, ARIZE_SPACE_KEY from ..utils.exceptions import ConfigurationException logger = logging.getLogger(__name__) ANSI_ORANGE = "\033[38;5;208m" ANSI_RESET = "\033[0m" ANSI_PURPLE = "\033[38;5;135m" class ArizeProvider: """Provider for Arize telemetry operations""" def __init__(self, api_key: str = None, space_id: str = None, project_name: str = None): # Prefer explicit env overrides used by Arize self.api_key = api_key or os.getenv("ARIZE_API_KEY") or ARIZE_API_KEY # Use ARIZE_SPACE_ID instead of ARIZE_SPACE_KEY (which may be misused elsewhere) self.space_id = space_id or os.getenv("ARIZE_SPACE_ID") or ARIZE_SPACE_KEY # Prefer ARIZE_SPACE_NAME; fallback to ARIZE_PROJECT_NAME; final fallback to previous string self.project_name = ( project_name or os.getenv("ARIZE_SPACE_NAME") or os.getenv("ARIZE_PROJECT_NAME", "immediatetiger-chatbots") ) self.model_id = os.getenv("ARIZE_MODEL_ID", "TW9kZWw6NDA2NTE4MTI1MzpmNE4z") if not self.api_key: raise ConfigurationException( "Arize API key not provided", error_code="MISSING_ARIZE_API_KEY" ) if not self.space_id: raise ConfigurationException( "Arize Space ID not provided", error_code="MISSING_ARIZE_SPACE_KEY" ) # Initialize Arize telemetry try: self._setup_telemetry() logger.info("Arize provider initialized successfully") except Exception as e: logger.error(f"Failed to initialize Arize provider: {str(e)}") raise ConfigurationException( f"Failed to initialize Arize provider: {str(e)}", error_code="INITIALIZATION_FAILED" ) def _setup_telemetry(self): """Setup OpenTelemetry with Arize configuration""" # Create OTLP exporter for Arize self.otlp_exporter = OTLPSpanExporter( endpoint="https://otlp.arize.com/v1", headers={ "authorization": f"Bearer {self.api_key}", "x-space-id": self.space_id, "x-project-name": self.project_name, "x-model-id": self.model_id, } ) # Create resource resource = Resource.create({ "service.name": self.project_name, "service.version": "1.0.0", "deployment.environment": os.getenv("ENVIRONMENT", "development"), "external.model.id": self.model_id, "model.name": self.project_name, }) # Create tracer provider self.tracer_provider = TracerProvider(resource=resource) # Add Arize exporter to the tracer provider self.tracer_provider.add_span_processor( BatchSpanProcessor(self.otlp_exporter) ) # Add logging exporter to print everything we send and the result from Arize self.tracer_provider.add_span_processor( BatchSpanProcessor(self._create_logging_exporter(self.otlp_exporter)) ) # Set the tracer provider as the global default trace.set_tracer_provider(self.tracer_provider) # Create tracer self.tracer = trace.get_tracer(self.project_name) logger.info("Arize telemetry setup completed") def _create_logging_exporter(self, inner_exporter): """Create a wrapper exporter that logs spans and the export result.""" from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult # Capture config from the provider for use inside the inner class api_key = self.api_key space_id = self.space_id project_name = self.project_name model_id = self.model_id class LoggingExporter(SpanExporter): def export(self, spans): try: # Headers preview (masked) printed in purple for visibility masked_key = (api_key[:6] + "โฆ" + api_key[-6:]) if isinstance(api_key, str) and len(api_key) > 12 else "***" logger.info(f"{ANSI_PURPLE}๐ headers: authorization=Bearer {masked_key}, x-space-id={space_id}, x-project-name={project_name}, x-model-id={model_id}{ANSI_RESET}") logger.info(f"{ANSI_PURPLE}๐ Preparing to send spans to Arize{ANSI_RESET}") logger.info(f"{ANSI_PURPLE}๐ Number of spans: {len(spans)}{ANSI_RESET}") for i, span in enumerate(spans, start=1): try: logger.info(f"{ANSI_PURPLE}๐ Span {i}: {getattr(span, 'name', 'unknown')}{ANSI_RESET}") ctx = getattr(span, 'context', None) if ctx: logger.info(f"{ANSI_PURPLE} ๐ trace_id={ctx.trace_id} span_id={ctx.span_id}{ANSI_RESET}") logger.info(f"{ANSI_PURPLE} ๐ท๏ธ attributes={dict(getattr(span, 'attributes', {}))}{ANSI_RESET}") res = getattr(span, 'resource', None) if res and getattr(res, 'attributes', None): logger.info(f"{ANSI_PURPLE} ๐ฆ resource={dict(res.attributes)}{ANSI_RESET}") except Exception as e: logger.warning(f"Failed to log span {i}: {e}") result = inner_exporter.export(spans) # Highlight the entire Arize export response in orange for visibility logger.info(f"{ANSI_ORANGE}โ Arize export result: {result}{ANSI_RESET}") logger.info(f"{ANSI_ORANGE}๐ Full result object: {result.__dict__ if hasattr(result, '__dict__') else result}{ANSI_RESET}") logger.info(f"{ANSI_ORANGE}๐ Result type: {type(result)}{ANSI_RESET}") if hasattr(result, 'status_code'): logger.info(f"{ANSI_ORANGE}๐ Status code: {result.status_code}{ANSI_RESET}") if hasattr(result, 'success'): logger.info(f"{ANSI_ORANGE}๐ Success: {result.success}{ANSI_RESET}") return result if isinstance(result, SpanExportResult) else SpanExportResult.SUCCESS except Exception as e: logger.error(f"โ Error exporting/logging spans: {e}") return SpanExportResult.FAILURE def shutdown(self): try: if hasattr(inner_exporter, 'shutdown'): inner_exporter.shutdown() except Exception as e: logger.warning(f"LoggingExporter shutdown warning: {e}") return LoggingExporter() def trace_function(self, function_name: str, attributes: Dict[str, Any] = None): """Decorator to trace function calls""" def decorator(func): def wrapper(*args, **kwargs): with self.tracer.start_as_current_span(function_name) as span: # Add function attributes if attributes: for key, value in attributes.items(): span.set_attribute(key, str(value)) # Add function arguments as attributes span.set_attribute("function.args_count", len(args)) span.set_attribute("function.kwargs_count", len(kwargs)) try: result = func(*args, **kwargs) span.set_attribute("function.success", True) return result except Exception as e: span.set_attribute("function.success", False) span.set_attribute("function.error", str(e)) span.record_exception(e) raise return wrapper return decorator def trace_async_function(self, function_name: str, attributes: Dict[str, Any] = None): """Decorator to trace async function calls""" def decorator(func): async def wrapper(*args, **kwargs): with self.tracer.start_as_current_span(function_name) as span: # Add function attributes if attributes: for key, value in attributes.items(): span.set_attribute(key, str(value)) # Add function arguments as attributes span.set_attribute("function.args_count", len(args)) span.set_attribute("function.kwargs_count", len(kwargs)) try: result = await func(*args, **kwargs) span.set_attribute("function.success", True) return result except Exception as e: span.set_attribute("function.success", False) span.set_attribute("function.error", str(e)) span.record_exception(e) raise return wrapper return decorator
look dates
these are old ones
zero spans under my project
