Span Data Models
================

.. note::
   **Technical specification for HoneyHive span data structures**
   
   This document defines the exact data models and formats used for spans in the HoneyHive SDK, which follow OpenTelemetry specifications.

Spans represent units of work in a distributed trace, providing detailed timing and context information for operations in your LLM application.

Core Span Model
---------------

.. py:class:: Span

   The primary span data structure based on OpenTelemetry standards.

   .. py:attribute:: span_id
      :type: str

      Unique identifier for this span.

      **Format**: 16-character hexadecimal string (8 bytes)
      **Example**: ``"a1b2c3d4e5f6g7h8"``
      **Required**: Auto-generated by SDK

   .. py:attribute:: trace_id
      :type: str

      Unique identifier for the entire trace.

      **Format**: 32-character hexadecimal string (16 bytes)
      **Example**: ``"1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p"``
      **Required**: Auto-generated by SDK

   .. py:attribute:: parent_span_id
      :type: Optional[str]

      Parent span identifier for nested operations.

      **Format**: 16-character hexadecimal string (8 bytes)
      **Example**: ``"b2c3d4e5f6g7h8i9"``
      **Required**: No (None for root spans)

   .. py:attribute:: operation_name
      :type: str

      Name of the operation represented by this span.

      **Format**: Descriptive string, typically kebab-case
      **Example**: ``"llm-chat-completion"``
      **Required**: Yes

   .. py:attribute:: start_time
      :type: int

      Span start time in nanoseconds since Unix epoch.

      **Format**: 64-bit integer (nanoseconds)
      **Example**: ``1642253445123456789``
      **Required**: Auto-generated by SDK

   .. py:attribute:: end_time
      :type: Optional[int]

      Span end time in nanoseconds since Unix epoch.

      **Format**: 64-bit integer (nanoseconds)
      **Example**: ``1642253447654321987``
      **Required**: Auto-generated when span ends

   .. py:attribute:: duration_ns
      :type: Optional[int]

      Span duration in nanoseconds.

      **Calculation**: ``end_time - start_time``
      **Example**: ``2530865198``
      **Required**: Auto-calculated by SDK

   .. py:attribute:: status
      :type: SpanStatus

      Span completion status.

      **Structure**:
      
      .. code-block:: json
      
         {
           "code": "OK",
           "message": "Operation completed successfully"
         }

      **Status Codes**:
      - ``"UNSET"`` - Default status
      - ``"OK"`` - Operation completed successfully  
      - ``"ERROR"`` - Operation failed

      **Required**: Auto-determined by SDK

   .. py:attribute:: attributes
      :type: Dict[str, Union[str, int, float, bool]]

      Key-value pairs providing additional context.

      **Restrictions**: 
      - Keys must be strings
      - Values must be primitives (string, int, float, bool)
      - Arrays of primitives are also supported

      **Example**:
      
      .. code-block:: json
      
         {
           "llm.model": "gpt-3.5-turbo",
           "llm.provider": "openai",
           "llm.temperature": 0.7,
           "llm.max_tokens": 150,
           "llm.streaming": false,
           "http.status_code": 200,
           "operation.timeout_ms": 30000
         }

      **Required**: No

   .. py:attribute:: events
      :type: List[SpanEvent]

      Timestamped events that occurred during the span.

      **Structure**: List of :py:class:`SpanEvent` objects
      **Required**: No

   .. py:attribute:: links
      :type: List[SpanLink]

      Links to other spans (causally related).

      **Structure**: List of :py:class:`SpanLink` objects
      **Required**: No

   .. py:attribute:: resource
      :type: Resource

      Resource information (service, instance, etc.).

      **Structure**: :py:class:`Resource` object
      **Required**: Yes (auto-populated by SDK)

   .. py:attribute:: instrumentation_scope
      :type: InstrumentationScope

      Information about the instrumentation library.

      **Structure**: :py:class:`InstrumentationScope` object
      **Required**: Yes (auto-populated by SDK)

Span Event Model
----------------

