Skip to content

Add OpenAI embeddings instrumentation #3461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Added support for OpenAI embeddings instrumentation
([#3461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3461))
- Record prompt and completion events regardless of span sampling decision.
([#3226](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3226))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Check out the `manual example <examples/manual>`_ for more details.
Instrumenting all clients
*************************

When using the instrumentor, all clients will automatically trace OpenAI chat completion operations.
When using the instrumentor, all clients will automatically trace OpenAI operations including chat completions and embeddings.
You can also optionally capture prompts and completions as log events.

Make sure to configure OpenTelemetry tracing, logging, and events to capture all telemetry emitted by the instrumentation.
Expand All @@ -68,12 +68,19 @@ Make sure to configure OpenTelemetry tracing, logging, and events to capture all
OpenAIInstrumentor().instrument()

client = OpenAI()
# Chat completion example
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "Write a short poem on open telemetry."},
],
)

# Embeddings example
embedding_response = client.embeddings.create(
model="text-embedding-3-small",
input="Generate vector embeddings for this text"
)

Enabling message content
*************************
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Update this with your real OpenAI API key
OPENAI_API_KEY=sk-YOUR_API_KEY

# Uncomment to use Ollama instead of OpenAI
# OPENAI_BASE_URL=http://localhost:11434/v1
# OPENAI_API_KEY=unused
# CHAT_MODEL=qwen2.5:0.5b

# Uncomment and change to your OTLP endpoint
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc

OTEL_SERVICE_NAME=opentelemetry-python-openai

# Change to 'false' to disable collection of python logging logs
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true

# Uncomment if your OTLP endpoint doesn't support logs
# OTEL_LOGS_EXPORTER=console

# Change to 'false' to hide prompt and completion content
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
OpenTelemetry OpenAI Embeddings API Instrumentation Example
===========================================================

This is an example of how to instrument OpenAI Embeddings API calls with zero code changes,
using ``opentelemetry-instrument``.

When ``main.py`` is run, it exports traces and metrics to an OTLP
compatible endpoint. Traces include details such as the model used,
dimensions of embeddings, and the duration of the embedding request.
Metrics capture token usage and performance data.

Note: ``.env`` file configures additional environment variables:

- ``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true`` configures OpenTelemetry SDK to export logs and events.
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`` configures OpenAI instrumentation to capture content on events.
- ``OTEL_LOGS_EXPORTER=otlp`` to specify exporter type.

Setup
-----

Minimally, update the ``.env`` file with your ``OPENAI_API_KEY``. An
OTLP compatible endpoint should be listening for traces and logs on
http://localhost:4317. If not, update ``OTEL_EXPORTER_OTLP_ENDPOINT`` as well.

Next, set up a virtual environment like this:

::

python3 -m venv .venv
source .venv/bin/activate
pip install "python-dotenv[cli]"
pip install -r requirements.txt

Run
---

Run the example like this:

::

dotenv run -- opentelemetry-instrument python main.py

You should see embedding information printed while traces and metrics export to your
configured observability tool.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from openai import OpenAI


def main():
client = OpenAI()

# Create embeddings with OpenAI API
embedding_response = client.embeddings.create(
model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-small"),
input="OpenTelemetry provides observability for your applications.",
)

# Print embedding information
print(f"Model: {embedding_response.model}")
print(f"Dimensions: {len(embedding_response.data[0].embedding)}")
print(
f"Token usage - Prompt: {embedding_response.usage.prompt_tokens}, Total: {embedding_response.usage.total_tokens}"
)

# Print a sample of the embedding vector (first 5 dimensions)
print(
f"Embedding sample (first 5 dimensions): {embedding_response.data[0].embedding[:5]}"
)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
openai~=1.57.3

opentelemetry-sdk~=1.30.0
opentelemetry-exporter-otlp-proto-grpc~=1.30.0
opentelemetry-distro~=0.51b0
opentelemetry-instrumentation-openai-v2~=2.1b0
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@
from opentelemetry.trace import get_tracer

from .instruments import Instruments
from .patch import async_chat_completions_create, chat_completions_create
from .patch import (
async_chat_completions_create,
async_embeddings_create,
chat_completions_create,
embeddings_create,
)


class OpenAIInstrumentor(BaseInstrumentor):
Expand Down Expand Up @@ -106,8 +111,27 @@ def _instrument(self, **kwargs):
),
)

# Add instrumentation for the embeddings API
wrap_function_wrapper(
module="openai.resources.embeddings",
name="Embeddings.create",
wrapper=embeddings_create(
tracer, event_logger, instruments, is_content_enabled()
),
)

wrap_function_wrapper(
module="openai.resources.embeddings",
name="AsyncEmbeddings.create",
wrapper=async_embeddings_create(
tracer, event_logger, instruments, is_content_enabled()
),
)

def _uninstrument(self, **kwargs):
import openai # pylint: disable=import-outside-toplevel

unwrap(openai.resources.chat.completions.Completions, "create")
unwrap(openai.resources.chat.completions.AsyncCompletions, "create")
unwrap(openai.resources.embeddings.Embeddings, "create")
unwrap(openai.resources.embeddings.AsyncEmbeddings, "create")
Loading