ioa-observe-sdk 1.0.6__tar.gz → 1.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {ioa_observe_sdk-1.0.6/ioa_observe_sdk.egg-info → ioa_observe_sdk-1.0.8}/PKG-INFO +99 -47
  2. ioa_observe_sdk-1.0.8/README.md +143 -0
  3. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/decorators/__init__.py +22 -10
  4. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/decorators/base.py +82 -23
  5. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/decorators/util.py +17 -10
  6. ioa_observe_sdk-1.0.8/ioa_observe/sdk/instrumentations/a2a.py +111 -0
  7. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/instrumentations/slim.py +18 -18
  8. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/__init__.py +2 -2
  9. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/context_utils.py +19 -19
  10. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/tracing.py +48 -28
  11. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8/ioa_observe_sdk.egg-info}/PKG-INFO +99 -47
  12. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe_sdk.egg-info/SOURCES.txt +1 -0
  13. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe_sdk.egg-info/requires.txt +46 -46
  14. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/pyproject.toml +47 -47
  15. ioa_observe_sdk-1.0.6/README.md +0 -91
  16. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/LICENSE.md +0 -0
  17. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/__init__.py +0 -0
  18. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/__init__.py +0 -0
  19. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/client/__init__.py +0 -0
  20. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/client/client.py +0 -0
  21. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/client/http.py +0 -0
  22. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/config/__init__.py +0 -0
  23. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/connectors/__init__.py +0 -0
  24. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/connectors/slim.py +0 -0
  25. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/instrumentations/__init__.py +0 -0
  26. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/instruments.py +0 -0
  27. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/logging/__init__.py +0 -0
  28. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/logging/logging.py +0 -0
  29. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/__init__.py +0 -0
  30. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agent.py +0 -0
  31. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/__init__.py +0 -0
  32. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/agent_connections.py +0 -0
  33. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/availability.py +0 -0
  34. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/heuristics.py +0 -0
  35. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/recovery_tracker.py +0 -0
  36. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/tool_call_tracker.py +0 -0
  37. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/agents/tracker.py +0 -0
  38. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/metrics/metrics.py +0 -0
  39. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/telemetry.py +0 -0
  40. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/content_allow_list.py +0 -0
  41. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/context_manager.py +0 -0
  42. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/tracing/manual.py +0 -0
  43. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/utils/__init__.py +0 -0
  44. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/utils/const.py +0 -0
  45. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/utils/in_memory_span_exporter.py +0 -0
  46. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/utils/json_encoder.py +0 -0
  47. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/utils/package_check.py +0 -0
  48. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe/sdk/version.py +0 -0
  49. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe_sdk.egg-info/dependency_links.txt +0 -0
  50. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/ioa_observe_sdk.egg-info/top_level.txt +0 -0
  51. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/setup.cfg +0 -0
  52. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/tests/test_client.py +0 -0
  53. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/tests/test_instrumentor.py +0 -0
  54. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/tests/test_manual_instrumentation.py +0 -0
  55. {ioa_observe_sdk-1.0.6 → ioa_observe_sdk-1.0.8}/tests/test_version.py +0 -0
@@ -1,38 +1,38 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ioa-observe-sdk
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: IOA Observability SDK
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE.md
8
- Requires-Dist: aiohappyeyeballs==2.4.8
9
- Requires-Dist: aiohttp==3.11.18
10
- Requires-Dist: aiosignal==1.3.2
11
- Requires-Dist: annotated-types==0.7.0
12
- Requires-Dist: anyio==4.8.0
13
- Requires-Dist: async-timeout==4.0.3
14
- Requires-Dist: attrs==25.1.0
15
- Requires-Dist: backoff==2.2.1
16
- Requires-Dist: certifi==2025.1.31
17
- Requires-Dist: charset-normalizer==3.4.1
8
+ Requires-Dist: aiohappyeyeballs>=2.4.8
9
+ Requires-Dist: aiohttp>=3.11.18
10
+ Requires-Dist: aiosignal>=1.3.2
11
+ Requires-Dist: annotated-types>=0.7.0
12
+ Requires-Dist: anyio>=4.8.0
13
+ Requires-Dist: async-timeout>=4.0.3
14
+ Requires-Dist: attrs>=25.1.0
15
+ Requires-Dist: backoff>=2.2.1
16
+ Requires-Dist: certifi>=2025.1.31
17
+ Requires-Dist: charset-normalizer>=3.4.1
18
18
  Requires-Dist: colorama==0.4.6
