Skip to content

Commit 7a0aea6

Browse files
authored
Merge branch 'main' into add_reasoning_token_attr
2 parents e14c549 + 1d7f26a commit 7a0aea6

29 files changed

Lines changed: 1071 additions & 50 deletions

instrumentation-genai/AGENTS.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,32 @@ This layer is responsible only for:
1919
Everything else (span creation, metric recording, event emission, context propagation)
2020
belongs in `util/opentelemetry-util-genai`.
2121

22-
## 2. Invocation Pattern
22+
## 2. TelemetryHandler Initialization
23+
24+
Construct `TelemetryHandler` once inside `_instrument()`, passing all OTel providers and the
25+
completion hook. Always prefer an explicitly injected hook (`kwargs.get("completion_hook")`)
26+
over the entry-point hook loaded by `load_completion_hook()`, so test code can override the
27+
hook without touching the environment.
28+
29+
```python
30+
from opentelemetry.util.genai.completion_hook import load_completion_hook
31+
from opentelemetry.util.genai.handler import TelemetryHandler
32+
33+
def _instrument(self, **kwargs):
34+
tracer_provider = kwargs.get("tracer_provider")
35+
meter_provider = kwargs.get("meter_provider")
36+
logger_provider = kwargs.get("logger_provider")
37+
38+
handler = TelemetryHandler(
39+
tracer_provider=tracer_provider,
40+
meter_provider=meter_provider,
41+
logger_provider=logger_provider,
42+
completion_hook=kwargs.get("completion_hook") or load_completion_hook(),
43+
)
44+
# pass handler to each patch/wrapper function
45+
```
46+
47+
## 3. Invocation Pattern
2348

2449
Use `start_*()` and control span lifetime manually:
2550

@@ -36,7 +61,7 @@ except Exception as exc:
3661
raise
3762
```
3863

39-
## 3. Semantic conventions
64+
## 4. Semantic conventions
4065

4166
Attributes, spans, events, and metrics follow the
4267
[GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/gen-ai).
@@ -45,14 +70,14 @@ Do not emit signals that are not covered by semconv.
4570
`gen_ai.*` attribute names and the enums for well-known values (e.g. `GenAiOutputTypeValues` for
4671
`gen_ai.output.type`) live in `opentelemetry.semconv._incubating.attributes.gen_ai_attributes`.
4772

48-
## 4. Tests
73+
## 5. Tests
4974

5075
- Use VCR cassettes for provider calls. Do not skip tests when an API key is missing.
5176
- Cover streaming and non-streaming variants when both exist.
5277
- Cover error scenarios, at minimum: provider error / endpoint unavailable, stream interrupted by
5378
network, stream closed early by the caller.
5479

55-
## 5. Examples
80+
## 6. Examples
5681

5782
New instrumentations ship a minimal example under the package's `examples/` directory, with
5883
both a `manual/` setup and a `zero-code/` (auto-instrumentation) variant.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- Add strongly typed Responses API extractors with validation and content
3636
extraction improvements
3737
([#4337](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4337))
38+
- Add completion hook support.
39+
([#4315](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4315))
40+
- Fix `response_format` handling: map `json_object`/`json_schema` to `json` output type.
41+
([#4315](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4315))
42+
- Skip attribute values with `openai.Omit` value.
43+
([#4315](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4315))
3844
- Default empty string for `gen_ai.request.model` attribute on missing model.
3945
([#4494](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4494))
4046

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ are not captured by default. To capture message content as log events, set the e
9898
- ``span_and_event`` - Used to enable content capturing on both *span* and *event* attributes when
9999
`latest experimental features <#enabling-the-latest-experimental-features>`_ are enabled.
100100

