fiddler-adk 1.0.0rc1__py3-none-any.whl

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,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,7 @@
1
+ fiddler_adk/__init__.py,sha256=b3gnUqv24j7yBFllu1XuSb5A1BUgxloLuwj_SMqs5N8,456
2
+ fiddler_adk/instrumentation.py,sha256=l--RYnW1IIPR0gkmKK4KQrnGIGWB-82GcmxOpukKsZA,4513
3
+ fiddler_adk/span_processor.py,sha256=1PGjJYUfAskTGDc7KPgNeMukpZa1XV9FyBlL1qwqQws,4392
4
+ fiddler_adk-1.0.0rc1.dist-info/METADATA,sha256=MRQo1YSfHQBX-wJENTuJh8vi1-Prp3XqUUiTBl94p9Y,3974
5
+ fiddler_adk-1.0.0rc1.dist-info/WHEEL,sha256=YLJXdYXQ2FQ0Uqn2J-6iEIC-3iOey8lH3xCtvFLkd8Q,91
6
+ fiddler_adk-1.0.0rc1.dist-info/top_level.txt,sha256=8MFhPOdEqsxcEyzJjg7CuXkoX4ro4lgvAx8reLEc5W0,12
7
+ fiddler_adk-1.0.0rc1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (81.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ fiddler_adk