19
- Requires-Dist: Deprecated==1.2.18
20
- Requires-Dist: distro==1.9.0
21
- Requires-Dist: exceptiongroup==1.2.2
22
- Requires-Dist: frozenlist==1.5.0
23
- Requires-Dist: googleapis-common-protos==1.69.0
24
- Requires-Dist: grpcio==1.70.0
25
- Requires-Dist: h11==0.16.0
26
- Requires-Dist: httpcore==1.0.9
27
- Requires-Dist: httpx==0.28.1
28
- Requires-Dist: idna==3.10
29
- Requires-Dist: importlib_metadata==8.5.0
30
- Requires-Dist: Jinja2==3.1.6
31
- Requires-Dist: jiter==0.8.2
32
- Requires-Dist: MarkupSafe==3.0.2
33
- Requires-Dist: monotonic==1.6
34
- Requires-Dist: multidict==6.1.0
35
- Requires-Dist: openai==1.75.0
19
+ Requires-Dist: Deprecated>=1.2.18
20
+ Requires-Dist: distro>=1.9.0
21
+ Requires-Dist: exceptiongroup>=1.2.2
22
+ Requires-Dist: frozenlist>=1.5.0
23
+ Requires-Dist: googleapis-common-protos>=1.69.0
24
+ Requires-Dist: grpcio>=1.70.0
25
+ Requires-Dist: h11>=0.16.0
26
+ Requires-Dist: httpcore>=1.0.9
27
+ Requires-Dist: httpx>=0.28.1
28
+ Requires-Dist: idna>=3.10
29
+ Requires-Dist: importlib_metadata>=8.5.0
30
+ Requires-Dist: Jinja2>=3.1.6
31
+ Requires-Dist: jiter>=0.8.2
32
+ Requires-Dist: MarkupSafe>=3.0.2
33
+ Requires-Dist: monotonic>=1.6
34
+ Requires-Dist: multidict>=6.1.0
35
+ Requires-Dist: openai>=1.75.0
36
36
  Requires-Dist: opentelemetry-api==1.33.1
37
37
  Requires-Dist: opentelemetry-distro
38
38
  Requires-Dist: opentelemetry-exporter-otlp==1.33.1
@@ -52,32 +52,32 @@ Requires-Dist: opentelemetry-sdk==1.33.1
52
52
  Requires-Dist: opentelemetry-semantic-conventions==0.54b1
53
53
  Requires-Dist: opentelemetry-semantic-conventions-ai==0.4.9
54
54
  Requires-Dist: opentelemetry-util-http==0.54b1
55
- Requires-Dist: packaging==24.2
56
- Requires-Dist: propcache==0.3.0
57
- Requires-Dist: protobuf==5.29.3
58
- Requires-Dist: pydantic==2.10.6
59
- Requires-Dist: pydantic_core==2.27.2
60
- Requires-Dist: python-dateutil==2.9.0.post0
55
+ Requires-Dist: packaging>=24.2
56
+ Requires-Dist: propcache>=0.3.0
57
+ Requires-Dist: protobuf>=5.29.3
58
+ Requires-Dist: pydantic>=2.10.6
59
+ Requires-Dist: pydantic_core>=2.27.2
60
+ Requires-Dist: python-dateutil>=2.9.0.post0
61
61
  Requires-Dist: regex==2024.11.6
