Skip to content

Commit a8e8739

Browse files
committed
Resolve merge conflict.
2 parents 82c832b + 72490ea commit a8e8739

File tree

26 files changed

+7573
-131
lines changed

26 files changed

+7573
-131
lines changed

.github/workflows/fossa.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: FOSSA scanning
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
fossa:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
16+
17+
- uses: fossas/fossa-action@93a52ecf7c3ac7eb40f5de77fd69b1a19524de94 # v1.5.0
18+
with:
19+
api-key: ${{secrets.FOSSA_API_KEY}}
20+
team: OpenTelemetry

.pre-commit-config.yaml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
repos:
2-
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
# Ruff version.
4-
rev: v0.6.9
5-
hooks:
6-
# Run the linter.
7-
- id: ruff
8-
args: ["--fix", "--show-fixes"]
9-
# Run the formatter.
10-
- id: ruff-format
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
# Ruff version.
4+
rev: v0.6.9
5+
hooks:
6+
# Run the linter.
7+
- id: ruff
8+
args: ["--fix", "--show-fixes"]
9+
# Run the formatter.
10+
- id: ruff-format
11+
- repo: https://github.com/astral-sh/uv-pre-commit
12+
# uv version.
13+
rev: 0.6.0
14+
hooks:
15+
- id: uv-lock

CHANGELOG.md

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

1414
### Added
1515

