Skip to content

Commit fecd8af

Browse files
drewbyxrmx
andauthored
Add OpenAI embeddings instrumentation (#3461)
* Initial implementation and tests * Add embeddings example * Update documentation * Changelog entry * Add comment about custom attributes * Update PR link in Changelog * Add input and output events for embeddings * Fix changelog * Use gen_ai.embeddings.dimension.count * Remove total_tokens * Use end_on_exit * Don't import conditionally * Fix heading * Use gen_ai.request.encoding_formats * Use gen_ai.request.encoding_formats * Rename span_attr to request_attr * Remove embeddings capture via events * bump versions * Remove unused event code * Updates for PR feedback * Update requirements for example * dimension.count is added in get_llm_request_attributes * Use a shared function to reduce duplicate code * remove return * Update instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/embeddings/requirements.txt --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 9515f04 commit fecd8af

28 files changed

+22229
-264
lines changed

instrumentation-genai/opentelemetry-instrumentation-openai-v2/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
- Added support for OpenAI embeddings instrumentation
11+
([#3461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3461))
1012
- Record prompt and completion events regardless of span sampling decision.
1113
([#3226](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3226))
1214
- Migrate off the deprecated events API to use the logs API

instrumentation-genai/opentelemetry-instrumentation-openai-v2/README.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Check out the `manual example <examples/manual>`_ for more details.
5656
Instrumenting all clients
5757
*************************
5858

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

6262
Make sure to configure OpenTelemetry tracing, logging, and events to capture all telemetry emitted by the instrumentation.
@@ -68,12 +68,19 @@ Make sure to configure OpenTelemetry tracing, logging, and events to capture all
6868
OpenAIInstrumentor().instrument()
6969
7070
client = OpenAI()
71+
# Chat completion example
7172
response = client.chat.completions.create(
7273
model="gpt-4o-mini",
7374
messages=[
7475
{"role": "user", "content": "Write a short poem on open telemetry."},
7576
],
7677
)
78+
79+
# Embeddings example
80+
embedding_response = client.embeddings.create(
81+
model="text-embedding-3-small",
82+
input="Generate vector embeddings for this text"
83+
)
7784
7885
Enabling message content
7986
*************************
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Update this with your real OpenAI API key
2+
OPENAI_API_KEY=sk-YOUR_API_KEY
3+
4+
# Uncomment to use Ollama instead of OpenAI
5+
# OPENAI_BASE_URL=http://localhost:11434/v1
6+
# OPENAI_API_KEY=unused
7+
# CHAT_MODEL=qwen2.5:0.5b
8+
9+
# Uncomment and change to your OTLP endpoint
10+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
11+
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc
12+
13+
OTEL_SERVICE_NAME=opentelemetry-python-openai
14+
15+
# Change to 'false' to disable collection of python logging logs
16+
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
17+
18+
# Uncomment if your OTLP endpoint doesn't support logs
19+
# OTEL_LOGS_EXPORTER=console
20+
21+
# Change to 'false' to hide prompt and completion content
22+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
OpenTelemetry OpenAI Embeddings API Instrumentation Example
2+
===========================================================
3+
4+
This is an example of how to instrument OpenAI Embeddings API calls with zero code changes,
5+
using ``opentelemetry-instrument``.
6+
7+
When ``main.py`` is run, it exports traces and metrics to an OTLP
8+
compatible endpoint. Traces include details such as the model used,
9+
dimensions of embeddings, and the duration of the embedding request.
10+
Metrics capture token usage and performance data.
11+
12+
Note: ``.env`` file configures additional environment variables:
13+
14+
- ``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true`` configures OpenTelemetry SDK to export logs and events.
15+
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`` configures OpenAI instrumentation to capture content on events.
16+
- ``OTEL_LOGS_EXPORTER=otlp`` to specify exporter type.
17+
18+
Setup
19+
-----
20+
21+
Minimally, update the ``.env`` file with your ``OPENAI_API_KEY``. An
22+
OTLP compatible endpoint should be listening for traces and logs on
23+
http://localhost:4317. If not, update ``OTEL_EXPORTER_OTLP_ENDPOINT`` as well.
24+
25+
Next, set up a virtual environment like this:
26+
27+
::
28+
29+
python3 -m venv .venv
30+
source .venv/bin/activate
31+
pip install "python-dotenv[cli]"
32+
pip install -r requirements.txt
33+
34+
Run
35+
---
36+
37+
Run the example like this:
38+
39+
::
40+
41+
dotenv run -- opentelemetry-instrument python main.py
42+
43+
You should see embedding information printed while traces and metrics export to your
44+
configured observability tool.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import os
2+
3+
from openai import OpenAI
4+
5+
6+
def main():
7+
client = OpenAI()
8+
9+
# Create embeddings with OpenAI API
10+
embedding_response = client.embeddings.create(
11+
model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-small"),
12+
input="OpenTelemetry provides observability for your applications.",
13+
)
14+
15+
# Print embedding information
16+
print(f"Model: {embedding_response.model}")
17+
print(f"Dimensions: {len(embedding_response.data[0].embedding)}")
18+
print(
19+
f"Token usage - Prompt: {embedding_response.usage.prompt_tokens}, Total: {embedding_response.usage.total_tokens}"
20+
)
21+
22+
# Print a sample of the embedding vector (first 5 dimensions)
23+
print(
24+
f"Embedding sample (first 5 dimensions): {embedding_response.data[0].embedding[:5]}"
25+
)
26+
27+
28+
if __name__ == "__main__":
29+
main()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
openai~=1.57.3
2+
3+
opentelemetry-sdk~=1.36.0
4+
opentelemetry-exporter-otlp-proto-grpc~=1.36.0
5+
opentelemetry-distro~=0.57b0
6+
opentelemetry-instrumentation-openai-v2~=2.2b0

instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/__init__.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@
5454
from opentelemetry.trace import get_tracer
5555

5656
from .instruments import Instruments
57-
from .patch import async_chat_completions_create, chat_completions_create
57+
from .patch import (
58+
async_chat_completions_create,
59+
async_embeddings_create,
60+
chat_completions_create,
61+
embeddings_create,
62+
)
5863

5964

6065
class OpenAIInstrumentor(BaseInstrumentor):
@@ -106,8 +111,27 @@ def _instrument(self, **kwargs):
106111
),
107112
)
108113

114+
# Add instrumentation for the embeddings API
115+
wrap_function_wrapper(
116+
module="openai.resources.embeddings",
117+
name="Embeddings.create",
118+
wrapper=embeddings_create(
119+
tracer, instruments, is_content_enabled()
120+
),
121+
)
122+
123+
wrap_function_wrapper(
124+
module="openai.resources.embeddings",
125+
name="AsyncEmbeddings.create",
126+
wrapper=async_embeddings_create(
127+
tracer, instruments, is_content_enabled()
128+
),
129+
)
130+
109131
def _uninstrument(self, **kwargs):
110132
import openai # pylint: disable=import-outside-toplevel
111133

112134
unwrap(openai.resources.chat.completions.Completions, "create")
113135
unwrap(openai.resources.chat.completions.AsyncCompletions, "create")
136+
unwrap(openai.resources.embeddings.Embeddings, "create")
137+
unwrap(openai.resources.embeddings.AsyncEmbeddings, "create")

0 commit comments

Comments
 (0)