fiddler-adk 1.0.0rc1__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.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: fiddler-adk
3
+ Version: 1.0.0rc1
4
+ Summary: Fiddler SDK for Google ADK (Agent Development Kit) instrumentation with OpenTelemetry
5
+ Author-email: Fiddler AI <support@fiddler.ai>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://fiddler.ai
8
+ Project-URL: Documentation, https://docs.fiddler.ai
9
+ Project-URL: Repository, https://github.com/fiddler-labs/fiddler-sdk
10
+ Project-URL: Issues, https://github.com/fiddler-labs/fiddler-sdk/issues
11
+ Keywords: fiddler,ai,genai,llm,monitoring,observability,instrumentation,google-adk,adk,opentelemetry
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: System :: Monitoring
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Operating System :: OS Independent
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: fiddler-otel<2.0.0,>=1.0.0
27
+ Requires-Dist: google-adk>=1.34.2
28
+ Requires-Dist: opentelemetry-api>=1.37.0
29
+ Requires-Dist: opentelemetry-sdk>=1.37.0
30
+ Requires-Dist: opentelemetry-instrumentation>=0.58b0
31
+ Requires-Dist: opentelemetry-exporter-otlp>=1.37.0
32
+ Requires-Dist: pydantic>=2.0
33
+ Provides-Extra: examples
34
+ Requires-Dist: python-dotenv>=1.0.0; extra == "examples"
35
+
36
+ # fiddler-adk
37
+
38
+ Fiddler SDK for instrumenting [Google ADK](https://github.com/google/adk-python) (Agent Development Kit) with OpenTelemetry.
39
+
40
+ This package lives in the [fiddler-sdk](https://github.com/fiddler-labs/fiddler-sdk) monorepo under `packages/integrations/fiddler-adk/`. It depends on [`fiddler-otel`](../../fiddler-otel) for the OTel pipeline, span attributes, and span processing.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install fiddler-adk
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from fiddler_otel import FiddlerClient
52
+ from fiddler_adk import GoogleADKInstrumentor
53
+
54
+ client = FiddlerClient(
55
+ api_key='YOUR_API_KEY',
56
+ application_id='YOUR_APPLICATION_ID', # UUID4 from the Fiddler GenAI Applications page
57
+ url='https://your-instance.fiddler.ai',
58
+ )
59
+ GoogleADKInstrumentor(client).instrument()
60
+
61
+ # Build and run ADK agents as usual — traces flow to Fiddler automatically.
62
+ ```
63
+
64
+ ## How it works
65
+
66
+ Google ADK emits OpenTelemetry spans natively (`invoke_agent`, `call_llm`,
67
+ `execute_tool`, ...) through a tracer that resolves against the **global**
68
+ tracer provider. `GoogleADKInstrumentor.instrument()`:
69
+
70
+ 1. **Routes spans to Fiddler.** If no global tracer provider is configured,
71
+ the Fiddler client's provider (span processor + OTLP exporter) is promoted
72
+ to global. If your application (or `adk web`) already configured one, the
73
+ Fiddler span processor and exporter are attached to it instead.
74
+ 2. **Enriches span attributes.** An `ADKSpanProcessor` reads the JSON attributes
75
+ that ADK natively sets on spans (`gcp.vertex.agent.llm_request`, etc.) and
76
+ maps them to `gen_ai.llm.input.user`, `gen_ai.llm.input.system`,
77
+ `gen_ai.llm.output`, `gen_ai.tool.input`, and `gen_ai.tool.output` — the
78
+ attributes Fiddler's evaluations consume. No monkey-patching is involved.
79
+
80
+ ### Conversation and session context
81
+
82
+ ```python
83
+ import uuid
84
+ from fiddler_adk import add_session_attributes, set_conversation_id
85
+
86
+ set_conversation_id(str(uuid.uuid4())) # gen_ai.conversation.id on all spans
87
+ add_session_attributes('user_tier', 'premium') # fiddler.session.user.user_tier on all spans
88
+ ```
89
+
90
+ ### Content capture
91
+
92
+ ADK includes full LLM request/response payloads in span attributes by default.
93
+ Set `ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS=false` to disable ADK's payload
94
+ capture if your prompts may contain PII.
95
+
96
+ See `examples/` in this directory for a runnable agent.
@@ -0,0 +1,61 @@
1
+ # fiddler-adk
2
+
3
+ Fiddler SDK for instrumenting [Google ADK](https://github.com/google/adk-python) (Agent Development Kit) with OpenTelemetry.
4
+
5
+ This package lives in the [fiddler-sdk](https://github.com/fiddler-labs/fiddler-sdk) monorepo under `packages/integrations/fiddler-adk/`. It depends on [`fiddler-otel`](../../fiddler-otel) for the OTel pipeline, span attributes, and span processing.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install fiddler-adk
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from fiddler_otel import FiddlerClient
17
+ from fiddler_adk import GoogleADKInstrumentor
18
+
19
+ client = FiddlerClient(
20
+ api_key='YOUR_API_KEY',
21
+ application_id='YOUR_APPLICATION_ID', # UUID4 from the Fiddler GenAI Applications page
22
+ url='https://your-instance.fiddler.ai',
23
+ )
24
+ GoogleADKInstrumentor(client).instrument()
25
+
26
+ # Build and run ADK agents as usual — traces flow to Fiddler automatically.
27
+ ```
28
+
29
+ ## How it works
30
+
31
+ Google ADK emits OpenTelemetry spans natively (`invoke_agent`, `call_llm`,
32
+ `execute_tool`, ...) through a tracer that resolves against the **global**
33
+ tracer provider. `GoogleADKInstrumentor.instrument()`:
34
+
35
+ 1. **Routes spans to Fiddler.** If no global tracer provider is configured,
36
+ the Fiddler client's provider (span processor + OTLP exporter) is promoted
37
+ to global. If your application (or `adk web`) already configured one, the
38
+ Fiddler span processor and exporter are attached to it instead.
39
+ 2. **Enriches span attributes.** An `ADKSpanProcessor` reads the JSON attributes
40
+ that ADK natively sets on spans (`gcp.vertex.agent.llm_request`, etc.) and
41
+ maps them to `gen_ai.llm.input.user`, `gen_ai.llm.input.system`,
42
+ `gen_ai.llm.output`, `gen_ai.tool.input`, and `gen_ai.tool.output` — the
43
+ attributes Fiddler's evaluations consume. No monkey-patching is involved.
44
+
45
+ ### Conversation and session context
46
+
47
+ ```python
48
+ import uuid
49
+ from fiddler_adk import add_session_attributes, set_conversation_id
50
+
51
+ set_conversation_id(str(uuid.uuid4())) # gen_ai.conversation.id on all spans
52
+ add_session_attributes('user_tier', 'premium') # fiddler.session.user.user_tier on all spans
53
+ ```
54
+
55
+ ### Content capture
56
+
57
+ ADK includes full LLM request/response payloads in span attributes by default.
58
+ Set `ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS=false` to disable ADK's payload
59
+ capture if your prompts may contain PII.
60
+
61
+ See `examples/` in this directory for a runnable agent.
@@ -0,0 +1 @@
1
+ 1.0.0rc1
@@ -0,0 +1,113 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0,<82.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fiddler-adk"
7
+ dynamic = ["version"]
8
+ authors = [
9
+ {name = "Fiddler AI", email = "support@fiddler.ai"},
10
+ ]
11
+ description = "Fiddler SDK for Google ADK (Agent Development Kit) instrumentation with OpenTelemetry"
12
+ readme = "README.md"
13
+ license = "Apache-2.0"
14
+ requires-python = ">=3.10"
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Topic :: System :: Monitoring",
20
+ "Topic :: Software Development :: Quality Assurance",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Operating System :: OS Independent",
28
+ ]
29
+ keywords = ["fiddler", "ai", "genai", "llm", "monitoring", "observability", "instrumentation", "google-adk", "adk", "opentelemetry"]
30
+ dependencies = [
31
+ "fiddler-otel>=1.0.0,<2.0.0",
32
+ "google-adk>=1.34.2",
33
+ "opentelemetry-api>=1.37.0",
34
+ "opentelemetry-sdk>=1.37.0",
35
+ "opentelemetry-instrumentation>=0.58b0",
36
+ "opentelemetry-exporter-otlp>=1.37.0",
37
+ "pydantic>=2.0",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ examples = [
42
+ "python-dotenv>=1.0.0",
43
+ ]
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "setuptools>=61.0,<82.0.0",
48
+ "pytest>=9.0.3",
49
+ "pytest-asyncio>=0.24.0",
50
+ "pytest-cov>=6.1.1",
51
+ "mypy>=1.16.1",
52
+ "ruff>=0.15.10",
53
+ ]
54
+
55
+ [project.urls]
56
+ Homepage = "https://fiddler.ai"
57
+ Documentation = "https://docs.fiddler.ai"
58
+ Repository = "https://github.com/fiddler-labs/fiddler-sdk"
59
+ Issues = "https://github.com/fiddler-labs/fiddler-sdk/issues"
60
+
61
+ [tool.setuptools.packages.find]
62
+ where = ["src"]
63
+
64
+ [tool.setuptools.dynamic]
65
+ version = {file = ["VERSION"]}
66
+
67
+ [tool.setuptools.package-data]
68
+ fiddler_adk = ["VERSION"]
69
+
70
+ [tool.pytest.ini_options]
71
+ testpaths = ["tests"]
72
+ python_files = ["test_*.py", "*_test.py"]
73
+ python_classes = ["Test*"]
74
+ python_functions = ["test_*"]
75
+ asyncio_mode = "auto"
76
+
77
+ [tool.ruff]
78
+ line-length = 100
79
+ target-version = "py310"
80
+ extend-exclude = ["tests"]
81
+
82
+ [tool.ruff.format]
83
+ quote-style = "single"
84
+
85
+ [tool.ruff.lint]
86
+ select = [
87
+ "E", "F", "I", "N", "UP", "B", "C4", "DTZ", "T10", "RET", "SIM", "PTH", "ERA", "RUF",
88
+ ]
89
+ ignore = [
90
+ "E501", "B008", "B904", "SIM108", "E402", "ERA001",
91
+ ]
92
+ fixable = ["ALL"]
93
+
94
+ [tool.ruff.lint.isort]
95
+ known-first-party = ["fiddler_adk", "fiddler_otel"]
96
+
97
+ [tool.ruff.lint.per-file-ignores]
98
+ "__init__.py" = ["F401", "E402"]
99
+ "tests/*" = ["S101"]
100
+
101
+ [tool.mypy]
102
+ python_version = "3.10"
103
+ warn_return_any = true
104
+ warn_unused_configs = true
105
+ disallow_untyped_defs = true
106
+
107
+ [[tool.mypy.overrides]]
108
+ module = [
109
+ "google.adk.*",
110
+ "google.genai.*",
111
+ "pydantic",
112
+ ]
113
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ """Fiddler ADK - Google ADK (Agent Development Kit) instrumentation for Fiddler."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from fiddler_adk.instrumentation import GoogleADKInstrumentor
6
+ from fiddler_adk.span_processor import ADKSpanProcessor
7
+
8
+ try:
9
+ __version__ = version('fiddler-adk')
10
+ except PackageNotFoundError:
11
+ __version__ = 'unknown'
12
+
13
+ __all__ = [
14
+ 'ADKSpanProcessor',
15
+ 'GoogleADKInstrumentor',
16
+ '__version__',
17
+ ]
@@ -0,0 +1,121 @@
1
+ """OpenTelemetry instrumentation for Google ADK (Agent Development Kit).
2
+
3
+ Google ADK emits OpenTelemetry spans natively through a module-level tracer
4
+ named ``gcp.vertex.agent`` that resolves against the **global** tracer
5
+ provider. This instrumentor sets up a Fiddler-managed tracer provider (via
6
+ :class:`~fiddler_otel.client.FiddlerClient`) and promotes it to global so
7
+ ADK's tracer resolves to it.
8
+
9
+ The SDK operates in standalone mode: it initialises its own isolated tracer,
10
+ provider, processor, and exporter via ``FiddlerClient``. It does not interact
11
+ with customer-configured tracers or providers.
12
+
13
+ Content extraction and attribute mapping are handled by the Fiddler backend's
14
+ ``GoogleADKSpanMapper`` — not by this SDK.
15
+
16
+ Example::
17
+
18
+ from fiddler_otel import FiddlerClient
19
+ from fiddler_adk import GoogleADKInstrumentor
20
+
21
+ client = FiddlerClient(url="...", api_key="...", application_id="...")
22
+ GoogleADKInstrumentor(client).instrument()
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ from collections.abc import Collection
29
+ from importlib.metadata import PackageNotFoundError, version
30
+ from typing import Any
31
+
32
+ from opentelemetry import trace
33
+ from opentelemetry.instrumentation.instrumentor import ( # type: ignore[attr-defined]
34
+ BaseInstrumentor,
35
+ )
36
+ from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
37
+
38
+ from fiddler_adk.span_processor import ADKSpanProcessor
39
+ from fiddler_otel.client import FiddlerClient
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ def _get_package_version(package_name: str) -> str:
45
+ """Return the installed version of a package, or ``'unknown'``."""
46
+ try:
47
+ return version(package_name)
48
+ except PackageNotFoundError:
49
+ return 'unknown'
50
+
51
+
52
+ class GoogleADKInstrumentor(BaseInstrumentor):
53
+ """OpenTelemetry instrumentor for Google ADK agents.
54
+
55
+ Sets up the Fiddler tracing pipeline via ``FiddlerClient`` and promotes
56
+ it to the global tracer provider so ADK's ``gcp.vertex.agent`` tracer
57
+ resolves to it. Adds ``ADKSpanProcessor`` for root-span conversation-id
58
+ backfill.
59
+
60
+ The SDK operates in standalone mode — it initialises its own isolated
61
+ tracer, provider, processor, and exporter. It does not interact with
62
+ customer-configured tracers.
63
+ """
64
+
65
+ def __init__(self, client: FiddlerClient):
66
+ """Initialise the Google ADK instrumentor.
67
+
68
+ :param client: The ``FiddlerClient`` instance that owns the tracing
69
+ pipeline (tracer, provider, processor, exporter).
70
+ """
71
+ self._client = client
72
+ self._provider_configured = False
73
+
74
+ self._client.update_resource(
75
+ {
76
+ 'lib.google-adk.version': _get_package_version('google-adk'),
77
+ 'lib.fiddler-adk.version': _get_package_version('fiddler-adk'),
78
+ }
79
+ )
80
+ super().__init__()
81
+
82
+ def instrumentation_dependencies(self) -> Collection[str]:
83
+ """Return the packages required for this instrumentation."""
84
+ return ['google-adk']
85
+
86
+ def _instrument(self, **kwargs: Any) -> None:
87
+ """Enable instrumentation: set up tracer provider with ADKSpanProcessor."""
88
+ self._setup_tracer_provider()
89
+ logger.info('Google ADK instrumentation enabled')
90
+
91
+ def _uninstrument(self, **kwargs: Any) -> None:
92
+ """Disable instrumentation.
93
+
94
+ The tracer provider wiring is left in place: OpenTelemetry does not
95
+ support unsetting the global provider or removing span processors.
96
+ """
97
+ logger.info('Google ADK instrumentation disabled')
98
+
99
+ def _setup_tracer_provider(self) -> None:
100
+ """Set up the Fiddler tracer provider and promote it to global.
101
+
102
+ Uses ``FiddlerClient.get_tracer()`` which initialises the complete
103
+ tracing pipeline (provider, processor, exporter) in isolation.
104
+ The client's provider is then promoted to the global provider so
105
+ ADK's ``gcp.vertex.agent`` tracer resolves to it.
106
+ """
107
+ if self._provider_configured:
108
+ return
109
+
110
+ # Initialise the Fiddler tracing pipeline
111
+ self._client.get_tracer()
112
+ provider = self._client.get_tracer_provider()
113
+
114
+ # Add ADK-specific span processor for root-span conversation-id backfill.
115
+ if isinstance(provider, SDKTracerProvider):
116
+ provider.add_span_processor(ADKSpanProcessor())
117
+
118
+ # Promote to global so ADK's tracer resolves to Fiddler's provider
119
+ trace.set_tracer_provider(provider)
120
+
121
+ self._provider_configured = True
@@ -0,0 +1,109 @@
1
+ """ADK span processor: extends fiddler-otel with ADK-specific span enrichment.
2
+
3
+ This is a pure ``SpanProcessor`` approach — no monkey-patching.
4
+
5
+ Responsibilities:
6
+
7
+ * ``on_end``: backfill ``gen_ai.conversation.id`` onto the root ``invocation``
8
+ span, which ADK leaves attribute-less (it only stamps conversation id on
9
+ child spans).
10
+
11
+ All other enrichment (span-type classification, agent ID, content extraction,
12
+ attribute normalization) is handled by the Fiddler backend's
13
+ ``GoogleADKSpanMapper``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import threading
20
+
21
+ from opentelemetry import context
22
+ from opentelemetry.sdk.trace import ReadableSpan
23
+ from opentelemetry.sdk.trace import Span as SDKSpan
24
+ from opentelemetry.trace import Span
25
+
26
+ from fiddler_otel.attributes import FiddlerSpanAttributes
27
+ from fiddler_otel.span_processor import FiddlerSpanProcessor as CoreFiddlerSpanProcessor
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class ADKSpanProcessor(CoreFiddlerSpanProcessor):
33
+ """Backfills ``gen_ai.conversation.id`` onto the root ADK span.
34
+
35
+ Extends :class:`CoreFiddlerSpanProcessor` (contextvar injection, attribute
36
+ denormalization). ADK sets ``gen_ai.conversation.id`` only on child spans
37
+ (``invoke_agent``, ``call_llm``, etc.); the root ``invocation`` span gets no
38
+ attributes. This processor copies the conversation id onto the still-open
39
+ root span when the first child carrying it ends.
40
+
41
+ All other enrichment (span-type classification, agent ID computation,
42
+ content extraction, attribute normalization) is delegated to the Fiddler
43
+ backend's ``GoogleADKSpanMapper``.
44
+ """
45
+
46
+ def __init__(self) -> None:
47
+ """Initialise the processor and the live-root-span registry."""
48
+ super().__init__()
49
+ # Maps trace_id -> the still-open root span, so a child's
50
+ # ``gen_ai.conversation.id`` can be backfilled onto the root on end.
51
+ self._root_spans: dict[int, Span] = {}
52
+ self._root_lock = threading.Lock()
53
+
54
+ def on_start(self, span: Span, parent_context: context.Context | None = None) -> None:
55
+ """Track root spans, then delegate to core."""
56
+ self._track_root_span(span)
57
+ super().on_start(span, parent_context)
58
+
59
+ def on_end(self, span: ReadableSpan) -> None:
60
+ """Backfill ``gen_ai.conversation.id`` onto the root span.
61
+
62
+ ADK only sets ``gen_ai.conversation.id`` on child spans (``invoke_agent``,
63
+ ``call_llm``, etc.); the root ``invocation`` span gets no attributes. The
64
+ root span stays open for the whole run, so when a child ends we copy its
65
+ conversation id onto the still-recording root span.
66
+ """
67
+ try:
68
+ self._backfill_root_conversation_id(span)
69
+ except Exception as e: # pylint: disable=broad-except
70
+ logger.debug('Failed to backfill root conversation id: %s', e, exc_info=True)
71
+ super().on_end(span)
72
+
73
+ def _track_root_span(self, span: Span) -> None:
74
+ """Remember the root span of each trace so children can backfill onto it."""
75
+ if getattr(span, 'parent', None) is not None:
76
+ return
77
+ span_context = span.get_span_context()
78
+ if span_context is None:
79
+ return
80
+ with self._root_lock:
81
+ self._root_spans[span_context.trace_id] = span
82
+
83
+ def _backfill_root_conversation_id(self, span: ReadableSpan) -> None:
84
+ """Copy a child's conversation id onto the open root span; clean up on root end."""
85
+ span_context = span.context
86
+ if span_context is None:
87
+ return
88
+ trace_id = span_context.trace_id
89
+
90
+ with self._root_lock:
91
+ root = self._root_spans.get(trace_id)
92
+ # The root span itself is ending — drop the reference and stop.
93
+ if root is not None and span_context.span_id == root.get_span_context().span_id:
94
+ del self._root_spans[trace_id]
95
+ return
96
+
97
+ if not isinstance(root, SDKSpan):
98
+ return
99
+
100
+ conversation_id = (span.attributes or {}).get(FiddlerSpanAttributes.CONVERSATION_ID)
101
+ if not conversation_id:
102
+ return
103
+
104
+ if not (root.is_recording() and root.attributes is not None):
105
+ return
106
+ if FiddlerSpanAttributes.CONVERSATION_ID in root.attributes:
107
+ return
108
+
109
+ root.set_attribute(FiddlerSpanAttributes.CONVERSATION_ID, conversation_id)
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: fiddler-adk
3
+ Version: 1.0.0rc1
4
+ Summary: Fiddler SDK for Google ADK (Agent Development Kit) instrumentation with OpenTelemetry
5
+ Author-email: Fiddler AI <support@fiddler.ai>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://fiddler.ai
8
+ Project-URL: Documentation, https://docs.fiddler.ai
9
+ Project-URL: Repository, https://github.com/fiddler-labs/fiddler-sdk
10
+ Project-URL: Issues, https://github.com/fiddler-labs/fiddler-sdk/issues
11
+ Keywords: fiddler,ai,genai,llm,monitoring,observability,instrumentation,google-adk,adk,opentelemetry
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: System :: Monitoring
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Operating System :: OS Independent
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: fiddler-otel<2.0.0,>=1.0.0
27
+ Requires-Dist: google-adk>=1.34.2
28
+ Requires-Dist: opentelemetry-api>=1.37.0
29
+ Requires-Dist: opentelemetry-sdk>=1.37.0
30
+ Requires-Dist: opentelemetry-instrumentation>=0.58b0
31
+ Requires-Dist: opentelemetry-exporter-otlp>=1.37.0
32
+ Requires-Dist: pydantic>=2.0
33
+ Provides-Extra: examples
34
+ Requires-Dist: python-dotenv>=1.0.0; extra == "examples"
35
+
36
+ # fiddler-adk
37
+
38
+ Fiddler SDK for instrumenting [Google ADK](https://github.com/google/adk-python) (Agent Development Kit) with OpenTelemetry.
39
+
40
+ This package lives in the [fiddler-sdk](https://github.com/fiddler-labs/fiddler-sdk) monorepo under `packages/integrations/fiddler-adk/`. It depends on [`fiddler-otel`](../../fiddler-otel) for the OTel pipeline, span attributes, and span processing.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install fiddler-adk
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from fiddler_otel import FiddlerClient
52
+ from fiddler_adk import GoogleADKInstrumentor
53
+
54
+ client = FiddlerClient(
55
+ api_key='YOUR_API_KEY',
56
+ application_id='YOUR_APPLICATION_ID', # UUID4 from the Fiddler GenAI Applications page
57
+ url='https://your-instance.fiddler.ai',
58
+ )
59
+ GoogleADKInstrumentor(client).instrument()
60
+
61
+ # Build and run ADK agents as usual — traces flow to Fiddler automatically.
62
+ ```
63
+
64
+ ## How it works
65
+
66
+ Google ADK emits OpenTelemetry spans natively (`invoke_agent`, `call_llm`,
67
+ `execute_tool`, ...) through a tracer that resolves against the **global**
68
+ tracer provider. `GoogleADKInstrumentor.instrument()`:
69
+
70
+ 1. **Routes spans to Fiddler.** If no global tracer provider is configured,
71
+ the Fiddler client's provider (span processor + OTLP exporter) is promoted
72
+ to global. If your application (or `adk web`) already configured one, the
73
+ Fiddler span processor and exporter are attached to it instead.
74
+ 2. **Enriches span attributes.** An `ADKSpanProcessor` reads the JSON attributes
75
+ that ADK natively sets on spans (`gcp.vertex.agent.llm_request`, etc.) and
76
+ maps them to `gen_ai.llm.input.user`, `gen_ai.llm.input.system`,
77
+ `gen_ai.llm.output`, `gen_ai.tool.input`, and `gen_ai.tool.output` — the
78
+ attributes Fiddler's evaluations consume. No monkey-patching is involved.
79
+
80
+ ### Conversation and session context
81
+
82
+ ```python
83
+ import uuid
84
+ from fiddler_adk import add_session_attributes, set_conversation_id
85
+
86
+ set_conversation_id(str(uuid.uuid4())) # gen_ai.conversation.id on all spans
87
+ add_session_attributes('user_tier', 'premium') # fiddler.session.user.user_tier on all spans
88
+ ```
89
+
90
+ ### Content capture
91
+
92
+ ADK includes full LLM request/response payloads in span attributes by default.
93
+ Set `ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS=false` to disable ADK's payload
94
+ capture if your prompts may contain PII.
95
+
96
+ See `examples/` in this directory for a runnable agent.
@@ -0,0 +1,13 @@
1
+ README.md
2
+ VERSION
3
+ pyproject.toml
4
+ src/fiddler_adk/__init__.py
5
+ src/fiddler_adk/instrumentation.py
6
+ src/fiddler_adk/span_processor.py
7
+ src/fiddler_adk.egg-info/PKG-INFO
8
+ src/fiddler_adk.egg-info/SOURCES.txt
9
+ src/fiddler_adk.egg-info/dependency_links.txt
10
+ src/fiddler_adk.egg-info/requires.txt
11
+ src/fiddler_adk.egg-info/top_level.txt
12
+ tests/test_adk_instrumentation.py
13
+ tests/test_adk_span_processor.py
@@ -0,0 +1,10 @@
1
+ fiddler-otel<2.0.0,>=1.0.0
2
+ google-adk>=1.34.2
3
+ opentelemetry-api>=1.37.0
4
+ opentelemetry-sdk>=1.37.0
5
+ opentelemetry-instrumentation>=0.58b0
6
+ opentelemetry-exporter-otlp>=1.37.0
7
+ pydantic>=2.0
8
+
9
+ [examples]
10
+ python-dotenv>=1.0.0
@@ -0,0 +1 @@
1
+ fiddler_adk
@@ -0,0 +1,74 @@
1
+ """Tests for the GoogleADKInstrumentor."""
2
+
3
+ from unittest.mock import Mock, patch
4
+
5
+ import pytest
6
+ from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
7
+
8
+ from fiddler_adk import GoogleADKInstrumentor
9
+ from fiddler_adk.span_processor import ADKSpanProcessor
10
+
11
+
12
+ @pytest.fixture
13
+ def client():
14
+ """Mock FiddlerClient."""
15
+ mock_client = Mock()
16
+ mock_client.api_key = 'test-key'
17
+ mock_client.url = 'https://test.fiddler.ai'
18
+ mock_client.application_id = '00000000-0000-4000-8000-000000000000'
19
+ return mock_client
20
+
21
+
22
+ @pytest.fixture
23
+ def instrumentor(client):
24
+ """Fresh instrumentor."""
25
+ adk_instrumentor = GoogleADKInstrumentor(client)
26
+ adk_instrumentor._provider_configured = False
27
+ yield adk_instrumentor
28
+
29
+
30
+ class TestGoogleADKInstrumentor:
31
+ def test_instrumentation_dependencies(self, instrumentor):
32
+ assert 'google-adk' in instrumentor.instrumentation_dependencies()
33
+
34
+ def test_init_updates_client_resource(self, client):
35
+ GoogleADKInstrumentor(client)
36
+ attributes = client.update_resource.call_args[0][0]
37
+ assert 'lib.google-adk.version' in attributes
38
+ assert 'lib.fiddler-adk.version' in attributes
39
+
40
+ def test_requires_fiddler_client(self):
41
+ """Must be initialised with a FiddlerClient — no standalone mode."""
42
+ with pytest.raises(TypeError):
43
+ GoogleADKInstrumentor() # type: ignore[call-arg]
44
+
45
+ def test_setup_uses_client_provider(self, instrumentor, client):
46
+ """Uses FiddlerClient's own provider, promoted to global."""
47
+ with patch('fiddler_adk.instrumentation.trace') as mock_trace:
48
+ instrumentor._setup_tracer_provider()
49
+
50
+ client.get_tracer.assert_called_once()
51
+ mock_trace.set_tracer_provider.assert_called_once_with(
52
+ client.get_tracer_provider.return_value
53
+ )
54
+
55
+ def test_setup_is_idempotent(self, instrumentor, client):
56
+ with patch('fiddler_adk.instrumentation.trace'):
57
+ instrumentor._setup_tracer_provider()
58
+ instrumentor._setup_tracer_provider()
59
+
60
+ client.get_tracer.assert_called_once()
61
+
62
+ def test_no_monkey_patching(self, instrumentor):
63
+ assert not hasattr(instrumentor, '_patched')
64
+ assert not hasattr(instrumentor, '_wrapped_trace_call_llm')
65
+
66
+ def test_no_standalone_mode(self):
67
+ """No env-var-only / standalone pattern — always requires client."""
68
+ import inspect
69
+
70
+ sig = inspect.signature(GoogleADKInstrumentor.__init__)
71
+ params = list(sig.parameters.keys())
72
+ assert 'client' in params
73
+ # client should not have a default value (not Optional)
74
+ assert sig.parameters['client'].default is inspect.Parameter.empty
@@ -0,0 +1,64 @@
1
+ """Tests for the ADKSpanProcessor."""
2
+
3
+ import pytest
4
+ from opentelemetry.sdk.trace import TracerProvider
5
+
6
+ from fiddler_adk.span_processor import ADKSpanProcessor
7
+ from fiddler_otel.attributes import FiddlerSpanAttributes
8
+
9
+ CID = FiddlerSpanAttributes.CONVERSATION_ID
10
+
11
+
12
+ @pytest.fixture
13
+ def tracer():
14
+ """A tracer wired to an ADKSpanProcessor, mimicking the ADK span tree."""
15
+ provider = TracerProvider()
16
+ provider.add_span_processor(ADKSpanProcessor())
17
+ return provider.get_tracer('test')
18
+
19
+
20
+ class TestRootConversationIdBackfill:
21
+ def test_root_gets_conversation_id_from_child(self, tracer):
22
+ """ADK leaves the root ``invocation`` span attribute-less; a child's
23
+ conversation id should be backfilled onto it."""
24
+ with tracer.start_as_current_span('invocation') as root:
25
+ with tracer.start_as_current_span('invoke_agent time_agent') as child:
26
+ child.set_attribute(CID, 'session-abc')
27
+ assert root.attributes.get(CID) == 'session-abc'
28
+
29
+ def test_backfill_happens_while_root_open(self, tracer):
30
+ """The id should land on the root as soon as the first child ends."""
31
+ with tracer.start_as_current_span('invocation') as root:
32
+ with tracer.start_as_current_span('invoke_agent x') as child:
33
+ child.set_attribute(CID, 'session-xyz')
34
+ assert root.attributes.get(CID) == 'session-xyz'
35
+
36
+ def test_root_without_conversation_id_children_unchanged(self, tracer):
37
+ """No child carries a conversation id → root stays without one."""
38
+ with tracer.start_as_current_span('invocation') as root:
39
+ with tracer.start_as_current_span('invoke_agent x'):
40
+ pass
41
+ assert CID not in (root.attributes or {})
42
+
43
+ def test_first_child_conversation_id_wins(self, tracer):
44
+ """Once set, the root id is not overwritten by later children."""
45
+ with tracer.start_as_current_span('invocation') as root:
46
+ with tracer.start_as_current_span('invoke_agent a') as first:
47
+ first.set_attribute(CID, 'session-1')
48
+ with tracer.start_as_current_span('invoke_agent b') as second:
49
+ second.set_attribute(CID, 'session-2')
50
+ assert root.attributes.get(CID) == 'session-1'
51
+
52
+ def test_no_span_type_set(self, tracer):
53
+ """Span-type classification is delegated to the backend; the processor
54
+ must not set ``fiddler.span.type``."""
55
+ with tracer.start_as_current_span('invocation') as root:
56
+ with tracer.start_as_current_span('call_llm') as child:
57
+ child.set_attribute(CID, 'session-abc')
58
+ assert FiddlerSpanAttributes.TYPE not in (root.attributes or {})
59
+ assert FiddlerSpanAttributes.TYPE not in (child.attributes or {})
60
+
61
+
62
+ class TestGeneral:
63
+ def test_force_flush(self):
64
+ assert ADKSpanProcessor().force_flush() is True