62
- Requires-Dist: requests==2.32.3
63
- Requires-Dist: six==1.17.0
64
- Requires-Dist: sniffio==1.3.1
65
- Requires-Dist: tenacity==9.0.0
66
- Requires-Dist: tiktoken==0.9.0
67
- Requires-Dist: tqdm==4.67.1
68
- Requires-Dist: typing_extensions==4.12.2
69
- Requires-Dist: urllib3==2.3.0
70
- Requires-Dist: wrapt==1.17.2
71
- Requires-Dist: yarl==1.18.3
72
- Requires-Dist: zipp==3.21.0
62
+ Requires-Dist: requests>=2.32.3
63
+ Requires-Dist: six>=1.17.0
64
+ Requires-Dist: sniffio>=1.3.1
65
+ Requires-Dist: tenacity>=9.0.0
66
+ Requires-Dist: tiktoken>=0.9.0
67
+ Requires-Dist: tqdm>=4.67.1
68
+ Requires-Dist: typing_extensions>=4.12.2
69
+ Requires-Dist: urllib3>=2.3.0
70
+ Requires-Dist: wrapt>=1.17.2
71
+ Requires-Dist: yarl>=1.18.3
72
+ Requires-Dist: zipp>=3.21.0
73
73
  Requires-Dist: langgraph>=0.3.2
74
74
  Requires-Dist: langchain>=0.3.19
75
75
  Requires-Dist: langchain-openai>=0.3.8
76
76
  Requires-Dist: langchain-community>=0.3.25
77
77
  Requires-Dist: llama-index>=0.12.34
78
78
  Requires-Dist: opentelemetry-instrumentation-requests
79
- Requires-Dist: opentelemetry-instrumentation-transformers>=0.40.2
80
- Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.2
79
+ Requires-Dist: opentelemetry-instrumentation-transformers>=0.40.8
80
+ Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.8
81
81
  Requires-Dist: llama-index-utils-workflow>=0.3.1
82
82
  Requires-Dist: pytest
83
83
  Requires-Dist: pytest-vcr
@@ -85,6 +85,8 @@ Dynamic: license-file
85
85
 
86
86
  # Observe-SDK
87
87
 