.. py:class:: SpanEvent

   Represents a timestamped event within a span.

   .. py:attribute:: name
      :type: str

      Event name.

      **Example**: ``"request_started"``, ``"cache_miss"``, ``"retry_attempt"``
      **Required**: Yes

   .. py:attribute:: timestamp
      :type: int

      Event timestamp in nanoseconds since Unix epoch.

      **Format**: 64-bit integer (nanoseconds)
      **Example**: ``1642253445500000000``
      **Required**: Yes

   .. py:attribute:: attributes
      :type: Optional[Dict[str, Union[str, int, float, bool]]]

      Event-specific attributes.

      **Example**:
      
      .. code-block:: json
      
         {
           "retry.attempt": 2,
           "retry.reason": "rate_limit",
           "retry.delay_ms": 1000
         }

      **Required**: No

**Example Span Event**:

.. code-block:: json

   {
     "name": "llm_response_received",
     "timestamp": 1642253446123456789,
     "attributes": {
       "response.token_count": 42,
       "response.finish_reason": "stop",
       "response.cached": false
     }
   }

Span Link Model
---------------

.. py:class:: SpanLink

   Links this span to another span (potentially in a different trace).

   .. py:attribute:: trace_id
      :type: str

      Trace ID of the linked span.

      **Format**: 32-character hexadecimal string
      **Required**: Yes

   .. py:attribute:: span_id
      :type: str

      Span ID of the linked span.

      **Format**: 16-character hexadecimal string
      **Required**: Yes

   .. py:attribute:: attributes
      :type: Optional[Dict[str, Union[str, int, float, bool]]]

      Link-specific attributes.

      **Example**:
      
      .. code-block:: json
      
         {
           "link.type": "follows_from",
           "link.description": "Async callback"
         }

      **Required**: No

Resource Model
--------------

.. py:class:: Resource

   Represents the entity producing telemetry.

   .. py:attribute:: attributes
      :type: Dict[str, Union[str, int, float, bool]]

      Resource attributes following OpenTelemetry semantic conventions.

      **Common Attributes**:
      
      .. code-block:: json
      
         {
           "service.name": "my-llm-app",
           "service.version": "1.2.0",
           "service.instance.id": "instance-001",
           "deployment.environment": "production",
           "host.name": "server-01",
           "process.pid": 12345,
           "telemetry.sdk.name": "honeyhive",
           "telemetry.sdk.version": "0.1.0",
           "telemetry.sdk.language": "python"
         }

      **Required**: Yes (auto-populated by SDK)

Instrumentation Scope Model
---------------------------

.. py:class:: InstrumentationScope

   Information about the instrumentation library that created the span.

   .. py:attribute:: name
      :type: str

      Name of the instrumentation library.

      **Example**: ``"honeyhive-python"``
      **Required**: Yes

   .. py:attribute:: version
      :type: Optional[str]

      Version of the instrumentation library.

      **Example**: ``"0.1.0"``
      **Required**: No

   .. py:attribute:: schema_url
      :type: Optional[str]

      Schema URL for semantic conventions.

      **Example**: ``"https://opentelemetry.io/schemas/1.21.0"``
      **Required**: No

HoneyHive Span Extensions
-------------------------

In addition to standard OpenTelemetry fields, HoneyHive adds specialized attributes for LLM observability:

**LLM Attributes**:

.. code-block:: json

   {
     "llm.provider": "openai",
     "llm.model": "gpt-3.5-turbo-0613", 
     "llm.temperature": 0.7,
     "llm.max_tokens": 150,
     "llm.top_p": 1.0,
     "llm.frequency_penalty": 0.0,
     "llm.presence_penalty": 0.0,
     "llm.streaming": false,
     "llm.function_call": "auto",
     "llm.tools_count": 3
   }

**Token Usage Attributes**:

.. code-block:: json

   {
     "llm.usage.prompt_tokens": 50,
     "llm.usage.completion_tokens": 75,
     "llm.usage.total_tokens": 125,
     "llm.usage.cache_hit_tokens": 20,
     "llm.usage.cache_miss_tokens": 30
   }

**Cost Attributes**:

.. code-block:: json

   {
     "llm.cost.prompt_cost_usd": 0.0001,
     "llm.cost.completion_cost_usd": 0.00015,
     "llm.cost.total_cost_usd": 0.00025,
     "llm.cost.currency": "USD"
   }

**Request/Response Attributes**:

