> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freeplay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenTelemetry

> Capture LLM observability data using OpenTelemetry for framework-agnostic tracing.

# Overview

Building LLM applications with modern frameworks is great—until you need to understand what's actually happening under the hood. Traditional observability tools weren't built for the nuances of LLM interactions: token counts, model parameters, prompt templates, tool calls, and multi-step agent workflows.

That's where OpenTelemetry (OTel) comes in. By integrating Freeplay with OTel, you get purpose-built LLM observability that works with any framework or orchestration approach. Whether you're using Langgraph, building custom agents, or mixing frameworks, Freeplay captures what matters—automatically.

Freeplay uses OpenTelemetry as a protocol to record LLM observability data. It's not intended to provide general application telemetry for non-LLM related code, so sending arbitrary telemetry to Freeplay will not work. Freeplay supports traces that conform to the OpenInference semantic conventions.

## Why OTel + Freeplay?

* **Framework flexibility**: Your team uses Langgraph. Another team built custom agents. A third is evaluating Google ADK. With OTel, one integration supports all of them.
* **LLM-native insights**: We automatically capture model parameters, token counts, tool schemas, and prompt templates—the data you actually need to improve your AI applications.
* **No architectural changes**: Freeplay observes your orchestration logic without becoming part of it. Your code stays clean, your flexibility stays intact.
* **Built on standards**: OpenInference semantic conventions mean your instrumentation is portable and future-proof.