16+
- `opentelemetry-instrumentation-system-metrics` Add `process` metrics and deprecated `process.runtime` prefixed ones
17+
([#3250](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3250))
1618
- `opentelemetry-instrumentation-botocore` Add support for GenAI user events and lazy initialize tracer
1719
([#3258](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3258))
1820
- `opentelemetry-instrumentation-botocore` Add support for GenAI system events

CONTRIBUTING.md

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,31 @@ Please also read the [OpenTelemetry Contributor Guide](https://github.com/open-t
1919

2020
## Index
2121

22-
* [Find a Buddy and get Started Quickly](#find-a-buddy-and-get-started-quickly)
23-
* [Development](#development)
24-
* [Troubleshooting](#troubleshooting)
25-
* [Benchmarks](#benchmarks)
26-
* [Pull requests](#pull-requests)
27-
* [How to Send Pull Requests](#how-to-send-pull-requests)
28-
* [How to Receive Comments](#how-to-receive-comments)
29-
* [How to Get PRs Reviewed](#how-to-get-prs-reviewed)
30-
* [How to Get PRs Merged](#how-to-get-prs-merged)
31-
* [Design Choices](#design-choices)
32-
* [Focus on Capabilities, Not Structure Compliance](#focus-on-capabilities-not-structure-compliance)
33-
* [Running Tests Locally](#running-tests-locally)
34-
* [Testing against a different Core repo branch/commit](#testing-against-a-different-core-repo-branchcommit)
35-
* [Style Guide](#style-guide)
36-
* [Guideline for instrumentations](#guideline-for-instrumentations)
37-
* [Guideline for GenAI instrumentations](#guideline-for-genai-instrumentations)
38-
* [Expectations from contributors](#expectations-from-contributors)
22+
- [Contributing to opentelemetry-python-contrib](#contributing-to-opentelemetry-python-contrib)
23+
- [Index](#index)
24+
- [Find a Buddy and get Started Quickly](#find-a-buddy-and-get-started-quickly)
25+
- [Development](#development)
26+
- [Virtual Environment](#virtual-environment)
27+
- [Troubleshooting](#troubleshooting)
28+
- [Benchmarks](#benchmarks)
29+
- [Pull Requests](#pull-requests)
30+
- [How to Send Pull Requests](#how-to-send-pull-requests)
31+
- [How to Receive Comments](#how-to-receive-comments)
32+
- [How to Get PRs Reviewed](#how-to-get-prs-reviewed)
33+
- [How to Get PRs Merged](#how-to-get-prs-merged)
34+
- [Design Choices](#design-choices)
35+
- [Focus on Capabilities, Not Structure Compliance](#focus-on-capabilities-not-structure-compliance)
36+
- [Running Tests Locally](#running-tests-locally)
37+
- [Testing against a different Core repo branch/commit](#testing-against-a-different-core-repo-branchcommit)
38+
- [Style Guide](#style-guide)
39+
- [Guideline for instrumentations](#guideline-for-instrumentations)
40+
- [Update supported instrumentation package versions](#update-supported-instrumentation-package-versions)
41+
- [Guideline for GenAI instrumentations](#guideline-for-genai-instrumentations)
42+
- [Get Involved](#get-involved)
43+
- [Expectations from contributors](#expectations-from-contributors)
44+
- [Updating supported Python versions](#updating-supported-python-versions)
45+
- [Bumping the Python baseline](#bumping-the-python-baseline)
46+
- [Adding support for a new Python release](#adding-support-for-a-new-python-release)
3947

4048
## Find a Buddy and get Started Quickly
4149

@@ -93,6 +101,20 @@ See
93101
[`tox.ini`](https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/tox.ini)
94102
for more detail on available tox commands.
95103

104+
### Virtual Environment
105+
106+
You can also create a single virtual environment to make it easier to run local tests.
107+
108+
For that, you'll need to install [`uv`](https://docs.astral.sh/uv/getting-started/installation/).
109+
110+
After installing `uv`, you can run the following command:
111+
112+
```sh
113+
uv sync
114+
```
115+
116+
This will create a virtual environment in the `.venv` directory and install all the necessary dependencies.
117+
96118
### Troubleshooting
97119

98120
Some packages may require additional system-wide dependencies to be installed. For example, you may need to install `libpq-dev` to run the postgresql client libraries instrumentation tests or `libsnappy-dev` to run the prometheus exporter tests. If you encounter a build error, please check the installation instructions for the package you are trying to run tests for.

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ your OpenAI requests.
1111

1212
Note: `.env <.env>`_ file configures additional environment variables:
1313

14-
- `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true` configures
15-
OpenAI instrumentation to capture prompt and completion contents on
16-
events.
14+
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`` configures OpenAI instrumentation to capture prompt and completion contents on events.
1715

1816
Setup
1917
-----
2018

21-
Minimally, update the `.env <.env>`_ file with your "OPENAI_API_KEY". An
19+
Minimally, update the `.env <.env>`_ file with your ``OPENAI_API_KEY``. An
2220
OTLP compatible endpoint should be listening for traces and logs on
23-
http://localhost:4317. If not, update "OTEL_EXPORTER_OTLP_ENDPOINT" as well.
21+
http://localhost:4317. If not, update ``OTEL_EXPORTER_OTLP_ENDPOINT`` as well.
2422

2523
Next, set up a virtual environment like this:
2624

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@ your OpenAI requests.
1212

1313
Note: `.env <.env>`_ file configures additional environment variables:
1414

15-
- `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true` configures
16-
OpenTelemetry SDK to export logs and events.
17-
- `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true` configures
18-
OpenAI instrumentation to capture prompt and completion contents on
19-
events.
20-
- `OTEL_LOGS_EXPORTER=otlp` to specify exporter type.
15+
- ``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true`` configures OpenTelemetry SDK to export logs and events.
16+
- ``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`` configures OpenAI instrumentation to capture prompt and completion contents on events.
17+
- ``OTEL_LOGS_EXPORTER=otlp`` to specify exporter type.
2118

2219
Setup
2320
-----
2421

25-
Minimally, update the `.env <.env>`_ file with your "OPENAI_API_KEY". An
22+
Minimally, update the `.env <.env>`_ file with your ``OPENAI_API_KEY``. An
2623
OTLP compatible endpoint should be listening for traces and logs on
27-
http://localhost:4317. If not, update "OTEL_EXPORTER_OTLP_ENDPOINT" as well.
24+
http://localhost:4317. If not, update ``OTEL_EXPORTER_OTLP_ENDPOINT`` as well.
2825

2926
Next, set up a virtual environment like this:
3027

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
([#3208](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3208))
1616
- VertexAI emit user, system, and assistant events
1717
([#3203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3203))
18-
- Add Vertex gen AI response span attributes
18+
- Add Vertex gen AI response attributes and `gen_ai.choice` events
1919
([#3227](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3227))
2020
- VertexAI stop serializing unset fields into event
2121
([#3236](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3236))
22+
- Vertex capture tool requests and responses
23+
([#3255](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3255))

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

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from __future__ import annotations
2424

2525
from dataclasses import asdict, dataclass
26-
from typing import Literal
26+
from typing import Any, Iterable, Literal
2727

2828
from opentelemetry._events import Event
2929
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
@@ -96,6 +96,33 @@ def system_event(
9696
)
9797

9898

99+
def tool_event(
100+
*,
101+
role: str | None,
102+
id_: str,
103+
content: AnyValue = None,
104+
) -> Event:
105+
"""Creates a Tool message event
106+
https://github.com/open-telemetry/semantic-conventions/blob/v1.28.0/docs/gen-ai/gen-ai-events.md#event-gen_aitoolmessage
107+
"""
108+
if not role:
109+
role = "tool"
110+
111+
body: dict[str, AnyValue] = {
112+
"role": role,
113+
"id": id_,
114+
}
115+
if content is not None:
116+
body["content"] = content
117+
return Event(
118+
name="gen_ai.tool.message",
119+
attributes={
120+
gen_ai_attributes.GEN_AI_SYSTEM: gen_ai_attributes.GenAiSystemValues.VERTEX_AI.value,
121+
},
122+
body=body,
123+
)
124+
125+
99126
@dataclass
100127
class ChoiceMessage:
101128
"""The message field for a gen_ai.choice event"""
@@ -104,36 +131,58 @@ class ChoiceMessage:
104131
role: str = "assistant"
105132

106133

134+
@dataclass
135+
class ChoiceToolCall:
136+
"""The tool_calls field for a gen_ai.choice event"""
137+
138+
@dataclass
139+
class Function:
140+
name: str
141+
arguments: AnyValue = None
142+
143+
function: Function
144+
id: str
145+
type: Literal["function"] = "function"
146+
147+
107148
FinishReason = Literal[
108149
"content_filter", "error", "length", "stop", "tool_calls"
109150
]
110151

111152

112-
# TODO add tool calls
113-
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3216
114153
def choice_event(
115154
*,
116155
finish_reason: FinishReason | str,
117156
index: int,
118157
message: ChoiceMessage,
158+
tool_calls: Iterable[ChoiceToolCall] = (),
119159
) -> Event:
120160
"""Creates a choice event, which describes the Gen AI response message.
121161
https://github.com/open-telemetry/semantic-conventions/blob/v1.28.0/docs/gen-ai/gen-ai-events.md#event-gen_aichoice
122162
"""
123163
body: dict[str, AnyValue] = {
124164
"finish_reason": finish_reason,
125165
"index": index,
126-
"message": asdict(
127-
message,
128-
# filter nulls
129-
dict_factory=lambda kvs: {k: v for (k, v) in kvs if v is not None},
130-
),
166+
"message": _asdict_filter_nulls(message),
131167
}
132168

169+
tool_calls_list = [
170+
_asdict_filter_nulls(tool_call) for tool_call in tool_calls
171+
]
172+
if tool_calls_list:
173+
body["tool_calls"] = tool_calls_list
174+
133175
return Event(
134176
name="gen_ai.choice",
135177
attributes={
136178
gen_ai_attributes.GEN_AI_SYSTEM: gen_ai_attributes.GenAiSystemValues.VERTEX_AI.value,
137179
},
138180
body=body,
139181
)
182+
183+
184+
def _asdict_filter_nulls(instance: Any) -> dict[str, AnyValue]:
185+
return asdict(
186+
instance,
187+
dict_factory=lambda kvs: {k: v for (k, v) in kvs if v is not None},
188+
)

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

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@
2626
)
2727
from urllib.parse import urlparse
2828

29+
from google.protobuf import json_format
30+
2931
from opentelemetry._events import Event
3032
from opentelemetry.instrumentation.vertexai.events import (
3133
ChoiceMessage,
34+
ChoiceToolCall,
3235
FinishReason,
3336
assistant_event,
3437
choice_event,
3538
system_event,
39+
tool_event,
3640
user_event,
3741
)
3842
from opentelemetry.semconv._incubating.attributes import (
@@ -219,12 +223,37 @@ def request_to_events(
219223
)
220224

221225
yield assistant_event(role=content.role, content=request_content)
222-
# Assume user event but role should be "user"
223-
else:
224-
request_content = _parts_to_any_value(
225-
capture_content=capture_content, parts=content.parts
226+
continue
227+
228+
# Tool event
229+
#
230+
# Function call results can be parts inside of a user Content or in a separate Content
231+
# entry without a role. That may cause duplication in a user event, see
232+
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3280
233+
function_responses = [
234+
part.function_response
235+
for part in content.parts
236+
if "function_response" in part
237+
]
238+
for idx, function_response in enumerate(function_responses):
239+
yield tool_event(
240+
id_=f"{function_response.name}_{idx}",
241+
role=content.role,
242+
content=json_format.MessageToDict(
243+
function_response._pb.response # type: ignore[reportUnknownMemberType]
244+
)
245+
if capture_content
246+
else None,
226247
)
227-
yield user_event(role=content.role, content=request_content)
248+
249+
if len(function_responses) == len(content.parts):
250+
# If the content only contained function responses, don't emit a user event
251+
continue
252+
253+
request_content = _parts_to_any_value(
254+
capture_content=capture_content, parts=content.parts
255+
)
256+
yield user_event(role=content.role, content=request_content)
228257

229258

230259
def response_to_events(
@@ -234,6 +263,12 @@ def response_to_events(
234263
capture_content: bool,
235264
) -> Iterable[Event]:
236265
for candidate in response.candidates:
266+
tool_calls = _extract_tool_calls(
267+
candidate=candidate, capture_content=capture_content
268+
)
269+
270+
# The original function_call Part is still duplicated in message, see
271+
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3280
237272
yield choice_event(
238273
finish_reason=_map_finish_reason(candidate.finish_reason),
239274
index=candidate.index,
@@ -245,6 +280,31 @@ def response_to_events(
245280
parts=candidate.content.parts,
246281
),
247282
),
283+
tool_calls=tool_calls,
284+
)
285+
286+
287+
def _extract_tool_calls(
288+
*,
289+
candidate: content.Candidate | content_v1beta1.Candidate,
290+
capture_content: bool,
291+
) -> Iterable[ChoiceToolCall]:
292+
for idx, part in enumerate(candidate.content.parts):
293+
if "function_call" not in part:
294+
continue
295+
296+
yield ChoiceToolCall(
297+
# Make up an id with index since vertex expects the indices to line up instead of
298+
# using ids.
299+
id=f"{part.function_call.name}_{idx}",
300+
function=ChoiceToolCall.Function(
301+
name=part.function_call.name,
302+
arguments=json_format.MessageToDict(
303+
part.function_call._pb.args # type: ignore[reportUnknownMemberType]
304+
)
305+
if capture_content
306+
else None,
307+
),
248308
)
249309

250310

0 commit comments

Comments
 (0)