.. code-block:: json

   {
     "llm.request.type": "chat",
     "llm.request.message_count": 3,
     "llm.request.system_message": true,
     "llm.response.finish_reason": "stop",
     "llm.response.choice_count": 1,
     "llm.response.logprobs": false
   }

**Error Attributes**:

.. code-block:: json

   {
     "error.type": "RateLimitError",
     "error.message": "Rate limit exceeded",
     "error.code": "rate_limit_exceeded", 
     "error.retry_after_s": 60,
     "error.request_id": "req_abc123"
   }

**Performance Attributes**:

.. code-block:: json

   {
     "performance.latency_ms": 1250.5,
     "performance.queue_time_ms": 45.2,
     "performance.processing_time_ms": 1205.3,
     "performance.tokens_per_second": 60.8,
     "performance.cache_hit_rate": 0.75
   }

**User/Session Attributes**:

.. code-block:: json

   {
     "user.id": "user_12345",
     "user.tier": "premium",
     "session.id": "session_abcdef",
     "session.turn": 3,
     "conversation.id": "conv_xyz789"
   }

Span Context Model
------------------

.. py:class:: SpanContext

   Represents the portion of a span that must be propagated to child spans.

   .. py:attribute:: trace_id
      :type: str

      Trace identifier.

      **Format**: 32-character hexadecimal string
      **Required**: Yes

   .. py:attribute:: span_id
      :type: str

      Span identifier.

      **Format**: 16-character hexadecimal string  
      **Required**: Yes

   .. py:attribute:: trace_flags
      :type: int

      Trace flags (typically 0x01 for sampled).

      **Values**: 8-bit integer
      **Required**: Yes

   .. py:attribute:: trace_state
      :type: Optional[str]

      Vendor-specific trace state.

      **Format**: Comma-separated key-value pairs
      **Example**: ``"honeyhive=abc123,vendor2=xyz789"``
      **Required**: No

   .. py:attribute:: is_remote
      :type: bool

      Whether this context was propagated from a remote parent.

      **Required**: Yes

Complete Span Example
---------------------

**Full LLM Span**:

.. code-block:: json

   {
     "span_id": "a1b2c3d4e5f6g7h8",
     "trace_id": "1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p",
     "parent_span_id": "b2c3d4e5f6g7h8i9",
     "operation_name": "openai-chat-completion",
     "start_time": 1642253445123456789,
     "end_time": 1642253447654321987,
     "duration_ns": 2530865198,
     "status": {
       "code": "OK",
       "message": "Request completed successfully"
     },
     "attributes": {
       "llm.provider": "openai",
       "llm.model": "gpt-3.5-turbo",
       "llm.temperature": 0.7,
       "llm.max_tokens": 150,
       "llm.usage.prompt_tokens": 50,
       "llm.usage.completion_tokens": 75,
       "llm.usage.total_tokens": 125,
       "llm.cost.total_cost_usd": 0.00025,
       "http.method": "POST",
       "http.url": "https://api.openai.com/v1/chat/completions",
       "http.status_code": 200,
       "user.id": "user_12345",
       "session.id": "session_abcdef"
     },
     "events": [
       {
         "name": "request_started",
         "timestamp": 1642253445123456789,
         "attributes": {
           "request.size_bytes": 1024
         }
       },
       {
         "name": "response_received", 
         "timestamp": 1642253447600000000,
         "attributes": {
           "response.size_bytes": 2048,
           "response.cached": false
         }
       }
     ],
     "links": [],
     "resource": {
       "attributes": {
         "service.name": "my-llm-app",
         "service.version": "1.2.0",
         "deployment.environment": "production",
         "telemetry.sdk.name": "honeyhive",
         "telemetry.sdk.version": "0.1.0"
       }
     },
     "instrumentation_scope": {
       "name": "honeyhive-python",
       "version": "0.1.0",
       "schema_url": "https://opentelemetry.io/schemas/1.21.0"
     }
   }

Trace Hierarchy Example
-----------------------

**Parent-Child Span Relationship**:

