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

# Manual LLM instrumentation

> How to manually construct spans in compliance with the OpenTelemetry specification

## Overview

Gentrace automatically collects spans from compliant automatic instrumentations, for example from the
[AI SDK](../integrations/ai-sdk) or [Pydantic AI SDK](../integrations/pydantic-ai).

Where automatic instrumentations are not available, you can manually construct spans in compliance with the
[OpenTelemetry specification](https://opentelemetry.io/docs/specs/semconv/gen-ai/).

For full details of the attributes and events supported by Gentrace, see our [OpenTelemetry API reference](/reference/opentelemetry-api).

## Example

<CodeGroup dropdown>
  ```typescript theme={null}
  import { trace } from '@opentelemetry/api';
  import OpenAI from 'openai';

  const tracer = trace.getTracer('my-app-tracer');
  const openai = new OpenAI();

  const span = tracer.startSpan(`chat o4-mini`, {
    attributes: {
      'gen_ai.operation.name': 'chat',
      'gen_ai.provider.name': 'openai',
      'gen_ai.request.model': 'o4-mini',
      'gen_ai.request.max_tokens': 1000,
    },
  });

  const messages = [{ role: 'user', content: 'Hello, world!' }];

  span.addEvent('gen_ai.user.message', {
    content: messages[0].content,
  });

  const response = await openai.chat.completions.create({
    model: 'o4-mini',
    messages,
  });

  const choice = response.choices[0];
  const completion = choice.message.content;

  span.addEvent('gen_ai.choice', {
    index: choice.index,
    finish_reason: choice.finish_reason,
    'message.content': completion,
    'message.role': choice.message.role,
  });

  span.setAttributes({
    'gen_ai.usage.input_tokens': response.usage.prompt_tokens,
    'gen_ai.usage.output_tokens': response.usage.completion_tokens,
    'gen_ai.response.finish_reasons': ['stop'],
  });

  span.end();
  ```

  ```python theme={null}
  import openai
  from opentelemetry import trace

  tracer = trace.get_tracer(__name__)

  with tracer.start_as_current_span(
      "chat o4-mini",
      attributes={
          "gen_ai.operation.name": "chat",
          "gen_ai.provider.name": "openai",
          "gen_ai.request.model": "o4-mini",
          "gen_ai.request.max_tokens": 1000,
      },
  ) as span:
      messages = [{"role": "user", "content": "Hello, world!"}]

      span.add_event(
          "gen_ai.user.message", {"content": messages[0]["content"]}
      )

      response = openai.chat.completions.create(
          model="o4-mini",
          messages=messages,
      )

      choice = response.choices[0]
      completion = choice.message.content

      span.add_event(
          "gen_ai.choice",
          {
              "index": choice.index,
              "finish_reason": choice.finish_reason,
              "message.content": completion,
              "message.role": choice.message.role,
          },
      )

      span.set_attributes(
          {
              "gen_ai.usage.input_tokens": response.usage.prompt_tokens,
              "gen_ai.usage.output_tokens": response.usage.completion_tokens,
              "gen_ai.response.finish_reasons": [choice.finish_reason],
          }
      )

  ```
</CodeGroup>

## Next steps

* Review [Gentrace trace attributes](../reference/opentelemetry-api) for all available options
* Learn about [automatic instrumentation](./interactions) with SDK helpers
* Explore [evaluation workflows](../evaluation/experiments) using native OpenTelemetry