<Warning>
  **Freeplay focuses on LLM observability**

  We only record traces and spans containing meaningful LLM information per OpenInference semantic conventions—not general application telemetry.

  **Recommended approach:**

  1. Use OpenInference instrumentation libraries when available (easiest option)
  2. Follow OpenInference semantic conventions for custom instrumentation
  3. For advanced control, use our OTel-compliant API directly

  If your framework lacks an OpenInference library, you can still record data using standard OpenInference attributes like `input.value`, `output.value`, and `gen_ai.request.model`. See the [supported attributes reference](#supported-attributes-reference) below.
</Warning>

## Getting Started

### Step 1: Install dependencies

<CodeGroup>
  ```bash theme={null}
  pip install freeplay opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp
  ```
</CodeGroup>

### Step 2: Configure the OTel exporter

Set up Freeplay as your OTel span processing endpoint:

<CodeGroup>
  ```python python theme={null}
  from opentelemetry.sdk.trace.export import SimpleSpanProcessor
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  import os

  freeplay_api_url = "https://app.freeplay.ai/api"

  exporter = OTLPSpanExporter(
      endpoint=f"{freeplay_api_url}/v0/otel/v1/traces",
      headers={
          "Authorization": f"Bearer {os.environ['FREEPLAY_API_KEY']}",
          "X-Freeplay-Project-Id": os.environ["FREEPLAY_PROJECT_ID"],
      },
  )

  tracer_provider = trace_sdk.TracerProvider()
  tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
  ```
</CodeGroup>

<Info>
  **Getting your credentials:**

  * API Key: [Freeplay Settings → API Access](https://app.freeplay.ai/settings/api-access)
  * Project ID: Found in your project settings
</Info>

### Step 3: Instrument your application

We recommend using OpenInference instrumentation libraries when available. For example, with Google ADK:

<CodeGroup>
  ```python python theme={null}
  from google.adk.instrumentation import GoogleADKInstrumentor

  GoogleADKInstrumentor().instrument(tracer_provider=tracer_provider)
  ```
</CodeGroup>

That's it! Your LLM interactions are now being captured and sent to Freeplay.

### Step 4: Enable debugging (optional but recommended)

During development, print spans to your console for easier debugging:

<CodeGroup>
  ```python python theme={null}
  from opentelemetry.sdk.trace.export import ConsoleSpanExporter

  tracer_provider.add_span_processor(
      SimpleSpanProcessor(ConsoleSpanExporter())
  )
  ```
</CodeGroup>

## Framework examples

### Langgraph/Langchain

### [Langgraph/Langchain Integration](https://colab.research.google.com/drive/1PzB4DoN_YVcwj2RrWas93cH8UOCHdvxG?usp=sharing)

[Complete end-to-end example integrating OTel with Langgraph](https://colab.research.google.com/drive/1PzB4DoN_YVcwj2RrWas93cH8UOCHdvxG?usp=sharing)

### Custom instrumentation

If you're building with a framework that doesn't have an OpenInference instrumentation library, you can manually instrument your code following OpenInference conventions:

<CodeGroup>
  ```python python theme={null}
  from opentelemetry import trace

  tracer = trace.get_tracer(__name__)

  with tracer.start_as_current_span("llm_call") as span:
      # Set OpenInference attributes
      span.set_attribute("openinference.span.kind", "LLM")
      span.set_attribute("gen_ai.request.model", "gpt-4")
      span.set_attribute("input.value", user_query)

      # Make your LLM call
      response = your_llm_call(user_query)

      # Record output and token usage
      span.set_attribute("output.value", response)
      span.set_attribute("gen_ai.usage.input_tokens", token_count.input)
      span.set_attribute("gen_ai.usage.output_tokens", token_count.output)
  ```
</CodeGroup>

<Warning>
  Your span must have a kind value of either `SPAN_KIND_INTERNAL` or `SPAN_KIND_SERVER`.

  Your spans must have an attribute named `openinference.span.kind` with a value of `LLM`, `AGENT`, `CHAIN`, or `TOOL`.

  Other spans will be ignored.

  Freeplay only records data data described below, not general application telemetry.
</Warning>

## Key concepts

### Span types

Freeplay processes different span types based on `openinference.span.kind`:

* **LLM**: Direct LLM API calls with prompts, completions, and token usage
* **AGENT**: Higher-level agentic workflows with decision-making
* **CHAIN**: Sequential operations or pipelines
* **TOOL**: External tool or function calls

Set the appropriate span kind to ensure Freeplay correctly interprets your traces.

### Sessions and traces

Use session and trace identifiers to organize related interactions:

<CodeGroup>
  ```python python theme={null}
  # Link multiple traces to the same conversation session
  span.set_attribute("session.id", session_id)

  # Or use Freeplay-specific attributes (takes precedence)

  span.set_attribute("freeplay.session.id", session_id)

  # Name your agent for better organization

  span.set_attribute("agent.name", "customer_support_agent")
  ```
</CodeGroup>

This enables you to track multi-turn conversations and group related agent runs in Freeplay's observability UI.

### Environment attribute

You should add environment attribute to your spans that corresponds with the
prompt template environment that you are using. For example, if you are using a
prompt template in the "production" environment, you should set the environment
attribute to "production".

<CodeGroup>
  ```python python theme={null}
  span.set_attribute("freeplay.environment", "production")
  # or "staging", "development", etc.
  ```
</CodeGroup>

## Supported attributes reference

### OpenInference attributes

Freeplay maps OpenInference attributes to internal fields for consistent observability. These are the standard attributes you should use when instrumenting your LLM applications:

| OTEL Attribute                  | Freeplay Field                               | Payload Type  | Notes                                              |
| ------------------------------- | -------------------------------------------- | ------------- | -------------------------------------------------- |
| `openinference.span.kind`       | N/A                                          | RecordPayload | Determines span type (LLM, AGENT, CHAIN, TOOL)     |
| `gen_ai.request.model`          | `completion.model_name`                      | RecordPayload | LLM model name                                     |
| `llm.model_name`                | `completion.model_name`                      | RecordPayload | Alternative LLM model name                         |
| `gen_ai.usage.input_tokens`     | `completion.token_counts.prompt_token_count` | RecordPayload | Input token count                                  |
| `llm.token_count.prompt`        | `completion.token_counts.prompt_token_count` | RecordPayload | Alternative input token count                      |
| `gen_ai.usage.output_tokens`    | `completion.token_counts.return_token_count` | RecordPayload | Output token count                                 |
| `llm.token_count.completion`    | `completion.token_counts.return_token_count` | RecordPayload | Alternative output token count                     |
| `llm.provider`                  | `completion.provider_name`                   | RecordPayload | LLM provider (openai, anthropic, google)           |
| `gen_ai.system`                 | `completion.provider_name`                   | RecordPayload | Alternative provider identifier                    |
| `llm.input_messages.*`          | `completion.api_messages`                    | RecordPayload | Input messages (flattened array format)            |
| `llm.output_messages.*`         | `completion.api_messages`                    | RecordPayload | Output messages (flattened array format)           |
| `llm.tools.*`                   | `completion.tool_schema`                     | RecordPayload | Tool/function definitions                          |
| `llm.prompt_template.variables` | `completion.inputs`                          | RecordPayload | Variables applied to template (merged into inputs) |
| `llm.invocation_parameters`     | `completion.llm_parameters`                  | RecordPayload | Parameters used during LLM invocation (JSON)       |
| `input.value`                   | `trace.input`                                | TracePayload  | Trace input data                                   |
| `output.value`                  | `trace.output`                               | TracePayload  | Trace output data                                  |
| `tool.name`                     | `trace.name`                                 | TracePayload  | Tool span name                                     |
| `agent.name`                    | `trace.agent_name`                           | TracePayload  | Agent span name                                    |
| `session.id`                    | `completion.session_id`                      | RecordPayload | Session identifier                                 |
| `session.id`                    | `trace.session_id`                           | TracePayload  | Session identifier                                 |

### Freeplay-specific attributes

Enhance your traces with Freeplay-specific metadata for tighter integration with Freeplay features:

| OTEL Attribute                        | Freeplay Field                          | Payload Type  | Notes                                                    |
| ------------------------------------- | --------------------------------------- | ------------- | -------------------------------------------------------- |
| `freeplay.session.id`                 | `completion.session_id`                 | RecordPayload | Freeplay session ID (takes precedence over `session.id`) |
| `freeplay.session.id`                 | `trace.session_id`                      | TracePayload  | Freeplay session ID (takes precedence over `session.id`) |
| `freeplay.environment`                | `completion.tag`                        | RecordPayload | Environment tag (e.g., "production", "staging")          |
| `freeplay.prompt_template.id`         | `completion.prompt_template_id`         | RecordPayload | Link to Freeplay prompt template                         |
| `freeplay.prompt_template.version.id` | `completion.prompt_template_version_id` | RecordPayload | Specific prompt template version                         |
| `freeplay.test_run.id`                | `completion.test_run_id`                | RecordPayload | Associate with test run                                  |
| `freeplay.test_case.id`               | `completion.test_case_id`               | RecordPayload | Associate with specific test case                        |
| `freeplay.input_variables`            | `completion.inputs`                     | RecordPayload | Input variables (JSON)                                   |
| `freeplay.metadata.*`                 | `completion.custom_metadata`            | RecordPayload | Custom metadata (flattened dict format)                  |
| `freeplay.eval_results.*`             | `completion.eval_results`               | RecordPayload | Evaluation results (flattened dict format)               |

## Next steps

Once you've integrated OTel with Freeplay, you can:

* **View traces** in the [Freeplay observability dashboard](https://app.freeplay.ai)
* **Run evaluations** on your logged interactions to measure quality
* **Build datasets** from production traces for systematic testing
* **Monitor performance** across different model versions and configurations
* **Track costs** with automatic token usage and cost calculations

## Additional resources

* [OpenInference Semantic Conventions](https://github.com/Arize-ai/openinference)
* [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
* [Freeplay Sessions, Traces, and Completions Guide](/core-concepts/observability/sessions-traces-and-completions)

***