.. code-block:: json

   {
     "trace_id": "1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p",
     "spans": [
       {
         "span_id": "root00000000",
         "parent_span_id": null,
         "operation_name": "rag-pipeline",
         "attributes": {
           "pipeline.type": "rag",
           "pipeline.version": "v2"
         }
       },
       {
         "span_id": "search000000",
         "parent_span_id": "root00000000", 
         "operation_name": "vector-search",
         "attributes": {
           "search.query": "What is machine learning?",
           "search.top_k": 5,
           "search.similarity_threshold": 0.8
         }
       },
       {
         "span_id": "llm000000000",
         "parent_span_id": "root00000000",
         "operation_name": "llm-generation", 
         "attributes": {
           "llm.provider": "openai",
           "llm.model": "gpt-3.5-turbo",
           "llm.context_length": 4096
         }
       }
     ]
   }

Span Sampling
-------------

**Sampling Decision**:

Spans can be sampled based on various criteria:

.. code-block:: json

   {
     "trace_flags": 1,
     "sampling": {
       "decision": "RECORD_AND_SAMPLE",
       "probability": 0.1,
       "reason": "TraceIdRatioBasedSampler",
       "attributes": {
         "sampling.rule": "high_value_users",
         "sampling.tier": "premium"
       }
     }
   }

**Sampling Strategies**:

- **Rate-based**: Sample a percentage of traces
- **Adaptive**: Adjust sampling based on traffic volume
- **Rule-based**: Sample based on attributes (user tier, error status)
- **Tail-based**: Sample entire traces based on downstream criteria

Span Export Format
------------------

**OTLP Format** (OpenTelemetry Protocol):

The SDK supports both Protobuf (default) and JSON formats for OTLP export.
Set the ``HH_OTLP_PROTOCOL`` environment variable to ``http/json`` to use JSON format,
or ``http/protobuf`` (default) for Protobuf format.

.. code-block:: json

   {
     "resource_spans": [
       {
         "resource": {
           "attributes": [
             {"key": "service.name", "value": {"string_value": "my-service"}}
           ]
         },
         "scope_spans": [
           {
             "scope": {
               "name": "honeyhive-python",
               "version": "0.1.0"
             },
             "spans": [
               {
                 "trace_id": "base64EncodedTraceId",
                 "span_id": "base64EncodedSpanId", 
                 "name": "operation-name",
                 "start_time_unix_nano": 1642253445123456789,
                 "end_time_unix_nano": 1642253447654321987,
                 "attributes": [
                   {"key": "llm.model", "value": {"string_value": "gpt-3.5-turbo"}}
                 ]
               }
             ]
           }
         ]
       }
     ]
   }

**HoneyHive Format** (Enhanced):

.. code-block:: json

   {
     "spans": [
       {
         "span_id": "a1b2c3d4e5f6g7h8",
         "trace_id": "1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p",
         "operation_name": "llm-call",
         "honeyhive": {
           "project": "my-project",
           "session_id": "session_abc",
           "event_id": "event_123",
           "evaluation_scores": {
             "relevance": 0.85,
             "accuracy": 0.92
           }
         }
       }
     ]
   }

Best Practices
--------------

**Span Design Guidelines**:

1. **Meaningful Names**: Use descriptive operation names that clearly indicate what work is being done
2. **Appropriate Granularity**: Create spans for significant operations, avoid over-instrumentation
3. **Rich Attributes**: Add relevant attributes that aid in debugging and analysis
4. **Error Handling**: Always set error status and attributes when operations fail
5. **Resource Attribution**: Include user, session, and business context
6. **Performance Metrics**: Capture relevant timing and throughput metrics

**Attribute Naming**:

Follow OpenTelemetry semantic conventions:

- Use lowercase with underscores: ``llm.model``, ``http.status_code``
- Namespace related attributes: ``llm.*``, ``db.*``, ``http.*``
- Use consistent units: ``_ms`` for milliseconds, ``_bytes`` for bytes
- Avoid sensitive data: Don't include API keys, passwords, PII

**Performance Considerations**:

1. **Attribute Limits**: Be mindful of attribute count and size limits
2. **Event Usage**: Use events sparingly for significant occurrences only
3. **Link Usage**: Use links judiciously to avoid circular references
4. **Sampling**: Implement appropriate sampling to control data volume

See Also
--------

- :doc:`events` - Event data models and formats
- :doc:`evaluations` - Evaluation data structures  
- :doc:`../api/tracer` - HoneyHiveTracer API for creating spans
- :doc:`../configuration/environment-vars` - Span configuration options