101+
Uploading prompts and completions
102+
*********************************
103+
104+
To enable the built-in upload hook, set:
105+
106+
- ``OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload``
107+
- ``OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH`` to an ``fsspec``-compatible URI/path
108+
(e.g. ``/path/to/prompts`` or ``gs://my_bucket``).
109+
110+
Install the ``upload`` extra to pull in ``fsspec``::
111+
112+
pip install opentelemetry-util-genai[upload]
113+
114+
See the `opentelemetry-util-genai
115+
<https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/util/opentelemetry-util-genai/README.rst>`_
116+
for additional options.
117+
101118
Enabling the latest experimental features
102119
***********************************************
103120

instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/manual/.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,8 @@ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_only
2929
#
3030
# Comment out if you want to use semantic conventions of version 1.30.0.
3131
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
32+
33+
# Uncomment to upload prompts and responses to an fsspec-compatible
34+
# destination instead of or in addition to recording them inline on spans/events.
35+
# OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload
36+
# OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH=/path/to/prompts

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Note: `.env <.env>`_ file configures additional environment variables:
1313

1414
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_only`` configures OpenAI instrumentation to capture prompt and completion contents on *span* attributes.
1515
- ``OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`` enables latest experimental features.
16+
- ``OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK`` (commented out) - uncomment along with ``OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH`` to upload prompts and completions to an ``fsspec``-compatible destination instead of recording them inline. Also uncomment the ``opentelemetry-util-genai[upload]`` line in `requirements.txt <requirements.txt>`_ and reinstall.
1617

1718
Setup
1819
-----
@@ -41,3 +42,18 @@ Run the example like this:
4142

4243
You should see a poem generated by OpenAI while traces and logs export to your
4344
configured observability tool.
45+
46+
Custom completion hook
47+
----------------------
48+
49+
`custom_hook.py <custom_hook.py>`_ is a variant of ``main.py`` that passes a
50+
custom ``CompletionHook`` implementation programmatically via
51+
``OpenAIInstrumentor().instrument(completion_hook=...)``. The example hook
52+
prints prompts and completions to stdout; real hooks typically forward content
53+
to external storage and record reference URIs on the span/log_record.
54+
55+
Run it the same way:
56+
57+
::
58+
59+
dotenv run -- python custom_hook.py
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# pylint: skip-file
2+
"""Same as main.py, but instruments OpenAI with a custom CompletionHook
3+
that prints prompts and completions to stdout.
4+
5+
Run with: dotenv run -- python custom_hook.py
6+
"""
7+
8+
import os
9+
10+
from openai import OpenAI
11+
12+
from opentelemetry import _logs, metrics, trace
13+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
14+
OTLPLogExporter,
15+
)
16+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
17+
OTLPMetricExporter,
18+
)
19+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
20+
OTLPSpanExporter,
21+
)
22+
from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor
23+
from opentelemetry.sdk._logs import LoggerProvider
24+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
25+
from opentelemetry.sdk.metrics import MeterProvider
26+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
27+
from opentelemetry.sdk.trace import TracerProvider
28+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
29+
from opentelemetry.util.genai.completion_hook import CompletionHook
30+
from opentelemetry.util.genai.types import (
31+
InputMessage,
32+
MessagePart,
33+
OutputMessage,
34+
ToolDefinition,
35+
)
36+
37+
38+
class PrintCompletionHook(CompletionHook):
39+
"""Minimal CompletionHook that prints inputs/outputs to stdout.
40+
41+
Real hooks typically forward content to external storage (object store,
42+
database, etc.) and record reference URIs on the span/log_record.
43+
"""
44+
45+
def on_completion(
46+
self,
47+
*,
48+
inputs: list[InputMessage],
49+
outputs: list[OutputMessage],
50+
system_instruction: list[MessagePart],
51+
tool_definitions: list[ToolDefinition] | None = None,
52+
span=None,
53+
log_record=None,
54+
) -> None:
55+
print(f"[hook] inputs: {inputs}")
56+
print(f"[hook] outputs: {outputs}")
57+
58+
59+
trace.set_tracer_provider(TracerProvider())
60+
trace.get_tracer_provider().add_span_processor(
61+
BatchSpanProcessor(OTLPSpanExporter())
62+
)
63+
64+
_logs.set_logger_provider(LoggerProvider())
65+
_logs.get_logger_provider().add_log_record_processor(
66+
BatchLogRecordProcessor(OTLPLogExporter())
67+
)
68+
69+
metrics.set_meter_provider(
70+
MeterProvider(
71+
metric_readers=[
72+
PeriodicExportingMetricReader(OTLPMetricExporter()),
73+
]
74+
)
75+
)
76+
77+
OpenAIInstrumentor().instrument(completion_hook=PrintCompletionHook())
78+
79+
80+
def main():
81+
client = OpenAI()
82+
chat_completion = client.chat.completions.create(
83+
model=os.getenv("CHAT_MODEL", "gpt-4o-mini"),
84+
messages=[
85+
{
86+
"role": "user",
87+
"content": "Write a short poem on OpenTelemetry.",
88+
},
89+
],
90+
)
91+
print(chat_completion.choices[0].message.content)
92+
93+
94+
if __name__ == "__main__":
95+
main()

instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/manual/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ openai~=1.57.3
33
opentelemetry-sdk~=1.36.0
44
opentelemetry-exporter-otlp-proto-grpc~=1.36.0
55
opentelemetry-instrumentation-openai-v2~=2.1b0
6+
7+
# Uncomment to enable the upload completion hook (pulls in fsspec).
8+
# Required when OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload.
9+
# opentelemetry-util-genai[upload]

instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/zero-code/.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_only
3232
#
3333
# Comment out if you want to use semantic conventions of version 1.30.0.
3434
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
35+
36+
# Uncomment to upload prompts and responses to an fsspec-compatible
37+
# destination instead of or in addition to recording them inline on spans/events.
38+
# OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload
39+
# OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH=/path/to/prompts

instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/zero-code/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Note: `.env <.env>`_ file configures additional environment variables:
1515
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_only`` configures OpenAI instrumentation to capture prompt and completion contents on *span* attributes.
1616
- ``OTEL_LOGS_EXPORTER=otlp`` to specify exporter type.
1717
- ``OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`` enables latest experimental features.
18+
- ``OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK`` (commented out) - uncomment along with ``OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH`` to upload prompts and completions to an ``fsspec``-compatible destination instead of recording them inline. Also uncomment the ``opentelemetry-util-genai[upload]`` line in `requirements.txt <requirements.txt>`_ and reinstall.
1819

1920
Setup
2021
-----

instrumentation-genai/opentelemetry-instrumentation-openai-v2/examples/zero-code/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ opentelemetry-sdk~=1.36.0
44
opentelemetry-exporter-otlp-proto-grpc~=1.36.0
55
opentelemetry-distro~=0.57b0
66
opentelemetry-instrumentation-openai-v2~=2.1b0
7+
8+
# Uncomment to enable the upload completion hook (pulls in fsspec).
9+
# Required when OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload.
10+
# opentelemetry-util-genai[upload]

0 commit comments

Comments
 (0)