88
+ [![PyPI version](https://img.shields.io/pypi/v/ioa-observe-sdk.svg)](https://pypi.org/project/ioa-observe-sdk/)
89
+
88
90
  IOA observability SDK for your multi-agentic application.
89
91
 
90
92
  ## Table of Contents
@@ -119,6 +121,20 @@ Link: [AGNTCY Observability Schema](https://github.com/agntcy/observe/blob/main/
119
121
 
120
122
  ## Dev
121
123
 
124
+ Any Opentelemetry compatible backend can be used, but for this guide, we will use ClickhouseDB as the backend database.
125
+
126
+ ### Opentelemetry collector
127
+
128
+ The OpenTelemetry Collector offers a vendor-agnostic implementation of how to receive, process and export telemetry data. It removes the need to run, operate, and maintain multiple agents/collectors.
129
+
130
+ ### Clickhouse DB
131
+
132
+ ClickhouseDB is used as a backend database to store and query the collected telemetry data efficiently, enabling you to analyze and visualize observability information for your multi-agentic applications.
133
+
134
+ ### Grafana (optional)
135
+
136
+ Grafana can be used to visualize the telemetry data collected by the OpenTelemetry Collector and stored in ClickhouseDB.
137
+
122
138
  To get started with development, start a Clickhouse DB and an OTel collector container locally using docker-compose like so:
123
139
 
124
140
  ```
@@ -126,6 +142,8 @@ cd deploy/
126
142
  docker compose up -d
127
143
  ```
128
144
 
145
+ Running both locally allows you to test, monitor, and debug your observability setup in a development environment before deploying to production.
146
+
129
147
  Ensure the contents of `otel-collector.yaml` is correct.
130
148
 
131
149
  Check the logs of the collector to ensure it is running correctly:
@@ -134,6 +152,18 @@ Check the logs of the collector to ensure it is running correctly:
134
152
  docker logs -f otel-collector
135
153
  ```
136
154
 
155
+ Viewing data in Clickhouse DB can be done using the Clickhouse client. The collector is configured to export telemetry data to Clickhouse.
156
+
157
+ The clickhouse exporter creates various tables in the Clickhouse DB, including `otel_traces`, which is used to store trace data.
158
+
159
+ For more info, refer to the [OpenTelemetry Clickhouse Exporter documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/clickhouseexporter/README.md)
160
+
161
+ ```bash
162
+ docker exec -it clickhouse-server clickhouse-client
163
+
164
+ select * from otel_traces LIMIT 10;
165
+ ```
166
+
137
167
  Create a `.env` file with the following content:
138
168
 
139
169
  ```bash
@@ -165,6 +195,28 @@ OPENAI_API_KEY=<KEY> make test
165
195
  For getting started with the SDK, please refer to the [Getting Started](https://github.com/agntcy/observe/blob/main/GETTING-STARTED.md)
166
196
  file. It contains detailed instructions on how to set up and use the SDK effectively.
167
197
 
198
+ ### Grafana
199
+
200
+ To configure Grafana to visualize the telemetry data, follow these steps:
201
+
202
+ 1. Spin up Grafana locally using Docker:
203
+
204
+ ```bash
205
+ docker run -d -p 3000:3000 --name=grafana grafana/grafana
206
+ ```
207
+ 2. Access Grafana by navigating to `http://localhost:3000` in your web browser.
208
+ - Default username: `admin`
209
+ - Default password: `admin`
210
+
211
+ 3. Add a new data source:
212
+ - Choose "ClickHouse" as the data source type.
213
+ - Set the URL to `http://0.0.0.0:8123`.
214
+ - Configure the authentication settings if necessary.
215
+ - Save and test the connection to ensure it works correctly.
216
+
217
+ Refer to the [Grafana ClickHouse plugin documentation](https://grafana.com/grafana/plugins/grafana-clickhouse-datasource/) for more details on configuring ClickHouse as a data source.
218
+
219
+
168
220
  ## Contributing
169
221
 
170
222
  Contributions are welcome! Please follow these steps to contribute:
@@ -0,0 +1,143 @@
1
+ # Observe-SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/ioa-observe-sdk.svg)](https://pypi.org/project/ioa-observe-sdk/)
4
+
5
+ IOA observability SDK for your multi-agentic application.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Schema](#schema)
11
+ - [Dev](#dev)
12
+ - [Testing](#testing)
13
+ - [Getting Started](#getting-started)
14
+ - [Contributing](#contributing)
15
+
16
+ ## Installation
17
+
18
+ To install the package via PyPI, simply run:
19
+
20
+ ```bash
21
+ pip install ioa_observe_sdk
22
+ ```
23
+
24
+ Alternatively, to download the SDK from git, you could also use the following command. Ensure you have `uv` installed in your environment.
25
+
26
+ ```bash
27
+ uv add "git+https://github.com/agntcy/observe"
28
+ ```
29
+
30
+ ## Schema
31
+
32
+ The AGNTCY observability schema is an extension of the OTel LLM Semantic Conventions for Generative AI systems.
33
+ This schema is designed to provide comprehensive observability for Multi-Agent Systems (MAS).
34
+
35
+ Link: [AGNTCY Observability Schema](https://github.com/agntcy/observe/blob/main/schema/)
36
+
37
+ ## Dev
38
+
39
+ Any Opentelemetry compatible backend can be used, but for this guide, we will use ClickhouseDB as the backend database.
40
+
41
+ ### Opentelemetry collector
42
+
43
+ The OpenTelemetry Collector offers a vendor-agnostic implementation of how to receive, process and export telemetry data. It removes the need to run, operate, and maintain multiple agents/collectors.
44
+
45
+ ### Clickhouse DB
46
+
47
+ ClickhouseDB is used as a backend database to store and query the collected telemetry data efficiently, enabling you to analyze and visualize observability information for your multi-agentic applications.
48
+
49
+ ### Grafana (optional)
50
+
51
+ Grafana can be used to visualize the telemetry data collected by the OpenTelemetry Collector and stored in ClickhouseDB.
52
+
53
+ To get started with development, start a Clickhouse DB and an OTel collector container locally using docker-compose like so:
54
+
55
+ ```
56
+ cd deploy/
57
+ docker compose up -d
58
+ ```
59
+
60
+ Running both locally allows you to test, monitor, and debug your observability setup in a development environment before deploying to production.
61
+
62
+ Ensure the contents of `otel-collector.yaml` is correct.
63
+
64
+ Check the logs of the collector to ensure it is running correctly:
65
+
66
+ ```
67
+ docker logs -f otel-collector
68
+ ```
69
+
70
+ Viewing data in Clickhouse DB can be done using the Clickhouse client. The collector is configured to export telemetry data to Clickhouse.
71
+
72
+ The clickhouse exporter creates various tables in the Clickhouse DB, including `otel_traces`, which is used to store trace data.
73
+
74
+ For more info, refer to the [OpenTelemetry Clickhouse Exporter documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/clickhouseexporter/README.md)
75
+
76
+ ```bash
77
+ docker exec -it clickhouse-server clickhouse-client
78
+
79
+ select * from otel_traces LIMIT 10;
80
+ ```
81
+
82
+ Create a `.env` file with the following content:
83
+
84
+ ```bash
85
+ OTLP_HTTP_ENDPOINT=http://localhost:4318
86
+ ```
87
+
88
+ Install the dependencies and activate the virtual environment:
89
+
90
+ ```bash
91
+ set -a
92
+ source .env
93
+ set +a
94
+
95
+ python3 -m venv .venv
96
+ source .venv/bin/activate
97
+ uv sync
98
+ ```
99
+
100
+ ## Testing
101
+
102
+ To run the unit tests, ensure you have the `OPENAI_API_KEY` set in your environment. You can run the tests using the following command:
103
+
104
+ ```bash
105
+ OPENAI_API_KEY=<KEY> make test
106
+ ```
107
+
108
+ ## 🚀 Getting Started
109
+
110
+ For getting started with the SDK, please refer to the [Getting Started](https://github.com/agntcy/observe/blob/main/GETTING-STARTED.md)
111
+ file. It contains detailed instructions on how to set up and use the SDK effectively.
112
+
113
+ ### Grafana
114
+
115
+ To configure Grafana to visualize the telemetry data, follow these steps:
116
+
117
+ 1. Spin up Grafana locally using Docker:
118
+
119
+ ```bash
120
+ docker run -d -p 3000:3000 --name=grafana grafana/grafana
121
+ ```
122
+ 2. Access Grafana by navigating to `http://localhost:3000` in your web browser.
123
+ - Default username: `admin`
124
+ - Default password: `admin`
125
+
126
+ 3. Add a new data source:
127
+ - Choose "ClickHouse" as the data source type.
128
+ - Set the URL to `http://0.0.0.0:8123`.
129
+ - Configure the authentication settings if necessary.
130
+ - Save and test the connection to ensure it works correctly.
131
+
132
+ Refer to the [Grafana ClickHouse plugin documentation](https://grafana.com/grafana/plugins/grafana-clickhouse-datasource/) for more details on configuring ClickHouse as a data source.
133
+
134
+
135
+ ## Contributing
136
+
137
+ Contributions are welcome! Please follow these steps to contribute:
138
+
139
+ 1. Fork the repository.
140
+ 2. Create a new branch (`git checkout -b feature-branch`).
141
+ 3. Commit your changes (`git commit -am 'Add new feature'`).
142
+ 4. Push to the branch (`git push origin feature-branch`).
143
+ 5. Create a new Pull Request.
@@ -19,15 +19,22 @@ F = TypeVar("F", bound=Callable[P, Union[R, Awaitable[R]]])
19
19
 
20
20
  def task(
21
21
  name: Optional[str] = None,
22
+ description: Optional[str] = None,
22
23
  version: Optional[int] = None,
23
24
  method_name: Optional[str] = None,
24
25
  tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
25
26
  ) -> Callable[[F], F]:
26
27
  if method_name is None:
27
- return entity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
28
+ return entity_method(
29
+ name=name,
30
+ description=description,
31
+ version=version,
32
+ tlp_span_kind=tlp_span_kind,
33
+ )
28
34
  else:
29
35
  return entity_class(
30
36
  name=name,
37
+ description=description,
31
38
  version=version,
32
39
  method_name=method_name,
33
40
  tlp_span_kind=tlp_span_kind,
@@ -36,21 +43,21 @@ def task(
36
43
 
37
44
  def workflow(
38
45
  name: Optional[str] = None,
46
+ description: Optional[str] = None,
39
47
  version: Optional[int] = None,
40
48
  method_name: Optional[str] = None,
41
49
  tlp_span_kind: Optional[
42
50
  Union[ObserveSpanKindValues, str]
43
51
  ] = ObserveSpanKindValues.WORKFLOW,
44
52
  ) -> Callable[[F], F]:
45
- if method_name is None:
46
- return entity_method(name=name, version=version, tlp_span_kind=tlp_span_kind)
47
- else:
48
- return entity_class(
49
- name=name,
50
- version=version,
51
- method_name=method_name,
52
- tlp_span_kind=tlp_span_kind,
53
- )
53
+ # Always use entity_class for class decorators
54
+ return entity_class(
55
+ name=name,
56
+ description=description,
57
+ version=version,
58
+ method_name=method_name,
59
+ tlp_span_kind=tlp_span_kind,
60
+ )
54
61
 
55
62
 
56
63
  def graph(
@@ -60,6 +67,7 @@ def graph(
60
67
  ) -> Callable[[F], F]:
61
68
  return workflow(
62
69
  name=name,
70
+ description=None,
63
71
  version=version,
64
72
  method_name=method_name,
65
73
  tlp_span_kind="graph",
@@ -68,11 +76,13 @@ def graph(
68
76
 
69
77
  def agent(
70
78
  name: Optional[str] = None,
79
+ description: Optional[str] = None,
71
80
  version: Optional[int] = None,
72
81
  method_name: Optional[str] = None,
73
82
  ) -> Callable[[F], F]:
74
83
  return workflow(
75
84
  name=name,
85
+ description=description,
76
86
  version=version,
77
87
  method_name=method_name,
78
88
  tlp_span_kind=ObserveSpanKindValues.AGENT,
@@ -81,11 +91,13 @@ def agent(
81
91
 
82
92
  def tool(
83
93
  name: Optional[str] = None,
94
+ description: Optional[str] = None,
84
95
  version: Optional[int] = None,
85
96
  method_name: Optional[str] = None,
86
97
  ) -> Callable[[F], F]:
87
98
  return task(
88
99
  name=name,
100
+ description=description,
89
101
  version=version,
90
102
  method_name=method_name,
91
103
  tlp_span_kind=ObserveSpanKindValues.TOOL,
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import json
5
+ import time
5
6
  import traceback
6
7
  from functools import wraps
7
8
  import os
@@ -91,6 +92,7 @@ def _setup_span(
91
92
  entity_name,
92
93
  tlp_span_kind: Optional[ObserveSpanKindValues] = None,
93
94
  version: Optional[int] = None,
95
+ description: Optional[str] = None,
94
96
  ):
95
97
  """Sets up the OpenTelemetry span and context"""
96
98
  if tlp_span_kind in [
@@ -100,8 +102,8 @@ def _setup_span(
100
102
  ]:
101
103
  set_workflow_name(entity_name)
102
104
  # if tlp_span_kind == "graph":
103
- # execution_id = entity_name + "_" + str(uuid.uuid4())
104
- # set_execution_id(execution_id)
105
+ # session_id = entity_name + "_" + str(uuid.uuid4())
106
+ # set_session_id(session_id)
105
107
  if tlp_span_kind == "graph":
106
108
  span_name = f"{entity_name}.{tlp_span_kind}"
107
109
  else:
@@ -122,13 +124,14 @@ def _setup_span(
122
124
  "agent_start_event",
123
125
  {
124
126
  "agent_name": entity_name,
127
+ "description": description if description else "",
125
128
  "type": tlp_span_kind.value,
126
129
  },
127
130
  )
128
131
  # start_span.end() # end the span immediately
129
- # execution_id = get_value("execution.id")
130
- # if execution_id is not None:
131
- # span.set_attribute("execution.id", execution_id)
132
+ # session_id = get_value("session.id")
133
+ # if session_id is not None:
134
+ # span.set_attribute("session.id", session_id)
132
135
  if tlp_span_kind in [
133
136
  ObserveSpanKindValues.TASK,
134
137
  ObserveSpanKindValues.TOOL,
@@ -145,9 +148,8 @@ def _setup_span(
145
148
  if version:
146
149
  span.set_attribute(OBSERVE_ENTITY_VERSION, version)
147
150
 
148
- # if execution_id:
149
- # print(f"Execution ID: {execution_id}")
150
- # span.set_attribute("execution.id", execution_id)
151
+ if tlp_span_kind == ObserveSpanKindValues.AGENT:
152
+ span.set_attribute("agent_chain_start_time", time.time())
151
153
 
152
154
  return span, ctx, ctx_token
153
155
 
@@ -243,12 +245,27 @@ def _handle_span_output(span, tlp_span_kind, res, cls=None):
243
245
 
244
246
  def _cleanup_span(span, ctx_token):
245
247
  """End the span process and detach the context token"""
248
+
249
+ # Calculate agent chain completion time before ending span
250
+ span_kind = span.attributes.get(OBSERVE_SPAN_KIND)
251
+ if span_kind == ObserveSpanKindValues.AGENT.value:
252
+ start_time = span.attributes.get("agent_chain_start_time")
253
+ if start_time is not None:
254
+ import time
255
+
256
+ completion_time = time.time() - start_time
257
+
258
+ # Emit the metric
259
+ TracerWrapper().agent_chain_completion_time_histogram.record(
260
+ completion_time, attributes=span.attributes
261
+ )
246
262
  span.end()
247
263
  context_api.detach(ctx_token)
248
264
 
249
265
 
250
266
  def entity_method(
251
267
  name: Optional[str] = None,
268
+ description: Optional[str] = None,
252
269
  version: Optional[int] = None,
253
270
  tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
254
271
  ) -> Callable[[F], F]:
@@ -266,7 +283,10 @@ def entity_method(
266
283
  return
267
284
 
268
285
  span, ctx, ctx_token = _setup_span(
269
- entity_name, tlp_span_kind, version
286
+ entity_name,
287
+ tlp_span_kind,
288
+ version,
289
+ description if description else None,
270
290
  )
271
291
  _handle_span_input(span, args, kwargs, cls=JSONEncoder)
272
292
 
@@ -284,7 +304,10 @@ def entity_method(
284
304
  return await fn(*args, **kwargs)
285
305
 
286
306
  span, ctx, ctx_token = _setup_span(
287
- entity_name, tlp_span_kind, version
307
+ entity_name,
308
+ tlp_span_kind,
309
+ version,
310
+ description if description else None,
288
311
  )
289
312
  _handle_span_input(span, args, kwargs, cls=JSONEncoder)
290
313
  success = False
@@ -359,6 +382,8 @@ def entity_method(
359
382
  _handle_agent_failure_event(str(e), span, tlp_span_kind)
360
383
  raise e
361
384
  finally:
385
+ if tlp_span_kind == ObserveSpanKindValues.AGENT:
386
+ TracerWrapper().record_agent_execution(entity_name, success)
362
387
  _cleanup_span(span, ctx_token)
363
388
  return res
364
389
 
@@ -370,10 +395,15 @@ def entity_method(
370
395
  if not TracerWrapper.verify_initialized():
371
396
  return fn(*args, **kwargs)
372
397
 
373
- span, ctx, ctx_token = _setup_span(entity_name, tlp_span_kind, version)
398
+ span, ctx, ctx_token = _setup_span(
399
+ entity_name,
400
+ tlp_span_kind,
401
+ version,
402
+ description if description else None,
403
+ )
374
404
 
375
405
  _handle_span_input(span, args, kwargs, cls=JSONEncoder)
376
- _handle_agent_span(span, entity_name, tlp_span_kind)
406
+ _handle_agent_span(span, entity_name, description, tlp_span_kind)
377
407
  success = False
378
408
 
379
409
  # Record heartbeat for agent
@@ -459,6 +489,8 @@ def entity_method(
459
489
  _handle_agent_failure_event(str(e), span, tlp_span_kind)
460
490
  raise e
461
491
  finally:
492
+ if tlp_span_kind == ObserveSpanKindValues.AGENT:
493
+ TracerWrapper().record_agent_execution(entity_name, success)
462
494
  _cleanup_span(span, ctx_token)
463
495
  return res
464
496
 
@@ -469,26 +501,51 @@ def entity_method(
469
501
 
470
502
  def entity_class(
471
503
  name: Optional[str],
504
+ description: Optional[str],
472
505
  version: Optional[int],
473
- method_name: str,
506
+ method_name: Optional[str],
474
507
  tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
475
508
  ):
476
509
  def decorator(cls):
477
510
  task_name = name if name else camel_to_snake(cls.__qualname__)
478
- method = getattr(cls, method_name)
479
- setattr(
480
- cls,
481
- method_name,
482
- entity_method(name=task_name, version=version, tlp_span_kind=tlp_span_kind)(
483
- method
484
- ),
485
- )
511
+
512
+ methods_to_wrap = []
513
+
514
+ if method_name:
515
+ # Specific method specified - existing behavior
516
+ methods_to_wrap = [method_name]
517
+ else:
518
+ # No method specified - wrap all public methods
519
+ for attr_name in dir(cls):
520
+ if (
521
+ not attr_name.startswith("_") # Skip private/built-in methods
522
+ and attr_name != "mro" # Skip class method
523
+ and hasattr(cls, attr_name)
524
+ and callable(getattr(cls, attr_name))
525
+ and not isinstance(
526
+ getattr(cls, attr_name), (classmethod, staticmethod, property)
527
+ )
528
+ ):
529
+ methods_to_wrap.append(attr_name)
530
+
531
+ # Wrap all detected methods
532
+ for method_to_wrap in methods_to_wrap:
533
+ if hasattr(cls, method_to_wrap):
534
+ method = getattr(cls, method_to_wrap)
535
+ wrapped_method = entity_method(
536
+ name=f"{task_name}.{method_to_wrap}",
537
+ description=description,
538
+ version=version,
539
+ tlp_span_kind=tlp_span_kind,
540
+ )(method)
541
+ setattr(cls, method_to_wrap, wrapped_method)
542
+
486
543
  return cls
487
544
 
488
545
  return decorator
489
546
 
490
547
 
491
- def _handle_agent_span(span, entity_name, tlp_span_kind):
548
+ def _handle_agent_span(span, entity_name, description, tlp_span_kind):
492
549
  if tlp_span_kind == ObserveSpanKindValues.AGENT:
493
550
  try:
494
551
  set_agent_id_event(entity_name)
@@ -496,6 +553,7 @@ def _handle_agent_span(span, entity_name, tlp_span_kind):
496
553
  "agent_start_event",
497
554
  {
498
555
  "agent_name": entity_name,
556
+ "description": description if description else "",
499
557
  "type": tlp_span_kind.value
500
558
  if tlp_span_kind != "graph"
501
559
  else "graph",
@@ -581,7 +639,8 @@ def _handle_graph_response(span, res, tlp_span_kind):
581
639
  }
582
640
 
583
641
  # Convert to JSON string
584
- s_graph_json = json.dumps(graph_dict, indent=2)
642
+ s_graph_json = json.dumps(graph_dict)
643
+ # convert to JSON and set as attribute
585
644
  span.set_attribute("gen_ai.ioa.graph", s_graph_json)
586
645
 
587
646
  # get graph dynamism