Skip to content

Commit 5a1c47a

Browse files
committed
feat(py): Added metrics for observability and implemented AIM telemetry for firebase
1 parent f639667 commit 5a1c47a

File tree

9 files changed

+434
-32
lines changed

9 files changed

+434
-32
lines changed

py/plugins/firebase/src/genkit/plugins/firebase/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
"""Firebase Plugin for Genkit."""
1919

20+
from genkit.plugins.google_cloud.telemetry.tracing import add_gcp_telemetry
21+
2022

2123
def package_name() -> str:
2224
"""Get the package name for the Firebase plugin.
@@ -27,4 +29,13 @@ def package_name() -> str:
2729
return 'genkit.plugins.firebase'
2830

2931

30-
__all__ = ['package_name']
32+
def add_firebase_telemetry() -> None:
33+
"""Add Firebase telemetry export to Google Cloud Observability.
34+
35+
Exports traces to Cloud Trace and metrics to Cloud Monitoring.
36+
In development (GENKIT_ENV=dev), telemetry is disabled by default.
37+
"""
38+
add_gcp_telemetry(force_export=False)
39+
40+
41+
__all__ = ['package_name', 'add_firebase_telemetry']
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for Firebase telemetry functionality."""
16+
17+
from unittest.mock import MagicMock, patch
18+
19+
from opentelemetry.sdk.trace import ReadableSpan
20+
21+
from genkit.plugins.firebase import add_firebase_telemetry
22+
from genkit.plugins.google_cloud.telemetry.metrics import record_generate_metrics
23+
24+
25+
def _create_model_span(
26+
model_name: str = "gemini-pro",
27+
path: str = "/{myflow,t:flow}",
28+
output: str = '{"usage": {"inputTokens": 100, "outputTokens": 50}}',
29+
is_ok: bool = True,
30+
start_time: int = 1000000000,
31+
end_time: int = 1500000000,
32+
) -> MagicMock:
33+
"""Helper function to create a model action span for testing.
34+
35+
Args:
36+
model_name: The model name for genkit:name attribute
37+
path: The genkit:path value
38+
output: The genkit:output JSON string
39+
is_ok: Whether the span status is ok
40+
start_time: Span start time in nanoseconds
41+
end_time: Span end time in nanoseconds
42+
43+
Returns:
44+
A mocked ReadableSpan with model action attributes
45+
"""
46+
mock_span = MagicMock(spec=ReadableSpan)
47+
mock_span.attributes = {
48+
"genkit:type": "action",
49+
"genkit:metadata:subtype": "model",
50+
"genkit:name": model_name,
51+
"genkit:path": path,
52+
"genkit:output": output,
53+
}
54+
mock_span.status.is_ok = is_ok
55+
mock_span.start_time = start_time
56+
mock_span.end_time = end_time
57+
return mock_span
58+
59+
60+
@patch("genkit.plugins.firebase.add_gcp_telemetry")
61+
def test_firebase_telemetry_delegates_to_gcp(mock_add_gcp_telemetry):
62+
"""Test that Firebase telemetry delegates to GCP telemetry."""
63+
add_firebase_telemetry()
64+
mock_add_gcp_telemetry.assert_called_once_with(force_export=False)
65+
66+
67+
@patch("genkit.plugins.google_cloud.telemetry.metrics._output_tokens")
68+
@patch("genkit.plugins.google_cloud.telemetry.metrics._input_tokens")
69+
@patch("genkit.plugins.google_cloud.telemetry.metrics._latency")
70+
@patch("genkit.plugins.google_cloud.telemetry.metrics._failures")
71+
@patch("genkit.plugins.google_cloud.telemetry.metrics._requests")
72+
def test_record_generate_metrics_with_model_action(
73+
mock_requests,
74+
mock_failures,
75+
mock_latency,
76+
mock_input_tokens,
77+
mock_output_tokens,
78+
):
79+
"""Test that metrics are recorded for model action spans with usage data."""
80+
# Setup mocks
81+
mock_request_counter = MagicMock()
82+
mock_latency_histogram = MagicMock()
83+
mock_input_counter = MagicMock()
84+
mock_output_counter = MagicMock()
85+
86+
mock_requests.return_value = mock_request_counter
87+
mock_failures.return_value = MagicMock()
88+
mock_latency.return_value = mock_latency_histogram
89+
mock_input_tokens.return_value = mock_input_counter
90+
mock_output_tokens.return_value = mock_output_counter
91+
92+
# Create test span using helper
93+
mock_span = _create_model_span(
94+
model_name="gemini-pro",
95+
path="/{myflow,t:flow}",
96+
output='{"usage": {"inputTokens": 100, "outputTokens": 50}}',
97+
)
98+
99+
# Execute
100+
record_generate_metrics(mock_span)
101+
102+
# Verify dimensions
103+
expected_dimensions = {"model": "gemini-pro", "source": "myflow", "error": "none"}
104+
105+
# Verify requests counter
106+
mock_request_counter.add.assert_called_once_with(1, expected_dimensions)
107+
108+
# Verify latency (500ms = 1.5s - 1.0s)
109+
mock_latency_histogram.record.assert_called_once_with(500.0, expected_dimensions)
110+
111+
# Verify token counts
112+
mock_input_counter.add.assert_called_once_with(100, expected_dimensions)
113+
mock_output_counter.add.assert_called_once_with(50, expected_dimensions)

py/plugins/google-cloud/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ classifiers = [
1919
dependencies = [
2020
"genkit",
2121
"opentelemetry-exporter-gcp-trace>=1.9.0",
22+
"opentelemetry-exporter-gcp-monitoring>=1.9.0",
2223
"strenum>=0.4.15; python_version < '3.11'",
2324
]
2425
description = "Genkit Google Cloud Plugin"

py/plugins/google-cloud/src/genkit/plugins/google_cloud/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
"""Google Cloud Plugin for Genkit."""
1919

20+
from .telemetry import add_gcp_telemetry
21+
2022

2123
def package_name() -> str:
2224
"""Get the package name for the Google Cloud plugin.
@@ -27,4 +29,4 @@ def package_name() -> str:
2729
return 'genkit.plugins.google_cloud'
2830

2931

30-
__all__ = ['package_name']
32+
__all__ = ['package_name', 'add_gcp_telemetry']
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
"""Telemetry exports for Google Cloud plugin."""
18+
19+
from .tracing import add_gcp_telemetry
20+
21+
__all__ = ['add_gcp_telemetry']

0 commit comments

Comments
 (0)