Skip to content

Commit 20197de

Browse files
RKestcopybara-github
authored andcommitted
fix(telemetry): emit gen_ai.workflow.nested for nested workflows
The `gen_ai.workflow.nested=true` attribute/dimension was never produced for any nested workflow in `gen_ai.invoke_workflow.duration` metric, so root and nested workflow invocations could not be distinguished. Root cause was a lost OpenTelemetry context value in `_use_invoke_workflow_span`. The "entrypoint workflow active" marker was only `set_value`'d onto the local `otel_context` handed to `tracer.start_as_current_span(context=...)`. OTel uses that argument solely to resolve the new span's parent; it does not attach it as the current context. The child telemetry context was then captured from `context_api.get_current()`, which never carried the marker, so every downstream workflow computed `nested=False`. Fix: after starting the span, `context_api.attach` the marker onto the current context (and `detach` on exit) so child nodes capturing `get_current()` observe `nested=True`. The now-redundant `set_value` onto the local context is removed. Extend the telemetry functional tests so the scenario runs a workflow nested inside a workflow. All expected span trees and the invoke_workflow duration metric now assert the inner workflow carries `gen_ai.workflow.nested=true` while the root omits it. This case previously went uncovered. Co-authored-by: Max Ind <maxind@google.com> PiperOrigin-RevId: 943153631
1 parent e6df097 commit 20197de

3 files changed

Lines changed: 232 additions & 47 deletions

File tree

src/google/adk/telemetry/node_tracing.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,6 @@ def _use_invoke_workflow_span(
186186
# The flag rides along the otel_context propagated to child nodes, so nested
187187
# workflows see it set.
188188
nested = bool(context_api.get_value(_ENTRYPOINT_WORKFLOW_KEY, otel_context))
189-
if not nested:
190-
otel_context = context_api.set_value(
191-
_ENTRYPOINT_WORKFLOW_KEY, True, otel_context
192-
)
193189
attributes: dict[str, AttributeValue] = {
194190
GEN_AI_OPERATION_NAME: "invoke_workflow",
195191
GEN_AI_CONVERSATION_ID: conversation_id,
@@ -207,11 +203,14 @@ def _use_invoke_workflow_span(
207203
start_s = time.monotonic()
208204
workflow_span: Span | None = None
209205
try:
210-
with tracer.start_as_current_span(
211-
name=span_name,
212-
attributes=attributes,
213-
context=otel_context,
214-
) as span:
206+
with (
207+
tracer.start_as_current_span(
208+
name=span_name,
209+
attributes=attributes,
210+
context=otel_context,
211+
) as span,
212+
_mark_nested_workflows(),
213+
):
215214
workflow_span = span
216215
yield span
217216
finally:
@@ -221,3 +220,14 @@ def _use_invoke_workflow_span(
221220
nested=nested,
222221
error=sys.exc_info()[1],
223222
)
223+
224+
225+
@contextmanager
226+
def _mark_nested_workflows() -> Iterator[None]:
227+
token = context_api.attach(
228+
context_api.set_value(_ENTRYPOINT_WORKFLOW_KEY, True)
229+
)
230+
try:
231+
yield
232+
finally:
233+
context_api.detach(token)

0 commit comments

Comments
 (0)