Skip to content

Commit 3197cac

Browse files
authored
Merge branch 'open-telemetry:main' into barebones
2 parents 4838e79 + c1a9f1f commit 3197cac

File tree

18 files changed

+520
-143
lines changed

18 files changed

+520
-143
lines changed

.github/component_owners.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ components:
99
instrumentation/opentelemetry-instrumentation-asyncio:
1010
- bourbonkk
1111

12+
instrumentation/opentelemetry-instrumentation-botocore:
13+
- lukeina2z
14+
- yiyuan-he
15+
1216
instrumentation/opentelemetry-instrumentation-pymssql:
13-
- guillaumep
17+
- guillaumep
1418

1519
instrumentation/opentelemetry-instrumentation-urllib:
1620
- shalevr
@@ -43,4 +47,4 @@ components:
4347

4448
instrumentation-genai/opentelemetry-instrumentation-langchain:
4549
- zhirafovod
46-
- wrisa
50+
- wrisa

instrumentation-genai/opentelemetry-instrumentation-vertexai/CHANGELOG.md

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

88
## Unreleased
99

10-
- Start making changes to implement the big semantic convention changes made in https://github.com/open-telemetry/semantic-conventions/pull/2179.
11-
Now only a single event (`gen_ai.client.inference.operation.details`) is used to capture Chat History. These changes will be opt-in,
12-
users will need to set the environment variable OTEL_SEMCONV_STABILITY_OPT_IN to `gen_ai_latest_experimental` to see them ([#3386](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3386)).
10+
- Update instrumentation to use the latest semantic convention changes made in https://github.com/open-telemetry/semantic-conventions/pull/2179.
11+
Now only a single event and span (`gen_ai.client.inference.operation.details`) are used to capture prompt and response content. These changes are opt-in,
12+
users will need to set the environment variable OTEL_SEMCONV_STABILITY_OPT_IN to `gen_ai_latest_experimental` to see them ([#3799](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3799)) and ([#3709](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3709)).
1313
- Implement uninstrument for `opentelemetry-instrumentation-vertexai`
1414
([#3328](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3328))
1515
- VertexAI support for async calling

instrumentation-genai/opentelemetry-instrumentation-vertexai/examples/manual/.env

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,17 @@ OTEL_SERVICE_NAME=opentelemetry-python-vertexai
66

77
# Change to 'false' to hide prompt and completion content
88
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
9+
10+
# Alternatively set this env var to enable the latest semantic conventions:
11+
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
12+
13+
# When using the latest experimental flag this env var controls which telemetry signals will have prompt and response content included in them.
14+
# Choices are NO_CONTENT, SPAN_ONLY, EVENT_ONLY, SPAN_AND_EVENT.
15+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_AND_EVENT
16+
17+
# Optional hook that will upload prompt and response content to some external destination.
18+
# For example fsspec.
19+
OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK = "upload"
20+
21+
# Required if using a completion hook. The path to upload content to for example gs://my_bucket.
22+
OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH = "gs://my_bucket"

instrumentation-genai/opentelemetry-instrumentation-vertexai/examples/zero-code/.env

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,17 @@ OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
1212

1313
# Change to 'false' to hide prompt and completion content
1414
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
15+
16+
# Alternatively set this env var to enable the latest semantic conventions:
17+
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
18+
19+
# When using the latest experimental flag this env var controls which telemetry signals will have prompt and response content included in them.
20+
# Choices are NO_CONTENT, SPAN_ONLY, EVENT_ONLY, SPAN_AND_EVENT.
21+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_AND_EVENT
22+
23+
# Optional hook that will upload prompt and response content to some external destination.
24+
# For example fsspec.
25+
OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK = "upload"
26+
27+
# Required if using a completion hook. The path to upload content to for example gs://my_bucket.
28+
OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH = "gs://my_bucket"

instrumentation-genai/opentelemetry-instrumentation-vertexai/src/opentelemetry/instrumentation/vertexai/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from opentelemetry.instrumentation.vertexai.utils import is_content_enabled
6161
from opentelemetry.semconv.schemas import Schemas
6262
from opentelemetry.trace import get_tracer
63+
from opentelemetry.util.genai.completion_hook import load_completion_hook
6364

6465

6566
def _methods_to_wrap(
@@ -109,6 +110,9 @@ def instrumentation_dependencies(self) -> Collection[str]:
109110

110111
def _instrument(self, **kwargs: Any):
111112
"""Enable VertexAI instrumentation."""
113+
completion_hook = (
114+
kwargs.get("completion_hook") or load_completion_hook()
115+
)
112116
sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
113117
_OpenTelemetryStabilitySignalType.GEN_AI,
114118
)
@@ -141,6 +145,7 @@ def _instrument(self, **kwargs: Any):
141145
event_logger,
142146
is_content_enabled(sem_conv_opt_in_mode),
143147
sem_conv_opt_in_mode,
148+
completion_hook,
144149
)
145150
elif sem_conv_opt_in_mode == _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL:
146151
# Type checker now knows it's the other literal
@@ -149,6 +154,7 @@ def _instrument(self, **kwargs: Any):
149154
event_logger,
150155
is_content_enabled(sem_conv_opt_in_mode),
151156
sem_conv_opt_in_mode,
157+
completion_hook,
152158
)
153159
else:
154160
raise RuntimeError(f"{sem_conv_opt_in_mode} mode not supported")

instrumentation-genai/opentelemetry-instrumentation-vertexai/src/opentelemetry/instrumentation/vertexai/patch.py

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
from contextlib import contextmanager
18+
from dataclasses import asdict
1819
from typing import (
1920
TYPE_CHECKING,
2021
Any,
@@ -27,22 +28,32 @@
2728
overload,
2829
)
2930

30-
from opentelemetry._events import EventLogger
31+
from opentelemetry._events import Event, EventLogger
3132
from opentelemetry.instrumentation._semconv import (
3233
_StabilityMode,
3334
)
3435
from opentelemetry.instrumentation.vertexai.utils import (
3536
GenerateContentParams,
36-
create_operation_details_event,
37+
_map_finish_reason,
38+
convert_content_to_message_parts,
3739
get_genai_request_attributes,
3840
get_genai_response_attributes,
3941
get_server_attributes,
4042
get_span_name,
4143
request_to_events,
4244
response_to_events,
4345
)
46+
from opentelemetry.semconv._incubating.attributes import (
47+
gen_ai_attributes as GenAI,
48+
)
4449
from opentelemetry.trace import SpanKind, Tracer
45-
from opentelemetry.util.genai.types import ContentCapturingMode
50+
from opentelemetry.util.genai.completion_hook import CompletionHook
51+
from opentelemetry.util.genai.types import (
52+
ContentCapturingMode,
53+
InputMessage,
54+
OutputMessage,
55+
)
56+
from opentelemetry.util.genai.utils import gen_ai_json_dumps
4657

4758
if TYPE_CHECKING:
4859
from google.cloud.aiplatform_v1.services.prediction_service import client
@@ -110,6 +121,7 @@ def __init__(
110121
sem_conv_opt_in_mode: Literal[
111122
_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
112123
],
124+
completion_hook: CompletionHook,
113125
) -> None: ...
114126

115127
@overload
@@ -119,6 +131,7 @@ def __init__(
119131
event_logger: EventLogger,
120132
capture_content: bool,
121133
sem_conv_opt_in_mode: Literal[_StabilityMode.DEFAULT],
134+
completion_hook: CompletionHook,
122135
) -> None: ...
123136

124137
def __init__(
@@ -130,11 +143,13 @@ def __init__(
130143
Literal[_StabilityMode.DEFAULT],
131144
Literal[_StabilityMode.GEN_AI_LATEST_EXPERIMENTAL],
132145
],
146+
completion_hook: CompletionHook,
133147
) -> None:
134148
self.tracer = tracer
135149
self.event_logger = event_logger
136150
self.capture_content = capture_content
137151
self.sem_conv_opt_in_mode = sem_conv_opt_in_mode
152+
self.completion_hook = completion_hook
138153

139154
@contextmanager
140155
def _with_new_instrumentation(
@@ -146,40 +161,88 @@ def _with_new_instrumentation(
146161
kwargs: Any,
147162
):
148163
params = _extract_params(*args, **kwargs)
149-
api_endpoint: str = instance.api_endpoint # type: ignore[reportUnknownMemberType]
150-
span_attributes = {
151-
**get_genai_request_attributes(False, params),
152-
**get_server_attributes(api_endpoint),
153-
}
154-
155-
span_name = get_span_name(span_attributes)
156-
164+
request_attributes = get_genai_request_attributes(True, params)
157165
with self.tracer.start_as_current_span(
158-
name=span_name,
166+
name=f"{GenAI.GenAiOperationNameValues.CHAT.value} {request_attributes.get(GenAI.GEN_AI_REQUEST_MODEL, '')}".strip(),
159167
kind=SpanKind.CLIENT,
160-
attributes=span_attributes,
161168
) as span:
162169

163170
def handle_response(
164171
response: prediction_service.GenerateContentResponse
165172
| prediction_service_v1beta1.GenerateContentResponse
166173
| None,
167174
) -> None:
168-
if span.is_recording() and response:
169-
# When streaming, this is called multiple times so attributes would be
170-
# overwritten. In practice, it looks the API only returns the interesting
171-
# attributes on the last streamed response. However, I couldn't find
172-
# documentation for this and setting attributes shouldn't be too expensive.
173-
span.set_attributes(
174-
get_genai_response_attributes(response)
175-
)
176-
self.event_logger.emit(
177-
create_operation_details_event(
178-
api_endpoint=api_endpoint,
179-
params=params,
180-
capture_content=capture_content,
181-
response=response,
175+
attributes = (
176+
get_server_attributes(instance.api_endpoint) # type: ignore[reportUnknownMemberType]
177+
| request_attributes
178+
| get_genai_response_attributes(response)
179+
)
180+
system_instructions, inputs, outputs = [], [], []
181+
if params.system_instruction:
182+
system_instructions = convert_content_to_message_parts(
183+
params.system_instruction
182184
)
185+
if params.contents:
186+
inputs = [
187+
InputMessage(
188+
role=content.role,
189+
parts=convert_content_to_message_parts(content),
190+
)
191+
for content in params.contents
192+
]
193+
if response:
194+
outputs = [
195+
OutputMessage(
196+
finish_reason=_map_finish_reason(
197+
candidate.finish_reason
198+
),
199+
role=candidate.content.role,
200+
parts=convert_content_to_message_parts(
201+
candidate.content
202+
),
203+
)
204+
for candidate in response.candidates
205+
]
206+
content_attributes = {
207+
k: [asdict(x) for x in v]
208+
for k, v in [
209+
(
210+
GenAI.GEN_AI_SYSTEM_INSTRUCTIONS,
211+
system_instructions,
212+
),
213+
(GenAI.GEN_AI_INPUT_MESSAGES, inputs),
214+
(GenAI.GEN_AI_OUTPUT_MESSAGES, outputs),
215+
]
216+
if v
217+
}
218+
if span.is_recording():
219+
span.set_attributes(attributes)
220+
if capture_content in (
221+
ContentCapturingMode.SPAN_AND_EVENT,
222+
ContentCapturingMode.SPAN_ONLY,
223+
):
224+
span.set_attributes(
225+
{
226+
k: gen_ai_json_dumps(v)
227+
for k, v in content_attributes.items()
228+
}
229+
)
230+
event = Event(
231+
name="gen_ai.client.inference.operation.details",
232+
)
233+
event.attributes = attributes
234+
if capture_content in (
235+
ContentCapturingMode.SPAN_AND_EVENT,
236+
ContentCapturingMode.EVENT_ONLY,
237+
):
238+
event.attributes |= content_attributes
239+
self.event_logger.emit(event)
240+
self.completion_hook.on_completion(
241+
inputs=inputs,
242+
outputs=outputs,
243+
system_instruction=system_instructions,
244+
span=span,
245+
log_record=event,
183246
)
184247

185248
yield handle_response

0 commit comments

Comments
 (0)